This commit is contained in:
Jérôme Bousquié
2026-09-25 11:20:20 +02:00
parent 35aeb769a8
commit 9614156848
15 changed files with 822 additions and 333 deletions
+122 -8
View File
@@ -40,6 +40,7 @@ use crate::resources::{
use crate::scene::Scene;
use crate::core::bloom::{BloomConfig, BloomPipeline};
use crate::core::hdr::ToneMapper;
use crate::core::msaa::MsaaConfig;
use crate::utils::conf::{
GPU_DRIVEN_SHADER, GPU_WORKGROUP_SIZE, LOD_THRESHOLDS, MAX_ENTITIES, MAX_LOD_LEVELS, TONEMAP_SHADER,
};
@@ -159,6 +160,16 @@ pub struct Renderer {
bloom: Option<BloomPipeline>,
/// Bloom configuration (used per-frame for uniform writes). Only meaningful when bloom is active.
bloom_config: BloomConfig,
/// MSAA configuration (Étape 24). `sample_count = 1` means MSAA is disabled (zero overhead).
msaa_config: MsaaConfig,
/// MSAA color texture (N samples). `None` when MSAA is disabled.
msaa_color_texture: Option<wgpu::Texture>,
/// MSAA color view used as the main pass color attachment when MSAA is active.
msaa_color_view: Option<wgpu::TextureView>,
/// MSAA depth texture (N samples). `None` when MSAA is disabled.
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>,
}
/// Internal HDR pipeline state: offscreen `Rgba16Float` texture + tone mapping render pipeline.
@@ -201,6 +212,7 @@ impl Renderer {
shadow_config: &super::shadow::ShadowConfig,
hdr: Option<ToneMapper>,
bloom_config: Option<BloomConfig>,
msaa_config: Option<MsaaConfig>,
) -> Self {
let queue: wgpu::Queue = context.queue.clone();
let device: wgpu::Device = context.device.clone();
@@ -594,6 +606,11 @@ impl Renderer {
hdr: None,
bloom: None,
bloom_config: bloom_config.clone().unwrap_or_default(),
msaa_config: msaa_config.clone().unwrap_or_default(),
msaa_color_texture: None,
msaa_color_view: None,
msaa_depth_texture: None,
msaa_depth_view: 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.
@@ -613,6 +630,33 @@ impl Renderer {
renderer.bloom_config = bloom_config.clone().unwrap();
}
}
// Étape 24: allocate MSAA textures when sample_count > 1.
// The MSAA color texture uses the same format as the main target (HDR or surface).
if renderer.msaa_config.sample_count > 1 {
let sc = renderer.msaa_config.sample_count;
let color_format = if renderer.hdr.is_some() {
wgpu::TextureFormat::Rgba16Float
} else {
format
};
let msaa_tex = renderer.device.create_texture(&wgpu::TextureDescriptor {
label: Some("MSAA color texture"),
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: sc,
dimension: wgpu::TextureDimension::D2,
format: color_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let msaa_view = msaa_tex.create_view(&wgpu::TextureViewDescriptor::default());
let (msaa_depth_tex, msaa_depth_view) =
create_msaa_depth_texture(&renderer.device, width, height, sc);
renderer.msaa_color_texture = Some(msaa_tex);
renderer.msaa_color_view = Some(msaa_view);
renderer.msaa_depth_texture = Some(msaa_depth_tex);
renderer.msaa_depth_view = Some(msaa_depth_view);
}
renderer
}
@@ -674,6 +718,32 @@ impl Renderer {
hdr.bind_group = bg;
}
}
// Étape 24: recreate MSAA textures at the new size.
if self.msaa_config.sample_count > 1 {
let sc = self.msaa_config.sample_count;
let color_format = if self.hdr.is_some() {
wgpu::TextureFormat::Rgba16Float
} else {
self.format
};
let msaa_tex = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("MSAA color texture"),
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: sc,
dimension: wgpu::TextureDimension::D2,
format: color_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let msaa_view = msaa_tex.create_view(&wgpu::TextureViewDescriptor::default());
let (msaa_depth_tex, msaa_depth_view) =
create_msaa_depth_texture(&self.device, width, height, sc);
self.msaa_color_texture = Some(msaa_tex);
self.msaa_color_view = Some(msaa_view);
self.msaa_depth_texture = Some(msaa_depth_tex);
self.msaa_depth_view = Some(msaa_depth_view);
}
}
/// Updates the stored surface texture format after a surface reconfigure (ROADMAP Phase 4.4).
@@ -963,26 +1033,38 @@ impl Renderer {
// are hoisted out of the slot loop: one per DISTINCT material, not one per entity.
// Étape 20: when HDR is active, the color attachment targets the offscreen HDR texture
// instead of the surface; the TM pass (step 8) then copies it to the surface.
let main_target = match &self.hdr {
Some(h) => &h.view,
None => view,
// Étape 24: when MSAA is active, the color attachment targets the MSAA texture and
// resolves into the single-sample target (HDR or swapchain). The depth is also MSAA.
let (color_view, resolve_target, depth_attach) = if let Some(msaa_view) = &self.msaa_color_view {
// MSAA active: render into MSAA, resolve to single-sample target.
let resolve = match &self.hdr {
Some(h) => Some(h.view.clone()),
None => Some(view.clone()),
};
let depth = self.msaa_depth_view.as_ref().unwrap();
(msaa_view.clone(), resolve, depth)
} else {
// No MSAA: current behavior.
let color = match &self.hdr {
Some(h) => &h.view,
None => view,
};
(color.clone(), None, &self.depth_view)
};
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("scene render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: main_target,
resolve_target: None,
view: &color_view,
resolve_target: resolve_target.as_ref(),
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
// 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,
view: depth_attach,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
@@ -1406,6 +1488,16 @@ impl Renderer {
self.bloom_config = config.clone();
}
/// Returns `true` if MSAA is active (sample_count > 1). (Étape 24)
pub fn msaa_enabled(&self) -> bool {
self.msaa_config.sample_count > 1
}
/// Returns the current MSAA sample count (1 = disabled). (Étape 24)
pub fn msaa_sample_count(&self) -> u32 {
self.msaa_config.sample_count
}
/// Computes the per-slot LOD levels for this frame (Step 19, D8): for each ACTIVE slot, the
/// entity's bounding sphere — the **same sphere** the GPU frustum culling uses (D8: bbox
/// center + max half-extent × max scale component, rotated by the entity's quaternion) — is
@@ -1504,6 +1596,28 @@ fn create_depth_texture(
(depth_texture, depth_view)
}
/// Creates an MSAA depth texture (N samples) with a view (Étape 24). Used when MSAA is active:
/// the main pass needs a multi-sampled depth buffer matching the MSAA color attachment.
fn create_msaa_depth_texture(
device: &wgpu::Device,
width: u32,
height: u32,
sample_count: u32,
) -> (wgpu::Texture, wgpu::TextureView) {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("MSAA depth texture"),
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count,
dimension: wgpu::TextureDimension::D2,
format: DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
}
/// Allocates the shadow-map texture + view backing the depth-only shadow pass's
/// `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`