doc
This commit is contained in:
+40
-40
@@ -13,10 +13,10 @@
|
||||
//! - **material**: provides the RenderPipeline reference via set_pipeline during draw.
|
||||
//!
|
||||
//! ## Architecture Notes (per ARCHI_APP.md)
|
||||
//! - **Phase d'Exécution**: Renderer executes per-frame render loops. During this phase it iterates Scene entities
|
||||
//! - **Execution Phase**: Renderer executes per-frame render loops. During this phase it iterates Scene entities
|
||||
//! and draws each one by binding the appropriate Material+Mesh pair.
|
||||
//! - **Performance**: Entity sorting within the render loop minimizes pipeline switches (batching par matériau).
|
||||
//! - **Accès Bas-Niveau**: Advanced users can bypass Scene and call Renderer directly for custom rendering paths.
|
||||
//! - **Performance**: Entity sorting within the render loop minimizes pipeline switches (batching by material).
|
||||
//! - **Low-Level Access**: Advanced users can bypass Scene and call Renderer directly for custom rendering paths.
|
||||
|
||||
use crate::core::Context;
|
||||
use crate::core::Frame;
|
||||
@@ -41,7 +41,7 @@ use std::collections::HashMap;
|
||||
/// plus the surface texture format. Executes WGPU rendering commands by binding Materials and Meshes
|
||||
/// into RenderPasses during each frame. Does not own raw hardware resources (they are Arc-cloned from Context).
|
||||
///
|
||||
/// Since Étape 3 every pipeline declares the two uniform bind groups (frame @0 + object @1), the
|
||||
/// Since Step 3 every pipeline declares the two uniform bind groups (frame @0 + object @1), the
|
||||
/// Renderer owns the matching GPU buffers and `BindGroup`s and binds them around every draw call.
|
||||
pub struct Renderer {
|
||||
/// GPU command submission queue — holds an Arc clone from Context; shared with other Context users.
|
||||
@@ -50,7 +50,7 @@ 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
|
||||
/// z-buffer texture backing `depth_view` (Step 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).
|
||||
@@ -73,9 +73,9 @@ pub struct Renderer {
|
||||
object_cache: RefCell<HashMap<String, (wgpu::Buffer, wgpu::BindGroup)>>,
|
||||
/// Flat (unlit) rendering flag, exposed via [`Renderer::set_unlit`]. When true, `options.x` of the
|
||||
/// `FrameUniforms` is set to 1 so the `standard` shader returns vertex colors as-is — flat 2D
|
||||
/// rendering is thus a special case of the 3D lit path (DRAFT Étape 5). Defaults to `false` (lit).
|
||||
/// rendering is thus a special case of the 3D lit path (DRAFT Step 5). Defaults to `false` (lit).
|
||||
unlit: bool,
|
||||
// Étape 14 (DRAFT 3.2) — shadow mapping resources, owned by the Renderer like the depth texture.
|
||||
// Step 14 (DRAFT 3.2) — shadow mapping resources, owned by the Renderer like the depth texture.
|
||||
/// Backing GPU shadow-map texture (D2), kept alive for the whole application lifetime. Sized
|
||||
/// `SHADOW_MAP_SIZE²`, `DEPTH_FORMAT`, used as the shadow pass depth attachment **and** bound
|
||||
/// for sampling in the main pass (`RENDER_ATTACHMENT | TEXTURE_BINDING`).
|
||||
@@ -98,7 +98,7 @@ impl Renderer {
|
||||
/// 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), width (surface width in pixels) and height (surface height in pixels) — the
|
||||
/// latter two size the depth texture allocated here (Étape 9).
|
||||
/// latter two size the depth texture allocated here (Step 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.
|
||||
@@ -107,12 +107,12 @@ impl Renderer {
|
||||
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.
|
||||
// Step 9 (DRAFT 9.1): depth texture + view, allocated once at the initial surface
|
||||
// size (D3). The isolated helper keeps the Phase 4.4 recreate trivial.
|
||||
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
|
||||
// Values become meaningful once an active camera is wired (Step 4.3); for now the default
|
||||
// is a coherent scene when a shader actually reads them, and irrelevant to shaders that don't.
|
||||
let frame_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("frame uniform buffer"),
|
||||
@@ -149,7 +149,7 @@ impl Renderer {
|
||||
}],
|
||||
});
|
||||
|
||||
// Étape 14 (DRAFT 3.2) : shadow mapping resources — shadow map texture/view, comparison
|
||||
// Step 14 (DRAFT 3.2): shadow mapping resources — shadow map texture/view, comparison
|
||||
// sampler, group-3 bind group, shadow-light uniform buffer + group-0 bind group, and the
|
||||
// depth-only shadow pipeline. All allocated once here at the default resolution (D2/D8).
|
||||
let (shadow_texture, shadow_view) = create_shadow_map(&device, SHADOW_MAP_SIZE);
|
||||
@@ -169,7 +169,7 @@ impl Renderer {
|
||||
// compare function holds for `compare_op(depth_ref, sampled)`, so `LessEqual` is the
|
||||
// correct choice: `depth_ref (= current_depth - bias) <= stored_depth` → lit. Using
|
||||
// `GreaterEqual` here inverts the test (shadowed regions render lit, directly-lit
|
||||
// surfaces self-shadow to black) — the regression seen in the Étape 14 `shadow_test`.
|
||||
// surfaces self-shadow to black) — the regression seen in the Step 14 `shadow_test`.
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
..Default::default()
|
||||
});
|
||||
@@ -236,7 +236,7 @@ impl Renderer {
|
||||
fn write_default_frame_uniforms(&self) {
|
||||
let frame = FrameUniforms {
|
||||
options: [if self.unlit { 1 } else { 0 }, 0, 0, 0],
|
||||
// Étape 14 (D2) : no active shadow caster in the low-level path — sentinel index
|
||||
// Step 14 (D2): no active shadow caster in the low-level path — sentinel index
|
||||
// MAX_LIGHTS disables the shadow term in the shader even if options.y were set.
|
||||
shadow_light_index: MAX_LIGHTS as u32,
|
||||
..FrameUniforms::default()
|
||||
@@ -246,9 +246,9 @@ impl Renderer {
|
||||
}
|
||||
|
||||
/// Toggles flat (unlit) rendering. When true, the `standard` shader returns vertex colors as-is
|
||||
/// (`options.x = 1`), so flat 2D rendering is a special case of the 3D lit path (DRAFT Étape 5 :
|
||||
/// « 2D ⊂ 3D »). Rewrites the shared frame buffer immediately so the low-level `render` path picks
|
||||
/// up the change ; the `render_scene` path reads the flag each frame in `write_frame_uniforms`.
|
||||
/// (`options.x = 1`), so flat 2D rendering is a special case of the 3D lit path (DRAFT Step 5:
|
||||
/// "2D ⊂ 3D"). Rewrites the shared frame buffer immediately so the low-level `render` path picks
|
||||
/// up the change; the `render_scene` path reads the flag each frame in `write_frame_uniforms`.
|
||||
/// Inputs: unlit — true for flat rendering, false (default) for Phong-lit rendering.
|
||||
pub fn set_unlit(&mut self, unlit: bool) {
|
||||
self.unlit = unlit;
|
||||
@@ -257,7 +257,7 @@ impl Renderer {
|
||||
|
||||
/// Recreates the depth texture at a new size, used on window resize (ROADMAP Phase 4.4).
|
||||
/// The previous depth texture is dropped when its field is replaced — no leak, no double
|
||||
/// allocation. The helper `create_depth_texture` (Étape 9, D3) is reused so the recreate stays
|
||||
/// allocation. The helper `create_depth_texture` (Step 9, D3) is reused so the recreate stays
|
||||
/// trivial. Inputs: width/height — the new surface dimensions in pixels.
|
||||
pub fn resize_depth(&mut self, width: u32, height: u32) {
|
||||
let (depth_texture, depth_view) = create_depth_texture(&self.device, width, height);
|
||||
@@ -275,7 +275,7 @@ impl Renderer {
|
||||
/// Rewrites the shared per-frame uniform buffer from the scene's active camera, its global
|
||||
/// light list, its ambient color, and the current viewport aspect, then returns the frame bind
|
||||
/// group wired to that buffer. Called at the start of every `render_scene` so the GPU sees the
|
||||
/// latest camera matrices, camera position, and lighting (Étape 4.3, Étapes 12–13).
|
||||
/// latest camera matrices, camera position, and lighting (Step 4.3, Steps 12–13).
|
||||
///
|
||||
/// The light array is packed via `Lights::into_frame_array` (directionals first, then point,
|
||||
/// then spot lights). Inputs: camera (the scene's active camera), lights (the scene's global
|
||||
@@ -291,7 +291,7 @@ impl Renderer {
|
||||
shadow_caster: Option<usize>,
|
||||
) {
|
||||
let (light_array, num_directional, num_point, num_spot) = lights.into_frame_array();
|
||||
// Étape 14 (DRAFT 3.2) : derive the shadow light's view_proj and shadow flags (D3).
|
||||
// Step 14 (DRAFT 3.2): derive the shadow light's view_proj and shadow flags (D3).
|
||||
let (shadow_light_index, light_view_proj, shadow_params, shadow_on) =
|
||||
match self.shadow_light_view_proj(lights, shadow_caster) {
|
||||
Some((index, vp)) => (
|
||||
@@ -404,8 +404,8 @@ 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).
|
||||
// Step 9 (DRAFT 9.2): depth attachment via the shared view (D1: clear 1.0
|
||||
// = max depth far away at frame start, then Store to keep it).
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
@@ -438,7 +438,7 @@ impl Renderer {
|
||||
/// projection.
|
||||
///
|
||||
/// Before drawing, the shared frame uniform buffer is rewritten from `scene.camera()` so the GPU
|
||||
/// receives the active camera's view/projection matrices and position for this frame (Étape 4.3).
|
||||
/// receives the active camera's view/projection matrices and position for this frame (Step 4.3).
|
||||
pub fn render_scene(&self, view: &wgpu::TextureView, scene: &Scene, aspect: f32) {
|
||||
self.write_frame_uniforms(
|
||||
scene.camera(),
|
||||
@@ -454,7 +454,7 @@ impl Renderer {
|
||||
label: Some("scene encoder"),
|
||||
});
|
||||
|
||||
// Étape 14 (DRAFT 3.2) : run the depth-only shadow pass first when a light is configured to
|
||||
// Step 14 (DRAFT 3.2): run the depth-only shadow pass first when a light is configured to
|
||||
// cast shadows (D4). It populates `shadow_view` on the shared encoder; the main pass below
|
||||
// then samples it via `shadow_bind_group`. `render_shadow_map` no-ops when shadows are off.
|
||||
self.render_shadow_map(&mut encoder, scene);
|
||||
@@ -471,8 +471,8 @@ 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).
|
||||
// Step 9 (DRAFT 9.2): same depth attachment as the low-level path, for a
|
||||
// coherent z-test (D2 — both render passes share the depth_view).
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
@@ -484,7 +484,7 @@ impl Renderer {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Étape 7 (DRAFT 7.3.4) : the Material is resolved from the Mesh itself, falling back
|
||||
// Step 7 (DRAFT 7.3.4): the Material is resolved from the Mesh itself, falling back
|
||||
// to the Scene's default material when the mesh carries none.
|
||||
for (label, mesh, transform) in scene.iter_entities() {
|
||||
let material = mesh
|
||||
@@ -506,7 +506,7 @@ impl Renderer {
|
||||
}
|
||||
|
||||
/// Renders every entity of `scene` from the shadow-casting light's point of view into the
|
||||
/// shadow depth map (Étape 14, D4), using the dedicated depth-only `shadow_pipeline`. Called at
|
||||
/// shadow depth map (Step 14, D4), using the dedicated depth-only `shadow_pipeline`. Called at
|
||||
/// the start of `render_scene`. No-ops (produces no GPU work) when `scene.shadow_caster()` is
|
||||
/// `None`. The shadow light's `view_proj` is written to `shadow_uniform_buffer`, and the shadow
|
||||
/// pass writes depth into `shadow_view` (clear 1.0, store). The per-entity model bind groups are
|
||||
@@ -533,7 +533,7 @@ impl Renderer {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("shadow map render pass"),
|
||||
color_attachments: &[],
|
||||
// Depth-only : the shadow map is the sole attachment. Clear 1.0 so fragments beyond
|
||||
// Depth-only: the shadow map is the sole attachment. Clear 1.0 so fragments beyond
|
||||
// `far` read as "fully distant" and never occlude lit surfaces (D4).
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.shadow_view,
|
||||
@@ -546,11 +546,11 @@ impl Renderer {
|
||||
..Default::default()
|
||||
});
|
||||
pass.set_pipeline(&self.shadow_pipeline);
|
||||
// Group 0 : the shadow light view_proj (D4) — the shadow pipeline's only uniform group.
|
||||
// Group 0: the shadow light view_proj (D4) — the shadow pipeline's only uniform group.
|
||||
pass.set_bind_group(0, &self.shadow_uniform_bind_group, &[]);
|
||||
for (label, mesh, transform) in scene.iter_entities() {
|
||||
let object_bind_group = self.object_bind_group_for(label, transform);
|
||||
// Group 1 : per-entity model. The shadow pipeline has no texture/sampler groups.
|
||||
// Group 1: per-entity model. The shadow pipeline has no texture/sampler groups.
|
||||
pass.set_bind_group(1, &object_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||
if let Some(index_buffer) = &mesh.index_buffer {
|
||||
@@ -585,7 +585,7 @@ impl Renderer {
|
||||
|
||||
/// Returns the per-entity object bind group for `label`, creating its uniform buffer on first
|
||||
/// encounter and rewriting the model matrix each call. Since `render_scene(&self)` is immutable,
|
||||
/// the lazily-populated cache is interior-mutable (`RefCell`). Étape 4.2.
|
||||
/// the lazily-populated cache is interior-mutable (`RefCell`). Step 4.2.
|
||||
/// Inputs: label (entity identifier used as cache key), transform (world placement to upload).
|
||||
/// Returns an owned (cheaply Arc-cloned) reference handle to the object bind group (group 1).
|
||||
fn object_bind_group_for(&self, label: &str, transform: &Transform) -> wgpu::BindGroup {
|
||||
@@ -618,7 +618,7 @@ 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
|
||||
/// (Step 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.
|
||||
///
|
||||
@@ -650,7 +650,7 @@ fn create_depth_texture(
|
||||
}
|
||||
|
||||
/// Allocates the shadow-map texture + view backing the depth-only shadow pass's
|
||||
/// `depth_stencil_attachment` (Étape 14, D2/D8). Square (`size` x `size`), `DEPTH_FORMAT`, single
|
||||
/// `depth_stencil_attachment` (Step 14, D2/D8). Square (`size` x `size`), `DEPTH_FORMAT`, single
|
||||
/// mip, no MSAA. Unlike the screen depth texture this one is flagged **both** `RENDER_ATTACHMENT`
|
||||
/// (shadow pass writes depth) **and** `TEXTURE_BINDING` (main pass samples it via the group-3
|
||||
/// comparison sampler). Allocated once at the default resolution; resizing is deferred (D8).
|
||||
@@ -678,8 +678,8 @@ fn create_shadow_map(device: &wgpu::Device, size: u32) -> (wgpu::Texture, wgpu::
|
||||
/// Binds a Material pipeline, the four shared 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 (@0), object (@1), texture (@2) and shadow-map (@3) bind groups are **required** by
|
||||
/// every pipeline layout (Étape 3 : un seul layout pour tous — Étape 10 : groupe texture — Étape 14 :
|
||||
/// groupe ombre) — they must be bound even if the shader does not read them. Draws indexed geometry
|
||||
/// every pipeline layout (Step 3: a single layout for all — Step 10: texture group — Step 14:
|
||||
/// shadow group) — they must be bound even if the shader does not read them. Draws indexed geometry
|
||||
/// when an index buffer exists, otherwise falls back to a non-indexed draw.
|
||||
/// Inputs: pass (active render pass), mesh (geometry to draw), material (pipeline + texture bind
|
||||
/// group to bind), frame_bind_group (shared per-frame uniforms), object_bind_group (per-entity/
|
||||
@@ -700,11 +700,11 @@ fn draw_entity(
|
||||
pass.set_pipeline(&material.pipeline);
|
||||
pass.set_bind_group(0, frame_bind_group, &[]);
|
||||
pass.set_bind_group(1, object_bind_group, &[]);
|
||||
// Étape 10 (DRAFT 10.4) : groupe texture — le Material possède son bind group (placeholder
|
||||
// blanc s'il n'a pas de texture, D1/D2). Toujours liable car posé sur toutes les pipelines.
|
||||
// Step 10 (DRAFT 10.4): texture group — the Material owns its bind group (placeholder
|
||||
// white if it has no texture, D1/D2). Always bindable since it is attached to every pipeline.
|
||||
pass.set_bind_group(2, &material.texture_bind_group, &[]);
|
||||
// Étape 14 : groupe ombre — toujours lié pour rester conforme au layout unifié, que la pipeline
|
||||
// soit éclairée ou non (le groupe @3 reste requis par toutes les pipelines standards).
|
||||
// Step 14: shadow group — always bound to stay conformant with the unified layout, whether
|
||||
// the pipeline is lit or not (group @3 is still required by all standard pipelines).
|
||||
pass.set_bind_group(3, shadow_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||
if let Some(index_buffer) = &mesh.index_buffer {
|
||||
|
||||
Reference in New Issue
Block a user