@bornengine/engine 0.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +24 -0
- package/native/android/src/lib.rs +702 -0
- package/native/ios/Cargo.lock +1690 -0
- package/native/ios/Cargo.toml +32 -0
- package/native/ios/src/lib.rs +1267 -0
- package/native/linux/Cargo.lock +3279 -0
- package/native/linux/Cargo.toml +29 -0
- package/native/linux/src/lib.rs +1331 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +46 -0
- package/native/macos/src/lib.rs +1302 -0
- package/native/shared/Cargo.lock +1899 -0
- package/native/shared/Cargo.toml +62 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +270 -0
- package/native/shared/shaders/common/clouds.wgsl +122 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +186 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +57 -0
- package/native/shared/shaders/material_abi.wgsl +383 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/anim_mixer.rs +61 -0
- package/native/shared/src/attach.rs +263 -0
- package/native/shared/src/audio/decode.rs +123 -0
- package/native/shared/src/audio/mod.rs +863 -0
- package/native/shared/src/audio/render.rs +892 -0
- package/native/shared/src/audio/spsc.rs +156 -0
- package/native/shared/src/audio/stream.rs +226 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/decals.rs +245 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +261 -0
- package/native/shared/src/ffi.rs +116 -0
- package/native/shared/src/ffi_core/assets.rs +388 -0
- package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
- package/native/shared/src/ffi_core/draw.rs +334 -0
- package/native/shared/src/ffi_core/game_loop.rs +577 -0
- package/native/shared/src/ffi_core/input.rs +234 -0
- package/native/shared/src/ffi_core/mod.rs +127 -0
- package/native/shared/src/ffi_core/models.rs +1154 -0
- package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
- package/native/shared/src/ffi_core/scene.rs +626 -0
- package/native/shared/src/ffi_core/vfx.rs +212 -0
- package/native/shared/src/ffi_core/visual.rs +691 -0
- package/native/shared/src/frame_callbacks.rs +122 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +182 -0
- package/native/shared/src/input.rs +448 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +55 -0
- package/native/shared/src/models.rs +1093 -0
- package/native/shared/src/models_gltf.rs +1280 -0
- package/native/shared/src/particles.rs +391 -0
- package/native/shared/src/physics_jolt.rs +1908 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +345 -0
- package/native/shared/src/profiler.rs +492 -0
- package/native/shared/src/ragdoll.rs +474 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/draw2d.rs +143 -0
- package/native/shared/src/renderer/formats.rs +822 -0
- package/native/shared/src/renderer/froxel.rs +421 -0
- package/native/shared/src/renderer/gi_bake.rs +653 -0
- package/native/shared/src/renderer/graph.rs +462 -0
- package/native/shared/src/renderer/hiz.rs +269 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +456 -0
- package/native/shared/src/renderer/lighting.rs +154 -0
- package/native/shared/src/renderer/material_instancing.rs +171 -0
- package/native/shared/src/renderer/material_pipeline.rs +700 -0
- package/native/shared/src/renderer/material_system.rs +1996 -0
- package/native/shared/src/renderer/material_system_tests.rs +601 -0
- package/native/shared/src/renderer/material_system_wasm.rs +41 -0
- package/native/shared/src/renderer/mod.rs +12556 -0
- package/native/shared/src/renderer/model_draw.rs +641 -0
- package/native/shared/src/renderer/occlusion.rs +429 -0
- package/native/shared/src/renderer/planar_pass.rs +593 -0
- package/native/shared/src/renderer/planar_reflection.rs +499 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/postfx_chain.rs +728 -0
- package/native/shared/src/renderer/pt_pass.rs +577 -0
- package/native/shared/src/renderer/scene_pass.rs +607 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +135 -0
- package/native/shared/src/renderer/shaders/ao.rs +570 -0
- package/native/shared/src/renderer/shaders/core.rs +1243 -0
- package/native/shared/src/renderer/shaders/env.rs +907 -0
- package/native/shared/src/renderer/shaders/gi.rs +810 -0
- package/native/shared/src/renderer/shaders/mod.rs +19 -0
- package/native/shared/src/renderer/shaders/post.rs +1558 -0
- package/native/shared/src/renderer/shaders/pt.rs +1859 -0
- package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
- package/native/shared/src/renderer/shadow_pass.rs +731 -0
- package/native/shared/src/renderer/ssgi_pass.rs +392 -0
- package/native/shared/src/renderer/ssr_pass.rs +188 -0
- package/native/shared/src/renderer/texture_store.rs +473 -0
- package/native/shared/src/renderer/transient.rs +591 -0
- package/native/shared/src/renderer/types.rs +941 -0
- package/native/shared/src/renderer/util.rs +152 -0
- package/native/shared/src/scene.rs +1362 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +1036 -0
- package/native/shared/src/staging.rs +102 -0
- package/native/shared/src/string_header.rs +266 -0
- package/native/shared/src/text_renderer.rs +502 -0
- package/native/shared/src/textures.rs +197 -0
- package/native/tvos/Cargo.lock +1693 -0
- package/native/tvos/Cargo.toml +36 -0
- package/native/tvos/metal-patched/Cargo.toml +178 -0
- package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
- package/native/tvos/metal-patched/LICENSE-MIT +25 -0
- package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
- package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
- package/native/tvos/metal-patched/src/argument.rs +366 -0
- package/native/tvos/metal-patched/src/blitpass.rs +102 -0
- package/native/tvos/metal-patched/src/buffer.rs +71 -0
- package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
- package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
- package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
- package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
- package/native/tvos/metal-patched/src/computepass.rs +107 -0
- package/native/tvos/metal-patched/src/constants.rs +152 -0
- package/native/tvos/metal-patched/src/counters.rs +119 -0
- package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
- package/native/tvos/metal-patched/src/device.rs +2134 -0
- package/native/tvos/metal-patched/src/drawable.rs +39 -0
- package/native/tvos/metal-patched/src/encoder.rs +2041 -0
- package/native/tvos/metal-patched/src/heap.rs +281 -0
- package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
- package/native/tvos/metal-patched/src/lib.rs +657 -0
- package/native/tvos/metal-patched/src/library.rs +902 -0
- package/native/tvos/metal-patched/src/mps.rs +575 -0
- package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
- package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
- package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
- package/native/tvos/metal-patched/src/renderpass.rs +443 -0
- package/native/tvos/metal-patched/src/resource.rs +182 -0
- package/native/tvos/metal-patched/src/sampler.rs +165 -0
- package/native/tvos/metal-patched/src/sync.rs +178 -0
- package/native/tvos/metal-patched/src/texture.rs +352 -0
- package/native/tvos/metal-patched/src/types.rs +90 -0
- package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
- package/native/tvos/src/audio_backend.rs +197 -0
- package/native/tvos/src/lib.rs +1891 -0
- package/native/visionos/Cargo.lock +1693 -0
- package/native/visionos/Cargo.toml +40 -0
- package/native/visionos/src/audio_backend.rs +197 -0
- package/native/visionos/src/lib.rs +1887 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1267 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +229 -0
- package/native/watchos/src/ffi_stubs.rs +915 -0
- package/native/watchos/src/ffi_stubs_manual.rs +35 -0
- package/native/watchos/src/lib.rs +1124 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +95 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1657 -0
- package/native/web/Cargo.toml +43 -0
- package/native/web/bloom_glue.js +695 -0
- package/native/web/build.sh +131 -0
- package/native/web/index.html +35 -0
- package/native/web/jolt_bridge.js +1519 -0
- package/native/web/src/input_ffi.rs +286 -0
- package/native/web/src/lib.rs +1796 -0
- package/native/web/src/material_ffi.rs +710 -0
- package/native/web/src/parity_ffi.rs +343 -0
- package/native/web/src/physics_ffi.rs +643 -0
- package/native/web/src/ragdoll_ffi.rs +250 -0
- package/native/web/src/render_settings.rs +98 -0
- package/native/windows/Cargo.lock +1815 -0
- package/native/windows/Cargo.toml +68 -0
- package/native/windows/src/lib.rs +1486 -0
- package/package.json +4279 -0
- package/src/audio/index.ts +315 -0
- package/src/core/colors.ts +63 -0
- package/src/core/index.ts +1206 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +104 -0
- package/src/index.ts +171 -0
- package/src/math/index.ts +516 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +1258 -0
- package/src/physics/index.ts +1134 -0
- package/src/scene/index.ts +698 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +187 -0
- package/src/vfx/index.ts +191 -0
- package/src/world/index.ts +24 -0
- package/src/world/loader.ts +423 -0
- package/src/world/prefab.ts +217 -0
- package/src/world/render.ts +172 -0
- package/src/world/saver.ts +108 -0
- package/src/world/serialize.ts +301 -0
- package/src/world/terrain.ts +355 -0
- package/src/world/types.ts +160 -0
- package/src/world/validate.ts +319 -0
- package/src/world/version.ts +114 -0
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
//! Post-FX chain passes split from end_frame_with_scene (2000-line file
|
|
2
|
+
//! policy + render-graph migration prep). Starts with the bloom chain;
|
|
3
|
+
//! the rest of the tail (compose/upscale/TAA/DoF/blur/SSS) migrates here
|
|
4
|
+
//! cluster by cluster.
|
|
5
|
+
|
|
6
|
+
use super::*;
|
|
7
|
+
use wgpu::util::DeviceExt;
|
|
8
|
+
|
|
9
|
+
impl Renderer {
|
|
10
|
+
/// Bloom: progressive downsample (Karis-thresholded first tap)
|
|
11
|
+
/// followed by additive upsample back up the chain. No-op (clears
|
|
12
|
+
/// nothing) when disabled — compose skips the bloom sample.
|
|
13
|
+
pub(super) fn record_bloom_chain(
|
|
14
|
+
&mut self,
|
|
15
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
16
|
+
profiler: &mut crate::profiler::Profiler,
|
|
17
|
+
surf_w: u32,
|
|
18
|
+
surf_h: u32,
|
|
19
|
+
) {
|
|
20
|
+
// ============================================================
|
|
21
|
+
// Bloom: progressive downsample (Karis-thresholded first tap)
|
|
22
|
+
// followed by additive upsample back up the chain.
|
|
23
|
+
// ============================================================
|
|
24
|
+
if self.bloom_enabled {
|
|
25
|
+
let mip_dims: Vec<(u32, u32)> = (0..BLOOM_MIP_COUNT)
|
|
26
|
+
.map(|i| (
|
|
27
|
+
((surf_w / 2) >> i).max(1),
|
|
28
|
+
((surf_h / 2) >> i).max(1),
|
|
29
|
+
))
|
|
30
|
+
.collect();
|
|
31
|
+
|
|
32
|
+
// Build per-pass bind groups + uniform writes. Each downsample
|
|
33
|
+
// reads the previous mip (or hdr_rt for the first) and writes
|
|
34
|
+
// to the current mip. Each upsample reads mip i+1 and blends
|
|
35
|
+
// additively into mip i.
|
|
36
|
+
let bloom_filter_radius = 1.0_f32; // upsample tent radius
|
|
37
|
+
|
|
38
|
+
// Downsample chain: mip 0 reads HDR, mips 1..N read previous mip.
|
|
39
|
+
for i in 0..BLOOM_MIP_COUNT as usize {
|
|
40
|
+
let (src_view, src_w, src_h, threshold_pass) = if i == 0 {
|
|
41
|
+
(&self.hdr_rt_view, surf_w as f32, surf_h as f32, true)
|
|
42
|
+
} else {
|
|
43
|
+
let prev = &self.bloom_mip_views[i - 1];
|
|
44
|
+
let (pw, ph) = mip_dims[i - 1];
|
|
45
|
+
(prev, pw as f32, ph as f32, false)
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Threshold in pre-exposure HDR units. The 2.5 reference was tuned
|
|
49
|
+
// at exposure 1.0 (Sponza), so scale it inversely with the game's
|
|
50
|
+
// manual exposure to keep bloom triggering at the same DISPLAY
|
|
51
|
+
// brightness regardless of exposure. Auto-exposure keeps the legacy
|
|
52
|
+
// constant — 2.5 was tuned against auto-normalised HDR originally.
|
|
53
|
+
let thr = if self.auto_exposure {
|
|
54
|
+
2.5
|
|
55
|
+
} else {
|
|
56
|
+
2.5 / self.manual_exposure.max(0.05)
|
|
57
|
+
};
|
|
58
|
+
let bp = BloomParams {
|
|
59
|
+
params: [1.0 / src_w, 1.0 / src_h, bloom_filter_radius, thr],
|
|
60
|
+
};
|
|
61
|
+
// Per-pass uniform buffer. A single shared buffer written with
|
|
62
|
+
// queue.write_buffer once per pass does NOT work here: all writes
|
|
63
|
+
// land before the encoder executes at submit, so every pass in the
|
|
64
|
+
// chain read the LAST write (wrong texel sizes for every mip, and
|
|
65
|
+
// it would clobber the threshold above).
|
|
66
|
+
let ub = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
67
|
+
label: Some("bloom_downsample_params"),
|
|
68
|
+
contents: bytemuck::bytes_of(&bp),
|
|
69
|
+
usage: wgpu::BufferUsages::UNIFORM,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
73
|
+
label: Some("bloom_downsample_bg"),
|
|
74
|
+
layout: &self.bloom_layout,
|
|
75
|
+
entries: &[
|
|
76
|
+
wgpu::BindGroupEntry { binding: 0, resource: ub.as_entire_binding() },
|
|
77
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(src_view) },
|
|
78
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let bloom_ts = profiler.pass_timestamp_writes("bloom_pass");
|
|
83
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
84
|
+
label: Some("bloom_downsample_pass"),
|
|
85
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
86
|
+
view: &self.bloom_mip_views[i],
|
|
87
|
+
resolve_target: None,
|
|
88
|
+
depth_slice: None,
|
|
89
|
+
ops: wgpu::Operations {
|
|
90
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
91
|
+
store: wgpu::StoreOp::Store,
|
|
92
|
+
},
|
|
93
|
+
})],
|
|
94
|
+
depth_stencil_attachment: None,
|
|
95
|
+
timestamp_writes: bloom_ts,
|
|
96
|
+
occlusion_query_set: None,
|
|
97
|
+
multiview_mask: None,
|
|
98
|
+
});
|
|
99
|
+
let pl = if threshold_pass {
|
|
100
|
+
&self.bloom_pipeline_threshold_downsample
|
|
101
|
+
} else {
|
|
102
|
+
&self.bloom_pipeline_downsample
|
|
103
|
+
};
|
|
104
|
+
pass.set_pipeline(pl);
|
|
105
|
+
// Force the viewport to this mip's actual size — wgpu's
|
|
106
|
+
// auto-viewport derives from the surface config, not the
|
|
107
|
+
// mip-view attachment, so without this the bloom pass
|
|
108
|
+
// writes into a fraction of the mip and leaves the rest
|
|
109
|
+
// uninitialized.
|
|
110
|
+
let (mw, mh) = mip_dims[i];
|
|
111
|
+
pass.set_viewport(0.0, 0.0, mw as f32, mh as f32, 0.0, 1.0);
|
|
112
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
113
|
+
pass.draw(0..3, 0..1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Upsample chain: blend mip i+1 additively into mip i for
|
|
117
|
+
// i = N-2..0. Final mip 0 ends up with the full bloom result.
|
|
118
|
+
for i in (0..(BLOOM_MIP_COUNT as usize - 1)).rev() {
|
|
119
|
+
let src_view = &self.bloom_mip_views[i + 1];
|
|
120
|
+
let (sw, sh) = mip_dims[i + 1];
|
|
121
|
+
|
|
122
|
+
let bp = BloomParams {
|
|
123
|
+
params: [1.0 / sw as f32, 1.0 / sh as f32, bloom_filter_radius, 0.0],
|
|
124
|
+
};
|
|
125
|
+
// Per-pass uniform buffer — see the downsample loop note: a shared
|
|
126
|
+
// buffer + write_buffer per pass aliases to the LAST write at submit.
|
|
127
|
+
let ub = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
128
|
+
label: Some("bloom_upsample_params"),
|
|
129
|
+
contents: bytemuck::bytes_of(&bp),
|
|
130
|
+
usage: wgpu::BufferUsages::UNIFORM,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
134
|
+
label: Some("bloom_upsample_bg"),
|
|
135
|
+
layout: &self.bloom_layout,
|
|
136
|
+
entries: &[
|
|
137
|
+
wgpu::BindGroupEntry { binding: 0, resource: ub.as_entire_binding() },
|
|
138
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(src_view) },
|
|
139
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
let bloom_up_ts = profiler.pass_timestamp_writes("bloom_pass");
|
|
144
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
145
|
+
label: Some("bloom_upsample_pass"),
|
|
146
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
147
|
+
view: &self.bloom_mip_views[i],
|
|
148
|
+
resolve_target: None,
|
|
149
|
+
depth_slice: None,
|
|
150
|
+
ops: wgpu::Operations {
|
|
151
|
+
// Load — additive blend on top of what
|
|
152
|
+
// downsample wrote.
|
|
153
|
+
load: wgpu::LoadOp::Load,
|
|
154
|
+
store: wgpu::StoreOp::Store,
|
|
155
|
+
},
|
|
156
|
+
})],
|
|
157
|
+
depth_stencil_attachment: None,
|
|
158
|
+
timestamp_writes: bloom_up_ts,
|
|
159
|
+
occlusion_query_set: None,
|
|
160
|
+
multiview_mask: None,
|
|
161
|
+
});
|
|
162
|
+
pass.set_pipeline(&self.bloom_pipeline_upsample);
|
|
163
|
+
// Same viewport fix as the downsample loop above — without
|
|
164
|
+
// this the upsample tents only cover a sub-region of the
|
|
165
|
+
// destination mip.
|
|
166
|
+
let (mw, mh) = mip_dims[i];
|
|
167
|
+
pass.set_viewport(0.0, 0.0, mw as f32, mh as f32, 0.0, 1.0);
|
|
168
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
169
|
+
pass.draw(0..3, 0..1);
|
|
170
|
+
}
|
|
171
|
+
} // end if self.bloom_enabled
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
impl Renderer {
|
|
176
|
+
/// Scene compose: merge HDR + SSR + SSGI*albedo + bloom + fog + sun
|
|
177
|
+
/// shafts into composed_rt. Runs unconditionally so the TAA-on path
|
|
178
|
+
/// (TAA consumes this) and the TAA-off path (composite consumes it)
|
|
179
|
+
/// see the same atmospherics.
|
|
180
|
+
pub(super) fn record_scene_compose(
|
|
181
|
+
&mut self,
|
|
182
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
183
|
+
) {
|
|
184
|
+
// Composite input views (were locals in end_frame_with_scene).
|
|
185
|
+
// PT-1: while path tracing, the SSR passes are skipped entirely,
|
|
186
|
+
// so history is stale — route compose to ssr_rt, which the march
|
|
187
|
+
// else-branch keeps cleared to transparent black.
|
|
188
|
+
let ssr_composite_view = if self.ssr_enabled && !self.pt_owns_frame() {
|
|
189
|
+
&self.ssr_history_views[self.ssr_history_idx]
|
|
190
|
+
} else {
|
|
191
|
+
&self.ssr_rt_view
|
|
192
|
+
};
|
|
193
|
+
let ssgi_composite_view = &self.ssgi_rt_view;
|
|
194
|
+
// ============================================================
|
|
195
|
+
// Scene-compose pass: merge HDR + SSR + SSGI*albedo + bloom
|
|
196
|
+
// + fog + sun shafts into composed_rt. Runs unconditionally
|
|
197
|
+
// so both the TAA-on path (TAA consumes this) and the
|
|
198
|
+
// TAA-off path (composite consumes this) get the same
|
|
199
|
+
// atmospherics + post-effects.
|
|
200
|
+
// ============================================================
|
|
201
|
+
let inv_vp_current = self.current_inv_vp_matrix;
|
|
202
|
+
// Sun shaft screen-space position. Project a point far along
|
|
203
|
+
// the sun direction through the current VP. If behind the
|
|
204
|
+
// camera (clip.w ≤ 0), the sun is off-screen → disable.
|
|
205
|
+
let sun_dir = self.lighting_uniforms.light_dir;
|
|
206
|
+
let sun_world = [sun_dir[0] * 1000.0, sun_dir[1] * 1000.0, sun_dir[2] * 1000.0, 1.0];
|
|
207
|
+
let clip = mat4_mul_vec4(&self.current_vp_matrix, &sun_world);
|
|
208
|
+
let (sun_uv, shaft_strength_eff) = if clip[3] > 0.0 {
|
|
209
|
+
let ndc_x = clip[0] / clip[3];
|
|
210
|
+
let ndc_y = clip[1] / clip[3];
|
|
211
|
+
let u = ndc_x * 0.5 + 0.5;
|
|
212
|
+
let v = 1.0 - (ndc_y * 0.5 + 0.5);
|
|
213
|
+
// Allow off-screen suns to still cast shafts that streak
|
|
214
|
+
// in from the edge — clamp to a small margin beyond ±[0,1]
|
|
215
|
+
// rather than disabling outright.
|
|
216
|
+
let off = u < -1.0 || u > 2.0 || v < -1.0 || v > 2.0;
|
|
217
|
+
if off { ([0.0, 0.0], 0.0) } else { ([u, v], self.sun_shaft_strength) }
|
|
218
|
+
} else {
|
|
219
|
+
([0.0, 0.0], 0.0)
|
|
220
|
+
};
|
|
221
|
+
// When bloom_enabled is false we skip the downsample/upsample
|
|
222
|
+
// chain entirely; forcing the composite's bloom multiplier to
|
|
223
|
+
// 0 here means stale bloom_mip_views[0] contents contribute
|
|
224
|
+
// nothing visually.
|
|
225
|
+
let effective_bloom_intensity = if self.bloom_enabled { self.bloom_intensity } else { 0.0 };
|
|
226
|
+
let cp = SceneComposeParams {
|
|
227
|
+
// misc.y = procedural-sky aerial-perspective on/off flag.
|
|
228
|
+
// The scene_compose shader reads this to decide between
|
|
229
|
+
// the legacy 16-step fog march and the V2 3D LUT sample.
|
|
230
|
+
misc: [
|
|
231
|
+
effective_bloom_intensity,
|
|
232
|
+
if self.procedural_sky_enabled { 1.0 } else { 0.0 },
|
|
233
|
+
AERIAL_MAX_DIST_KM,
|
|
234
|
+
0.0,
|
|
235
|
+
],
|
|
236
|
+
inv_vp: inv_vp_current,
|
|
237
|
+
fog_color_density: [
|
|
238
|
+
self.fog_color[0], self.fog_color[1], self.fog_color[2], self.fog_density,
|
|
239
|
+
],
|
|
240
|
+
fog_params: [self.fog_height_ref, self.fog_height_falloff, 0.0, 0.0],
|
|
241
|
+
sun_shaft_uv_strength: [
|
|
242
|
+
sun_uv[0], sun_uv[1], shaft_strength_eff, self.sun_shaft_decay,
|
|
243
|
+
],
|
|
244
|
+
sun_shaft_color: [
|
|
245
|
+
self.sun_shaft_color[0], self.sun_shaft_color[1], self.sun_shaft_color[2], 0.0,
|
|
246
|
+
],
|
|
247
|
+
};
|
|
248
|
+
self.queue.write_buffer(&self.scene_compose_uniform_buffer, 0, bytemuck::bytes_of(&cp));
|
|
249
|
+
{
|
|
250
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
251
|
+
label: Some("scene_compose_bg"),
|
|
252
|
+
layout: &self.scene_compose_layout,
|
|
253
|
+
entries: &[
|
|
254
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.scene_compose_uniform_buffer.as_entire_binding() },
|
|
255
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.hdr_rt_view) },
|
|
256
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
257
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(ssr_composite_view) },
|
|
258
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
259
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(ssgi_composite_view) },
|
|
260
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
261
|
+
wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::TextureView(&self.bloom_mip_views[0]) },
|
|
262
|
+
wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
263
|
+
wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::TextureView(&self.albedo_rt_view) },
|
|
264
|
+
wgpu::BindGroupEntry { binding: 10, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
265
|
+
wgpu::BindGroupEntry { binding: 11, resource: wgpu::BindingResource::TextureView(&self.depth_view) },
|
|
266
|
+
wgpu::BindGroupEntry { binding: 12, resource: wgpu::BindingResource::Sampler(&self.ssao_depth_sampler) },
|
|
267
|
+
// EN-005 V2 — always bound; shader gates use on `misc.y`.
|
|
268
|
+
wgpu::BindGroupEntry { binding: 13, resource: wgpu::BindingResource::TextureView(&self.aerial_perspective_view) },
|
|
269
|
+
wgpu::BindGroupEntry { binding: 14, resource: wgpu::BindingResource::Sampler(&self.aerial_perspective_sampler) },
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
// NOTE: GPU timestamp deliberately not requested on this pass.
|
|
273
|
+
// Empirically (sponza, Metal) the reported delta was ~249 ms
|
|
274
|
+
// for what should be a sub-millisecond fullscreen pass. Likely
|
|
275
|
+
// the end-of-pass write is synchronized to a later barrier
|
|
276
|
+
// and includes idle time. CPU-side timing via the enclosing
|
|
277
|
+
// `post_fx` phase captures the cost adequately.
|
|
278
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
279
|
+
label: Some("scene_compose_pass"),
|
|
280
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
281
|
+
view: &self.composed_rt_view,
|
|
282
|
+
resolve_target: None,
|
|
283
|
+
depth_slice: None,
|
|
284
|
+
ops: wgpu::Operations {
|
|
285
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
286
|
+
store: wgpu::StoreOp::Store,
|
|
287
|
+
},
|
|
288
|
+
})],
|
|
289
|
+
depth_stencil_attachment: None,
|
|
290
|
+
timestamp_writes: None,
|
|
291
|
+
occlusion_query_set: None,
|
|
292
|
+
multiview_mask: None,
|
|
293
|
+
});
|
|
294
|
+
pass.set_pipeline(&self.scene_compose_pipeline);
|
|
295
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
296
|
+
pass.draw(0..3, 0..1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
impl Renderer {
|
|
303
|
+
/// Post-FX tail: upscale (when sub-res and TAA is off), TAA, DoF,
|
|
304
|
+
/// motion blur, SSS, and CAS — each stage reads the output of the
|
|
305
|
+
/// last enabled stage before it. The internal `pre_*_view`
|
|
306
|
+
/// selections encode that chain; `composite_source_view` re-derives
|
|
307
|
+
/// the final link for the composite pass.
|
|
308
|
+
pub(super) fn record_postfx_tail(
|
|
309
|
+
&mut self,
|
|
310
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
311
|
+
profiler: &mut crate::profiler::Profiler,
|
|
312
|
+
) {
|
|
313
|
+
// ============================================================
|
|
314
|
+
// Upscale pass: render-res composed_rt → full-surface upscale_rt.
|
|
315
|
+
// Engages only when render_scale < 1.0 AND TAA is off — when
|
|
316
|
+
// TAA runs it does its own Catmull-Rom upscale. Downstream
|
|
317
|
+
// post-FX (DoF/MB/SSS/composite) read upscale_rt instead of
|
|
318
|
+
// hdr_rt in this case so the chain operates at full surface
|
|
319
|
+
// resolution.
|
|
320
|
+
// ============================================================
|
|
321
|
+
if self.render_scale < 0.999 && !self.taa_enabled {
|
|
322
|
+
let up = UpscaleParams {
|
|
323
|
+
params: [self.upscale_mode as f32, 0.0, 0.0, 0.0],
|
|
324
|
+
};
|
|
325
|
+
self.queue.write_buffer(&self.upscale_uniform_buffer, 0, bytemuck::bytes_of(&up));
|
|
326
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
327
|
+
label: Some("upscale_bg"),
|
|
328
|
+
layout: &self.upscale_layout,
|
|
329
|
+
entries: &[
|
|
330
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.upscale_uniform_buffer.as_entire_binding() },
|
|
331
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.composed_rt_view) },
|
|
332
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
333
|
+
],
|
|
334
|
+
});
|
|
335
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
336
|
+
label: Some("upscale_pass"),
|
|
337
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
338
|
+
view: &self.upscale_rt_view,
|
|
339
|
+
resolve_target: None,
|
|
340
|
+
depth_slice: None,
|
|
341
|
+
ops: wgpu::Operations {
|
|
342
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
343
|
+
store: wgpu::StoreOp::Store,
|
|
344
|
+
},
|
|
345
|
+
})],
|
|
346
|
+
depth_stencil_attachment: None,
|
|
347
|
+
timestamp_writes: None,
|
|
348
|
+
occlusion_query_set: None,
|
|
349
|
+
multiview_mask: None,
|
|
350
|
+
});
|
|
351
|
+
pass.set_pipeline(&self.upscale_pipeline);
|
|
352
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
353
|
+
pass.draw(0..3, 0..1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ============================================================
|
|
357
|
+
// TAA pass: reprojection + neighborhood clamp on composed_rt.
|
|
358
|
+
// Skipped when TAA is off — composite reads composed_rt
|
|
359
|
+
// directly and gets the same composed / fog / shafts output.
|
|
360
|
+
// ============================================================
|
|
361
|
+
let taa_dst_idx = self.taa_current_idx;
|
|
362
|
+
let taa_src_idx = 1 - self.taa_current_idx;
|
|
363
|
+
|
|
364
|
+
if self.taa_enabled {
|
|
365
|
+
// Effective temporal window scales with per-pixel sample
|
|
366
|
+
// density (~render_scale²). At 0.5 → 0.0625 (~16-frame
|
|
367
|
+
// window, close to the prior 0.05/20-frame); at 1.0 →
|
|
368
|
+
// 0.10 (~10-frame), matching native TAA.
|
|
369
|
+
let s2 = self.render_scale * self.render_scale;
|
|
370
|
+
let steady = 0.05 + 0.05 * s2;
|
|
371
|
+
let alpha = if self.taa_frame_index < 4 { 1.0 } else { steady };
|
|
372
|
+
// yz = the current jitter as a composed-UV offset. Content shifts by
|
|
373
|
+
// -jitter_ndc through the GL-convention perspective divide (w = -z),
|
|
374
|
+
// so the rendered position of a feature is uv + (-0.5*jx, +0.5*jy)
|
|
375
|
+
// (v axis flips). Empirically arbitrated by the variance rig: the
|
|
376
|
+
// wrong sign DOUBLES the effective jitter and the numbers scream.
|
|
377
|
+
let tp = TaaParams {
|
|
378
|
+
params: [alpha,
|
|
379
|
+
-0.5 * self.current_jitter_ndc[0],
|
|
380
|
+
0.5 * self.current_jitter_ndc[1],
|
|
381
|
+
0.0],
|
|
382
|
+
inv_vp: self.current_inv_vp_matrix,
|
|
383
|
+
prev_vp: self.prev_vp_matrix,
|
|
384
|
+
};
|
|
385
|
+
self.queue.write_buffer(&self.taa_uniform_buffer, 0, bytemuck::bytes_of(&tp));
|
|
386
|
+
|
|
387
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
388
|
+
label: Some("taa_bg"),
|
|
389
|
+
layout: &self.taa_layout,
|
|
390
|
+
entries: &[
|
|
391
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.taa_uniform_buffer.as_entire_binding() },
|
|
392
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.composed_rt_view) },
|
|
393
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
394
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.taa_views[taa_src_idx]) },
|
|
395
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
396
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&self.depth_view) },
|
|
397
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::Sampler(&self.ssao_depth_sampler) },
|
|
398
|
+
wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::TextureView(&self.velocity_rt_view) },
|
|
399
|
+
wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
400
|
+
],
|
|
401
|
+
});
|
|
402
|
+
let taa_ts = profiler.pass_timestamp_writes("taa_pass");
|
|
403
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
404
|
+
label: Some("taa_pass"),
|
|
405
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
406
|
+
view: &self.taa_views[taa_dst_idx],
|
|
407
|
+
resolve_target: None,
|
|
408
|
+
depth_slice: None,
|
|
409
|
+
ops: wgpu::Operations {
|
|
410
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
411
|
+
store: wgpu::StoreOp::Store,
|
|
412
|
+
},
|
|
413
|
+
})],
|
|
414
|
+
depth_stencil_attachment: None,
|
|
415
|
+
timestamp_writes: taa_ts,
|
|
416
|
+
occlusion_query_set: None,
|
|
417
|
+
multiview_mask: None,
|
|
418
|
+
});
|
|
419
|
+
pass.set_pipeline(&self.taa_pipeline);
|
|
420
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
421
|
+
pass.draw(0..3, 0..1);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ============================================================
|
|
425
|
+
// DoF pass: variable-radius Poisson disc blur driven by CoC
|
|
426
|
+
// Reads TAA output / upscale_rt / hdr_rt + depth → dof_rt
|
|
427
|
+
// ============================================================
|
|
428
|
+
let pre_dof_view = if self.taa_enabled {
|
|
429
|
+
&self.taa_views[taa_dst_idx]
|
|
430
|
+
} else if self.render_scale < 0.999 {
|
|
431
|
+
&self.upscale_rt_view
|
|
432
|
+
} else {
|
|
433
|
+
&self.hdr_rt_view
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
if self.dof_enabled && self.dof_aperture > 0.0 {
|
|
437
|
+
let inv_proj = self.current_inv_proj_matrix;
|
|
438
|
+
let dp = DofParams {
|
|
439
|
+
params: [self.dof_focus_distance, self.dof_aperture, self.dof_max_blur, 0.0],
|
|
440
|
+
inv_proj,
|
|
441
|
+
};
|
|
442
|
+
self.queue.write_buffer(&self.dof_uniform_buffer, 0, bytemuck::bytes_of(&dp));
|
|
443
|
+
|
|
444
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
445
|
+
label: Some("dof_bg"),
|
|
446
|
+
layout: &self.dof_layout,
|
|
447
|
+
entries: &[
|
|
448
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.dof_uniform_buffer.as_entire_binding() },
|
|
449
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(pre_dof_view) },
|
|
450
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
451
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.depth_view) },
|
|
452
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&self.ssao_depth_sampler) },
|
|
453
|
+
],
|
|
454
|
+
});
|
|
455
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
456
|
+
label: Some("dof_pass"),
|
|
457
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
458
|
+
view: &self.dof_rt_view,
|
|
459
|
+
resolve_target: None,
|
|
460
|
+
depth_slice: None,
|
|
461
|
+
ops: wgpu::Operations {
|
|
462
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
463
|
+
store: wgpu::StoreOp::Store,
|
|
464
|
+
},
|
|
465
|
+
})],
|
|
466
|
+
depth_stencil_attachment: None,
|
|
467
|
+
timestamp_writes: None,
|
|
468
|
+
occlusion_query_set: None,
|
|
469
|
+
multiview_mask: None,
|
|
470
|
+
});
|
|
471
|
+
pass.set_pipeline(&self.dof_pipeline);
|
|
472
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
473
|
+
pass.draw(0..3, 0..1);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ============================================================
|
|
477
|
+
// Motion blur pass: 8-tap directional blur along velocity
|
|
478
|
+
// Reads upstream color + velocity_rt → motion_blur_rt
|
|
479
|
+
// ============================================================
|
|
480
|
+
let pre_mblur_view = if self.dof_enabled && self.dof_aperture > 0.0 {
|
|
481
|
+
&self.dof_rt_view
|
|
482
|
+
} else if self.taa_enabled {
|
|
483
|
+
&self.taa_views[taa_dst_idx]
|
|
484
|
+
} else if self.render_scale < 0.999 {
|
|
485
|
+
&self.upscale_rt_view
|
|
486
|
+
} else {
|
|
487
|
+
&self.hdr_rt_view
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
if self.motion_blur_enabled && self.motion_blur_strength > 0.0 {
|
|
491
|
+
let mbp = MotionBlurParams {
|
|
492
|
+
params: [self.motion_blur_strength, self.motion_blur_max_blur, 0.0, 0.0],
|
|
493
|
+
};
|
|
494
|
+
self.queue.write_buffer(&self.motion_blur_uniform_buffer, 0, bytemuck::bytes_of(&mbp));
|
|
495
|
+
|
|
496
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
497
|
+
label: Some("motion_blur_bg"),
|
|
498
|
+
layout: &self.motion_blur_layout,
|
|
499
|
+
entries: &[
|
|
500
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.motion_blur_uniform_buffer.as_entire_binding() },
|
|
501
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(pre_mblur_view) },
|
|
502
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
503
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.velocity_rt_view) },
|
|
504
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
505
|
+
],
|
|
506
|
+
});
|
|
507
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
508
|
+
label: Some("motion_blur_pass"),
|
|
509
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
510
|
+
view: &self.motion_blur_rt_view,
|
|
511
|
+
resolve_target: None,
|
|
512
|
+
depth_slice: None,
|
|
513
|
+
ops: wgpu::Operations {
|
|
514
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
515
|
+
store: wgpu::StoreOp::Store,
|
|
516
|
+
},
|
|
517
|
+
})],
|
|
518
|
+
depth_stencil_attachment: None,
|
|
519
|
+
timestamp_writes: None,
|
|
520
|
+
occlusion_query_set: None,
|
|
521
|
+
multiview_mask: None,
|
|
522
|
+
});
|
|
523
|
+
pass.set_pipeline(&self.motion_blur_pipeline);
|
|
524
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
525
|
+
pass.draw(0..3, 0..1);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ============================================================
|
|
529
|
+
// SSS pass: chromatic disc blur (skin / wax / leaves)
|
|
530
|
+
// Reads upstream color + depth → sss_rt.
|
|
531
|
+
// Runs after motion blur so it applies to the fully composited
|
|
532
|
+
// motion state, not to individual geometry.
|
|
533
|
+
// ============================================================
|
|
534
|
+
let pre_sss_view = if self.motion_blur_enabled && self.motion_blur_strength > 0.0 {
|
|
535
|
+
&self.motion_blur_rt_view
|
|
536
|
+
} else if self.dof_enabled && self.dof_aperture > 0.0 {
|
|
537
|
+
&self.dof_rt_view
|
|
538
|
+
} else if self.taa_enabled {
|
|
539
|
+
&self.taa_views[taa_dst_idx]
|
|
540
|
+
} else if self.render_scale < 0.999 {
|
|
541
|
+
&self.upscale_rt_view
|
|
542
|
+
} else {
|
|
543
|
+
&self.hdr_rt_view
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
if self.sss_enabled && self.sss_strength > 0.0 {
|
|
547
|
+
let sp = SssParams {
|
|
548
|
+
params: [self.sss_strength, self.sss_width, 500.0, 0.0],
|
|
549
|
+
};
|
|
550
|
+
self.queue.write_buffer(&self.sss_uniform_buffer, 0, bytemuck::bytes_of(&sp));
|
|
551
|
+
|
|
552
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
553
|
+
label: Some("sss_bg"),
|
|
554
|
+
layout: &self.sss_layout,
|
|
555
|
+
entries: &[
|
|
556
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.sss_uniform_buffer.as_entire_binding() },
|
|
557
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(pre_sss_view) },
|
|
558
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
559
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.depth_view) },
|
|
560
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&self.ssao_depth_sampler) },
|
|
561
|
+
],
|
|
562
|
+
});
|
|
563
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
564
|
+
label: Some("sss_pass"),
|
|
565
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
566
|
+
view: &self.sss_rt_view,
|
|
567
|
+
resolve_target: None,
|
|
568
|
+
depth_slice: None,
|
|
569
|
+
ops: wgpu::Operations {
|
|
570
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
571
|
+
store: wgpu::StoreOp::Store,
|
|
572
|
+
},
|
|
573
|
+
})],
|
|
574
|
+
depth_stencil_attachment: None,
|
|
575
|
+
timestamp_writes: None,
|
|
576
|
+
occlusion_query_set: None,
|
|
577
|
+
multiview_mask: None,
|
|
578
|
+
});
|
|
579
|
+
pass.set_pipeline(&self.sss_pipeline);
|
|
580
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
581
|
+
pass.draw(0..3, 0..1);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ============================================================
|
|
585
|
+
// RCAS sharpen pass: contrast-adaptive 5-tap cross. Reads the
|
|
586
|
+
// same texture composite would otherwise sample (sss/mb/dof/
|
|
587
|
+
// taa/upscale/composed) and writes cas_rt. Off by default —
|
|
588
|
+
// gated on cas_strength > 0.
|
|
589
|
+
// ============================================================
|
|
590
|
+
let cas_input_view: &wgpu::TextureView = if self.sss_enabled && self.sss_strength > 0.0 {
|
|
591
|
+
&self.sss_rt_view
|
|
592
|
+
} else if self.motion_blur_enabled && self.motion_blur_strength > 0.0 {
|
|
593
|
+
&self.motion_blur_rt_view
|
|
594
|
+
} else if self.dof_enabled && self.dof_aperture > 0.0 {
|
|
595
|
+
&self.dof_rt_view
|
|
596
|
+
} else if self.taa_enabled {
|
|
597
|
+
&self.taa_views[taa_dst_idx]
|
|
598
|
+
} else if self.render_scale < 0.999 {
|
|
599
|
+
&self.upscale_rt_view
|
|
600
|
+
} else {
|
|
601
|
+
// TAA off, native res: composed_rt is already full-surface
|
|
602
|
+
// and carries SSR / SSGI / bloom / fog / shafts.
|
|
603
|
+
&self.composed_rt_view
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
if self.cas_strength > 0.0 {
|
|
607
|
+
let cp = RcasParams {
|
|
608
|
+
params: [self.cas_strength, 0.0, 0.0, 0.0],
|
|
609
|
+
};
|
|
610
|
+
self.queue.write_buffer(&self.cas_uniform_buffer, 0, bytemuck::bytes_of(&cp));
|
|
611
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
612
|
+
label: Some("cas_bg"),
|
|
613
|
+
layout: &self.cas_layout,
|
|
614
|
+
entries: &[
|
|
615
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.cas_uniform_buffer.as_entire_binding() },
|
|
616
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(cas_input_view) },
|
|
617
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
618
|
+
],
|
|
619
|
+
});
|
|
620
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
621
|
+
label: Some("cas_pass"),
|
|
622
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
623
|
+
view: &self.cas_rt_view,
|
|
624
|
+
resolve_target: None,
|
|
625
|
+
depth_slice: None,
|
|
626
|
+
ops: wgpu::Operations {
|
|
627
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
628
|
+
store: wgpu::StoreOp::Store,
|
|
629
|
+
},
|
|
630
|
+
})],
|
|
631
|
+
depth_stencil_attachment: None,
|
|
632
|
+
timestamp_writes: None,
|
|
633
|
+
occlusion_query_set: None,
|
|
634
|
+
multiview_mask: None,
|
|
635
|
+
});
|
|
636
|
+
pass.set_pipeline(&self.cas_pipeline);
|
|
637
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
638
|
+
pass.draw(0..3, 0..1);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/// The view the composite pass reads: the output of the LAST enabled
|
|
644
|
+
/// stage in chain order CAS > SSS > motion blur > DoF > TAA >
|
|
645
|
+
/// upscale > raw HDR. Must mirror the `pre_*_view` cascade in
|
|
646
|
+
/// record_postfx_tail.
|
|
647
|
+
pub(super) fn composite_source_view(&self) -> &wgpu::TextureView {
|
|
648
|
+
if self.cas_strength > 0.0 {
|
|
649
|
+
&self.cas_rt_view
|
|
650
|
+
} else if self.sss_enabled && self.sss_strength > 0.0 {
|
|
651
|
+
&self.sss_rt_view
|
|
652
|
+
} else if self.motion_blur_enabled && self.motion_blur_strength > 0.0 {
|
|
653
|
+
&self.motion_blur_rt_view
|
|
654
|
+
} else if self.dof_enabled && self.dof_aperture > 0.0 {
|
|
655
|
+
&self.dof_rt_view
|
|
656
|
+
} else if self.taa_enabled {
|
|
657
|
+
&self.taa_views[self.taa_current_idx]
|
|
658
|
+
} else if self.render_scale < 0.999 {
|
|
659
|
+
&self.upscale_rt_view
|
|
660
|
+
} else {
|
|
661
|
+
&self.hdr_rt_view
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
impl Renderer {
|
|
667
|
+
/// Auto-exposure measure + adapt pass into the dst slot of the
|
|
668
|
+
/// ping-pong exposure texture. No-op when auto_exposure is off (the
|
|
669
|
+
/// composite keeps reading the stale texture, which manual_exposure
|
|
670
|
+
/// bypasses). The caller owns the src/dst indices because the
|
|
671
|
+
/// composite binds the same dst view.
|
|
672
|
+
pub(super) fn record_auto_exposure(
|
|
673
|
+
&mut self,
|
|
674
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
675
|
+
exposure_src_idx: usize,
|
|
676
|
+
exposure_dst_idx: usize,
|
|
677
|
+
) {
|
|
678
|
+
// The luminance source is whatever the composite will read.
|
|
679
|
+
let composite_src_view = self.composite_source_view();
|
|
680
|
+
if self.auto_exposure {
|
|
681
|
+
let ep = ExposureParams {
|
|
682
|
+
params: [
|
|
683
|
+
self.auto_exposure_key,
|
|
684
|
+
self.auto_exposure_rate,
|
|
685
|
+
// Wide clamp — without SSGI, Sponza's shadowed
|
|
686
|
+
// corridors have ~7× less average luma than its
|
|
687
|
+
// sunlit courtyard, so exposure needs to span
|
|
688
|
+
// the same range to keep perceived brightness
|
|
689
|
+
// stable across rotations.
|
|
690
|
+
0.1,
|
|
691
|
+
10.0,
|
|
692
|
+
],
|
|
693
|
+
};
|
|
694
|
+
self.queue.write_buffer(&self.exposure_uniform_buffer, 0, bytemuck::bytes_of(&ep));
|
|
695
|
+
|
|
696
|
+
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
697
|
+
label: Some("exposure_bg"),
|
|
698
|
+
layout: &self.exposure_layout,
|
|
699
|
+
entries: &[
|
|
700
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.exposure_uniform_buffer.as_entire_binding() },
|
|
701
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(composite_src_view) },
|
|
702
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
703
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.exposure_views[exposure_src_idx]) },
|
|
704
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
|
|
705
|
+
],
|
|
706
|
+
});
|
|
707
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
708
|
+
label: Some("exposure_pass"),
|
|
709
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
710
|
+
view: &self.exposure_views[exposure_dst_idx],
|
|
711
|
+
resolve_target: None,
|
|
712
|
+
depth_slice: None,
|
|
713
|
+
ops: wgpu::Operations {
|
|
714
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
715
|
+
store: wgpu::StoreOp::Store,
|
|
716
|
+
},
|
|
717
|
+
})],
|
|
718
|
+
depth_stencil_attachment: None,
|
|
719
|
+
timestamp_writes: None,
|
|
720
|
+
occlusion_query_set: None,
|
|
721
|
+
multiview_mask: None,
|
|
722
|
+
});
|
|
723
|
+
pass.set_pipeline(&self.exposure_pipeline);
|
|
724
|
+
pass.set_bind_group(0, &bg, &[]);
|
|
725
|
+
pass.draw(0..3, 0..1);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|