From fca5d728adeea3335eb4b1b526d6729cc0e25b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Bousqui=C3=A9?= Date: Fri, 18 Sep 2026 13:03:52 +0200 Subject: [PATCH] =?UTF-8?q?refactor(renderer):=20activate=20depth=20buffer?= =?UTF-8?q?=20on=20all=20render=20paths=20(=C3=89tape=209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 9.1: allocate shared Depth32Float depth texture + view in Renderer, sized to the initial surface (create_depth_texture helper, reusable for the Phase 4.4 resize); Renderer::new now takes width/height. - 9.2: attach depth_stencil_attachment (clear 1.0 / store) to both the low-level render and high-level render_scene passes. - 9.3: every pipeline declares a matching DepthStencilState (write true, compare Less) via the shared DEPTH_FORMAT constant (D1). Décisions D1-D4 actées 2026-09-18 (DRAFT Étape 9). --- lib/examples/manual.rs | 2 +- lib/src/app.rs | 2 +- lib/src/core/renderer.rs | 74 ++++++++++++++++++++++++++++-- lib/src/pipeline/mod.rs | 2 +- lib/src/pipeline/pipeline_cache.rs | 21 ++++++++- 5 files changed, 94 insertions(+), 7 deletions(-) diff --git a/lib/examples/manual.rs b/lib/examples/manual.rs index c6414f2..7e72d69 100644 --- a/lib/examples/manual.rs +++ b/lib/examples/manual.rs @@ -64,7 +64,7 @@ impl ApplicationHandler for App { // Rendu 2D plat : `standard` en mode unlit (les bind groups frame+object sont posés par // draw_entity, la matrice frame par défaut est l'identité → positions NDC inchangées). - let mut renderer = Renderer::new(&context, format); + let mut renderer = Renderer::new(&context, format, 800, 600); renderer.set_unlit(true); // 3. Material : On utilise renderer.device() et renderer.format() diff --git a/lib/src/app.rs b/lib/src/app.rs index e176e93..46f1f15 100644 --- a/lib/src/app.rs +++ b/lib/src/app.rs @@ -233,7 +233,7 @@ impl ApplicationHandler for AppRunner { .configure(&context.adapter, self.width, self.height) .expect("Échec configuration surface"); let device = Arc::new(context.device.clone()); - let renderer = Renderer::new(&context, format); + let renderer = Renderer::new(&context, format, self.width, self.height); // Étape 7 (DRAFT 7.1) : the PipelineCache now lives in the Scene. We wire the GPU context // (device + format + cache) into the Scene before setup so it can build materials/meshes. diff --git a/lib/src/core/renderer.rs b/lib/src/core/renderer.rs index f05cf92..89ddfc7 100644 --- a/lib/src/core/renderer.rs +++ b/lib/src/core/renderer.rs @@ -21,7 +21,7 @@ use crate::core::Context; use crate::core::Frame; use crate::math::Transform; -use crate::pipeline::create_uniform_bind_group_layouts; +use crate::pipeline::{DEPTH_FORMAT, create_uniform_bind_group_layouts}; use crate::resources::uniform::{FRAME_UNIFORMS_SIZE, OBJECT_UNIFORM_SIZE}; use crate::resources::{Camera, FrameUniforms, Material, Mesh, ObjectUniform}; use crate::scene::Scene; @@ -42,6 +42,14 @@ pub struct Renderer { device: wgpu::Device, /// Surface texture output format — stored here so it can be passed to PipelineCache on Material creation. format: wgpu::TextureFormat, + /// z-buffer texture backing `depth_view` (Étape 9). Held here only to keep the GPU resource + /// alive for the whole application lifetime (a `TextureView` alone does not guarantee the + /// underlying `Texture` stays valid in wgpu). Not read directly (hence `_` prefix → no + /// `dead_code`); reused when the depth texture is recreated at resize (ROADMAP Phase 4.4). + _depth_texture: wgpu::Texture, + /// Depth attachment view used by both render passes (`render`, `render_scene`). Format + /// `DEPTH_FORMAT` (Depth32Float) — matches every pipeline's `DepthStencilState` (D1). + depth_view: wgpu::TextureView, /// Bind group layout for the per-object uniforms (group 1) — must match every pipeline layout. object_layout: wgpu::BindGroupLayout, /// Shared per-frame uniform buffer handle — kept so the camera matrices can be rewritten each @@ -64,15 +72,21 @@ pub struct Renderer { impl Renderer { /// Creates a Renderer by cloning Device and Queue Arc references from the Context, plus capturing /// the surface format, and allocates the shared frame + object uniform buffers and their bind groups. - /// Inputs: context (borrowed reference to Context providing GPU resource handles), format (surface texture format). + /// Inputs: context (borrowed reference to Context providing GPU resource handles), format (surface + /// texture format), width (surface width in pixels) and height (surface height in pixels) — the + /// latter two size the depth texture allocated here (Étape 9). /// Returns a new Renderer instance sharing the same underlying GPU resources as Context. /// Called once at application startup during scene setup. The Renderer shares these resources via Arc; /// Context retains ownership and can continue using them after this call. - pub fn new(context: &Context, format: wgpu::TextureFormat) -> Self { + pub fn new(context: &Context, format: wgpu::TextureFormat, width: u32, height: u32) -> Self { let queue: wgpu::Queue = context.queue.clone(); let device: wgpu::Device = context.device.clone(); let [frame_layout, object_layout] = create_uniform_bind_group_layouts(&device); + // Étape 9 (DRAFT 9.1) : depth texture + view, allouées une seule fois à la taille initiale + // de la surface (D3). Le helper isolé rendra trivial le recreate planifié en Phase 4.4. + let (depth_texture, depth_view) = create_depth_texture(&device, width, height); + // Shared frame uniforms: identity camera + white directional light, lit mode by default. // Values become meaningful once an active camera is wired (Étape 4.3); for now the default // is a coherent scene when a shader actually reads them, and irrelevant to shaders that don't. @@ -115,6 +129,8 @@ impl Renderer { queue, device, format, + _depth_texture: depth_texture, + depth_view, object_layout, frame_buffer, frame_bind_group, @@ -200,6 +216,16 @@ impl Renderer { store: wgpu::StoreOp::Store, }, })], + // Étape 9 (DRAFT 9.2) : depth attachment via la view partagée (D1 : clear 1.0 + // = profondeur max au loin en début de frame, puis Store pour la garder). + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), ..Default::default() }); @@ -245,6 +271,16 @@ impl Renderer { store: wgpu::StoreOp::Store, }, })], + // Étape 9 (DRAFT 9.2) : même depth attachment que le chemin bas niveau, pour un + // z-test cohérent (D2 — les deux render passes partagent la depth_view). + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), ..Default::default() }); @@ -321,6 +357,38 @@ impl Renderer { } } +/// Allocates the depth texture + view backing the render passes' `depth_stencil_attachment` +/// (Étape 9, DRAFT 9.1). Format is the shared `DEPTH_FORMAT` (Depth32Float, D1) so it always +/// matches every pipeline's `DepthStencilState`. Sized to the surface (width x height), single +/// mip, no MSAA, used strictly as a render target. +/// +/// Exposed as a standalone helper so the depth texture can be recreated cheaply at resize +/// (ROADMAP Phase 4.4) without touching the render-pass logic. +/// Inputs: device (GPU resource creator), width (surface width), height (surface height). +/// Returns the (texture, view) pair; the caller keeps both alive. +fn create_depth_texture( + device: &wgpu::Device, + width: u32, + height: u32, +) -> (wgpu::Texture, wgpu::TextureView) { + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("depth texture"), + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + (depth_texture, depth_view) +} + /// Binds a Material pipeline, the two uniform bind groups, and Mesh buffers into an active render /// pass and issues the draw call. Shared by `Renderer::render` and `Renderer::render_scene`. /// The frame (group 0) and object (group 1) bind groups are **required** by every pipeline layout diff --git a/lib/src/pipeline/mod.rs b/lib/src/pipeline/mod.rs index 99f0a17..56e7cdd 100644 --- a/lib/src/pipeline/mod.rs +++ b/lib/src/pipeline/mod.rs @@ -11,4 +11,4 @@ pub mod pipeline_cache; // Re-exports -pub use pipeline_cache::{PipelineCache, create_uniform_bind_group_layouts}; +pub use pipeline_cache::{DEPTH_FORMAT, PipelineCache, create_uniform_bind_group_layouts}; diff --git a/lib/src/pipeline/pipeline_cache.rs b/lib/src/pipeline/pipeline_cache.rs index 9d182fe..0833aff 100644 --- a/lib/src/pipeline/pipeline_cache.rs +++ b/lib/src/pipeline/pipeline_cache.rs @@ -59,6 +59,15 @@ pub fn create_uniform_bind_group_layouts(device: &wgpu::Device) -> [wgpu::BindGr ] } +/// Depth texture format shared by the whole library (Étape 9, décision D1 du 2026-09-18). +/// +/// Single z-buffer format used for **both** the depth attachment textures (`Renderer`) and the +/// `DepthStencilState` of every pipeline (`build_pipeline`). Keeping them on the same constant +/// guarantees by construction that the pipeline depth format always matches the texture format +/// (wgpu validation error otherwise). `Depth32Float` = portée maximale (comparaison précise), +/// avec clear `1.0` (profondeur maximale au loin), `depth_compare: Less`, write enabled. +pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + /// Shader pipeline cache: maps (shader_id, format) keys to compiled RenderPipelines. /// Ensures each unique shader+format combination is compiled at most once; subsequent requests return cached instances. pub struct PipelineCache { @@ -230,7 +239,17 @@ impl PipelineCache { })], }), primitive: wgpu::PrimitiveState::default(), - depth_stencil: None, + // Étape 9 (DRAFT 9.3) : depth test activé sur TOUTE pipeline. Le format doit matcher + // l'attachment depth (DEPTH_FORMAT) — c'est garanti par la constante partagée D1. + // depth_write_enabled + depth_compare sont des Option en wgpu 30 : Some(true) → on + // écrit la profondeur ; Some(Less) → le fragment est gardé si son z est plus proche. + depth_stencil: Some(wgpu::DepthStencilState { + format: DEPTH_FORMAT, + depth_write_enabled: Some(true), + depth_compare: Some(wgpu::CompareFunction::Less), + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), multisample: wgpu::MultisampleState::default(), // multiview → replaced by multiview_mask (NonZeroU32) and cache fields in wgpu 30. multiview_mask: None,