This commit is contained in:
Jérôme Bousquié
2026-09-25 13:43:59 +02:00
parent 9614156848
commit 8ece89ccba
22 changed files with 1998 additions and 245 deletions
+134 -6
View File
@@ -170,6 +170,12 @@ pub struct Renderer {
msaa_depth_texture: Option<wgpu::Texture>,
/// MSAA depth view used as the main pass depth attachment when MSAA is active.
msaa_depth_view: Option<wgpu::TextureView>,
/// Fog configuration (Étape 25). `None` = fog disabled (zero overhead).
fog: Option<super::fog::FogConfig>,
/// DoF configuration (Étape 26). `None` = DoF disabled (zero overhead).
dof: Option<super::dof::DoFConfig>,
/// DoF pipeline (Étape 26). Present only when DoF + HDR are both active.
dof_pipeline: Option<super::dof::DoFPipeline>,
}
/// Internal HDR pipeline state: offscreen `Rgba16Float` texture + tone mapping render pipeline.
@@ -213,6 +219,8 @@ impl Renderer {
hdr: Option<ToneMapper>,
bloom_config: Option<BloomConfig>,
msaa_config: Option<MsaaConfig>,
fog: Option<super::fog::FogConfig>,
dof_config: Option<super::dof::DoFConfig>,
) -> Self {
let queue: wgpu::Queue = context.queue.clone();
let device: wgpu::Device = context.device.clone();
@@ -220,7 +228,7 @@ impl Renderer {
// 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);
let (depth_texture, depth_view) = create_depth_texture(&device, width, height, dof_config.is_some());
// Shared frame uniforms: identity camera + white directional light, lit mode by default.
// Values become meaningful once an active camera is wired (Step 4.3); for now the default
@@ -606,11 +614,16 @@ impl Renderer {
hdr: None,
bloom: None,
bloom_config: bloom_config.clone().unwrap_or_default(),
msaa_config: msaa_config.clone().unwrap_or_default(),
// When MSAA is not requested (None), store sample_count=1 (disabled).
// Using `Default` here would give 4 and incorrectly trigger MSAA allocation.
msaa_config: msaa_config.unwrap_or(MsaaConfig { sample_count: 1 }),
msaa_color_texture: None,
msaa_color_view: None,
msaa_depth_texture: None,
msaa_depth_view: None,
fog,
dof: dof_config,
dof_pipeline: None,
};
// Seed the shared frame buffer with an identity camera + current unlit flag so the low-level
// `render` path (which has no window/camera) sees coherent values before `render_scene` runs.
@@ -657,6 +670,30 @@ impl Renderer {
renderer.msaa_depth_texture = Some(msaa_depth_tex);
renderer.msaa_depth_view = Some(msaa_depth_view);
}
// Étape 26: allocate the DoF pipeline when DoF + HDR are both active.
if renderer.dof.is_some() {
if let Some(hdr) = &mut renderer.hdr {
// The color source for DoF is the HDR texture (or bloom composite if bloom is active).
let color_tex: &wgpu::Texture = if let Some(bloom) = &renderer.bloom {
bloom.composite_texture()
} else {
&hdr.texture
};
let color_view = color_tex.create_view(&Default::default());
let dof_pipe = super::dof::DoFPipeline::new(
&renderer.device, width, height, &renderer.depth_view, &color_view,
);
// Recreate the TM bind group to read from the DoF output texture.
let (bg, _buf) = create_hdr_bind_group(
&renderer.device, &hdr.layout, &hdr.sampler, dof_pipe.output_texture(), width, height,
);
hdr.bind_group = bg;
renderer.dof_pipeline = Some(dof_pipe);
} else {
eprintln!("[WSG] DoF requires HDR: call with_hdr() before with_dof(). DoF disabled.");
renderer.dof = None;
}
}
renderer
}
@@ -686,12 +723,26 @@ impl Renderer {
self.write_default_frame_uniforms();
}
/// Sets the fog configuration at runtime (Étape 25). `None` disables fog.
/// Takes effect on the next `render_scene` call.
pub fn set_fog(&mut self, fog: Option<super::fog::FogConfig>) {
self.fog = fog;
}
/// Sets the DoF configuration at runtime (Étape 26). `None` disables DoF.
/// Only effective when DoF was enabled at construction (pipeline already allocated).
pub fn set_dof(&mut self, config: Option<super::dof::DoFConfig>) {
if self.dof_pipeline.is_some() {
self.dof = config;
}
}
/// 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` (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);
let (depth_texture, depth_view) = create_depth_texture(&self.device, width, height, self.dof_pipeline.is_some());
self._depth_texture = depth_texture;
self.depth_view = depth_view;
// Step 19 (D9): refresh the viewport height — the unit of the LOD projected-size test.
@@ -744,6 +795,23 @@ impl Renderer {
self.msaa_depth_texture = Some(msaa_depth_tex);
self.msaa_depth_view = Some(msaa_depth_view);
}
// Étape 26: resize DoF textures + re-point TM bind group at the DoF output.
if self.dof_pipeline.is_some() {
if let Some(hdr) = &mut self.hdr {
let color_tex: &wgpu::Texture = if let Some(bloom) = &self.bloom {
bloom.composite_texture()
} else {
&hdr.texture
};
let color_view = color_tex.create_view(&Default::default());
let dof_pipe = self.dof_pipeline.as_mut().unwrap();
dof_pipe.resize(&self.device, width, height, &self.depth_view, &color_view);
let (bg, _buf) = create_hdr_bind_group(
&self.device, &hdr.layout, &hdr.sampler, dof_pipe.output_texture(), width, height,
);
hdr.bind_group = bg;
}
}
}
/// Updates the stored surface texture format after a surface reconfigure (ROADMAP Phase 4.4).
@@ -801,6 +869,9 @@ impl Renderer {
light_view_proj,
shadow_params,
options: [if self.unlit { 1 } else { 0 }, shadow_on, 0, 0],
// Étape 25: fog params (disabled by default → fog_a.x = 0).
fog_a: self.fog.as_ref().map(|f| f.pack(true).0).unwrap_or(glam::Vec4::ZERO),
fog_b: self.fog.as_ref().map(|f| f.pack(true).1).unwrap_or(glam::Vec4::ZERO),
};
self.queue
.write_buffer(&self.frame_buffer, 0, bytemuck::bytes_of(&frame));
@@ -1168,9 +1239,61 @@ impl Renderer {
bloom.record_passes(&mut encoder, &self.queue, &self.bloom_config);
}
// 8d. Étape 26: DoF passes (CoC → Blur).
// Only runs when DoF + HDR are active and DoF config is set.
// The DoF output texture becomes the input to the TM pass.
if let Some(dof_pipe) = &self.dof_pipeline {
if let Some(dof_cfg) = &self.dof {
// Update the shared uniform buffer.
dof_pipe.update_uniform(&self.queue, dof_cfg, 0.1, 100.0);
// Pass 1: CoC (depth → R16Float radius texture).
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("dof coc pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: dof_pipe.coc_view(),
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
..Default::default()
});
pass.set_pipeline(dof_pipe.coc_pipeline());
pass.set_bind_group(0, dof_pipe.coc_bind_group(), &[]);
pass.draw(0..3, 0..1);
}
// Pass 2: Blur (color + CoC → blurred Rgba16Float output).
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("dof blur pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: dof_pipe.output_view(),
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
..Default::default()
});
pass.set_pipeline(dof_pipe.blur_pipeline());
pass.set_bind_group(0, dof_pipe.blur_bind_group(), &[]);
pass.draw(0..3, 0..1);
}
}
}
// 9. Étape 20: tone mapping pass — renders a fullscreen triangle that reads the HDR
// texture (or the bloom composite when bloom is active), applies exposure + tone
// mapping curve, and writes to the surface.
// texture (or the bloom composite when bloom is active, or DoF output when DoF is active),
// applies exposure + tone mapping curve, and writes to the surface.
if let Some(hdr) = &self.hdr {
let mut tm_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("tone mapping pass"),
@@ -1577,7 +1700,12 @@ fn create_depth_texture(
device: &wgpu::Device,
width: u32,
height: u32,
texturable: bool,
) -> (wgpu::Texture, wgpu::TextureView) {
let mut usage = wgpu::TextureUsages::RENDER_ATTACHMENT;
if texturable {
usage |= wgpu::TextureUsages::TEXTURE_BINDING;
}
let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("depth texture"),
size: wgpu::Extent3d {
@@ -1589,7 +1717,7 @@ fn create_depth_texture(
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
usage,
view_formats: &[],
});
let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());