@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,607 @@
|
|
|
1
|
+
//! The HDR scene pass: sky-view LUT refresh, sky + immediate-mode 3D +
|
|
2
|
+
//! retained scene graph rendered into the linear-HDR MRT set (HDR +
|
|
3
|
+
//! material + velocity + albedo + depth), followed by the opaque
|
|
4
|
+
//! material pass running on the inner render graph. Split from
|
|
5
|
+
//! end_frame_with_scene (2000-line file policy + render-graph
|
|
6
|
+
//! migration).
|
|
7
|
+
|
|
8
|
+
use super::*;
|
|
9
|
+
|
|
10
|
+
impl Renderer {
|
|
11
|
+
pub(super) fn record_hdr_scene_pass(
|
|
12
|
+
&mut self,
|
|
13
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
14
|
+
profiler: &mut crate::profiler::Profiler,
|
|
15
|
+
scene: &mut crate::scene::SceneGraph,
|
|
16
|
+
) {
|
|
17
|
+
// Rebind: the immediate-mode 3D upload just before this call
|
|
18
|
+
// checks the same predicate; vertices_3d is untouched between.
|
|
19
|
+
let has_3d = !self.vertices_3d.is_empty();
|
|
20
|
+
// ============================================================
|
|
21
|
+
// HDR pass: sky + 3D + scene → linear HDR offscreen RT.
|
|
22
|
+
// ============================================================
|
|
23
|
+
// The composite-tonemap pass downstream reads this RT and
|
|
24
|
+
// writes the final image to the sRGB surface. Keeping the
|
|
25
|
+
// intermediate radiance in HDR sets up a future bloom pass
|
|
26
|
+
// and means tonemap + sRGB encode happen exactly once, in
|
|
27
|
+
// one place.
|
|
28
|
+
// EN-005 Phase 2 — refresh the sky-view LUT before the HDR
|
|
29
|
+
// pass opens. The compute dispatch can't be nested inside a
|
|
30
|
+
// render pass, and `maybe_update_sky_view_lut` is a no-op
|
|
31
|
+
// unless the sun (or atmosphere knobs) actually changed.
|
|
32
|
+
// EN-005 V2 — also re-bake the aerial-perspective volume,
|
|
33
|
+
// which must happen every frame because the camera moves.
|
|
34
|
+
if self.procedural_sky_enabled {
|
|
35
|
+
self.maybe_update_sky_view_lut();
|
|
36
|
+
self.dispatch_aerial_perspective_lut();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// EN-044 — DEPTH PREPASS over the cached-model draws.
|
|
40
|
+
//
|
|
41
|
+
// The scene fragment shader can `discard` (alpha-cutout foliage), and a shader
|
|
42
|
+
// that may discard cannot early-Z *write*: the GPU must run it in full before it
|
|
43
|
+
// knows whether the pixel survives. So an 88-tree forest of overlapping leaf
|
|
44
|
+
// cards shaded the whole 5-target MRT several layers deep and threw most of it
|
|
45
|
+
// away. Measured: the forest alone was 5.6 ms of a 7.4 ms main_hdr_pass, and
|
|
46
|
+
// dropping it took the title screen from 46.7 fps to the 60 fps vsync cap.
|
|
47
|
+
//
|
|
48
|
+
// Priming depth first turns that around. The prepass writes depth only (no MRT,
|
|
49
|
+
// no lighting, alpha cutout honoured so cards keep their real silhouette), and
|
|
50
|
+
// the main pass then early-Z *rejects* the occluded leaves before its shader
|
|
51
|
+
// ever runs. The main pass tests LessEqual, not Less — the visible surface
|
|
52
|
+
// arrives with a depth exactly equal to the one the prepass stored, and `Less`
|
|
53
|
+
// would throw it away.
|
|
54
|
+
//
|
|
55
|
+
// Same vertex stage, so the foliage wind displaces identically in both and the
|
|
56
|
+
// depths agree to the bit.
|
|
57
|
+
// Runs even with no cached models, because it now owns the depth CLEAR that
|
|
58
|
+
// main_hdr_pass used to do — skipping it would hand the main pass a depth
|
|
59
|
+
// buffer full of last frame's garbage.
|
|
60
|
+
profiler.begin("depth_prepass");
|
|
61
|
+
{
|
|
62
|
+
let prepass_ts = profiler.pass_timestamp_writes("depth_prepass");
|
|
63
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
64
|
+
label: Some("bloom_depth_prepass"),
|
|
65
|
+
color_attachments: &[],
|
|
66
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
67
|
+
view: &self.depth_view,
|
|
68
|
+
depth_ops: Some(wgpu::Operations {
|
|
69
|
+
load: wgpu::LoadOp::Clear(1.0),
|
|
70
|
+
store: wgpu::StoreOp::Store,
|
|
71
|
+
}),
|
|
72
|
+
stencil_ops: None,
|
|
73
|
+
}),
|
|
74
|
+
timestamp_writes: prepass_ts,
|
|
75
|
+
occlusion_query_set: None,
|
|
76
|
+
multiview_mask: None,
|
|
77
|
+
});
|
|
78
|
+
// EN-063 — on wasm the prepass DRAWS are skipped (the pass still
|
|
79
|
+
// owns the depth clear). The prepass/main pairing requires the two
|
|
80
|
+
// pipelines to produce bit-identical depths; Tint (the browser's
|
|
81
|
+
// WGSL compiler) does not preserve that even under @invariant once
|
|
82
|
+
// the foliage-wind displacement is in the chain, and the main
|
|
83
|
+
// pass's Equal test then discards whole leaf cards (white torn
|
|
84
|
+
// canopies, streaks). The main pass runs the classic Less+write
|
|
85
|
+
// pipeline on wasm instead — overdraw over early-Z, correctness
|
|
86
|
+
// over speed.
|
|
87
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
88
|
+
{
|
|
89
|
+
pass.set_pipeline(&self.scene_depth_pipeline);
|
|
90
|
+
pass.set_bind_group(1, &self.lighting_bind_group, &[]);
|
|
91
|
+
pass.set_bind_group(3, &self.joint_bind_group, &[]);
|
|
92
|
+
let cam_vp = mat4_multiply(
|
|
93
|
+
self.current_proj_matrix_unjittered,
|
|
94
|
+
self.current_view_matrix,
|
|
95
|
+
);
|
|
96
|
+
let cam_planes = crate::scene::extract_frustum_planes(&cam_vp);
|
|
97
|
+
for cmd in &self.model_draw_commands {
|
|
98
|
+
if let Some(Some(meshes)) = self.model_gpu_cache.get(&cmd.cache_handle) {
|
|
99
|
+
if cmd.mesh_idx < meshes.len() {
|
|
100
|
+
let mesh = &meshes[cmd.mesh_idx];
|
|
101
|
+
let (wmin, wmax) = cmd.bounds_override.unwrap_or_else(|| {
|
|
102
|
+
transform_aabb(&cmd.model, mesh.local_min, mesh.local_max)
|
|
103
|
+
});
|
|
104
|
+
if wmin[0] <= wmax[0]
|
|
105
|
+
&& crate::scene::aabb_outside_frustum(&cam_planes, wmin, wmax)
|
|
106
|
+
{
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
pass.set_bind_group(0, &self.model_uniform_bind_groups[cmd.uniform_slot], &[]);
|
|
110
|
+
pass.set_bind_group(2, &mesh.material_bg, &[]);
|
|
111
|
+
pass.set_vertex_buffer(0, mesh.vb.slice(..));
|
|
112
|
+
pass.set_index_buffer(mesh.ib.slice(..), wgpu::IndexFormat::Uint32);
|
|
113
|
+
pass.draw_indexed(0..mesh.index_count, 0, 0..1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
profiler.end("depth_prepass");
|
|
120
|
+
|
|
121
|
+
profiler.begin("main_hdr_pass");
|
|
122
|
+
{
|
|
123
|
+
// HDR clear: the user's clear_color is in 0-1 srgb-ish
|
|
124
|
+
// range; treat it as the linear background for the HDR
|
|
125
|
+
// RT. After tonemap it ends up roughly the same shade.
|
|
126
|
+
let hdr_ts = profiler.pass_timestamp_writes("main_hdr_pass");
|
|
127
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
128
|
+
label: Some("bloom_hdr_pass"),
|
|
129
|
+
color_attachments: &[
|
|
130
|
+
Some(wgpu::RenderPassColorAttachment {
|
|
131
|
+
view: &self.hdr_rt_view,
|
|
132
|
+
resolve_target: None,
|
|
133
|
+
depth_slice: None,
|
|
134
|
+
ops: wgpu::Operations {
|
|
135
|
+
load: wgpu::LoadOp::Clear(self.clear_color),
|
|
136
|
+
store: wgpu::StoreOp::Store,
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
Some(wgpu::RenderPassColorAttachment {
|
|
140
|
+
view: &self.material_rt_view,
|
|
141
|
+
resolve_target: None,
|
|
142
|
+
depth_slice: None,
|
|
143
|
+
ops: wgpu::Operations {
|
|
144
|
+
// Blank pixels clear to metallic=0. SSR's
|
|
145
|
+
// `metallic < 0.2` gate early-outs before
|
|
146
|
+
// roughness is read, so the roughness
|
|
147
|
+
// component of the clear is dead — leaving
|
|
148
|
+
// it at 0 instead of 1 keeps the material
|
|
149
|
+
// texture black in frame captures and
|
|
150
|
+
// avoids a false "green G-buffer" readout
|
|
151
|
+
// if the RT is ever viewed as RGBA.
|
|
152
|
+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
153
|
+
store: wgpu::StoreOp::Store,
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
Some(wgpu::RenderPassColorAttachment {
|
|
157
|
+
view: &self.velocity_rt_view,
|
|
158
|
+
resolve_target: None,
|
|
159
|
+
depth_slice: None,
|
|
160
|
+
ops: wgpu::Operations {
|
|
161
|
+
// Zero velocity = stationary pixel.
|
|
162
|
+
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
|
|
163
|
+
store: wgpu::StoreOp::Store,
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
Some(wgpu::RenderPassColorAttachment {
|
|
167
|
+
view: &self.albedo_rt_view,
|
|
168
|
+
resolve_target: None,
|
|
169
|
+
depth_slice: None,
|
|
170
|
+
ops: wgpu::Operations {
|
|
171
|
+
// Clear to zero albedo — pixels the scene
|
|
172
|
+
// doesn't cover (before sky writes) absorb
|
|
173
|
+
// indirect light fully. Sky then writes 0
|
|
174
|
+
// too so SSGI rays landing on sky don't
|
|
175
|
+
// re-tint bounce by background radiance.
|
|
176
|
+
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
|
|
177
|
+
store: wgpu::StoreOp::Store,
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
],
|
|
181
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
182
|
+
view: &self.depth_view,
|
|
183
|
+
depth_ops: Some(wgpu::Operations {
|
|
184
|
+
// EN-044 — LOAD, not Clear: the depth prepass just primed this
|
|
185
|
+
// buffer, and clearing it here would throw that away.
|
|
186
|
+
load: wgpu::LoadOp::Load,
|
|
187
|
+
store: wgpu::StoreOp::Store,
|
|
188
|
+
}),
|
|
189
|
+
stencil_ops: None,
|
|
190
|
+
}),
|
|
191
|
+
timestamp_writes: hdr_ts,
|
|
192
|
+
occlusion_query_set: None,
|
|
193
|
+
multiview_mask: None,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Sky uses the same env_intensity as IBL so the background
|
|
197
|
+
// and lighting stay in sync — otherwise bumping IBL down
|
|
198
|
+
// would leave the sky blown out.
|
|
199
|
+
if self.procedural_sky_enabled {
|
|
200
|
+
self.render_procedural_sky_pass(&mut pass, self.lighting_uniforms.camera_pos[3]);
|
|
201
|
+
} else {
|
|
202
|
+
self.render_sky_pass(&mut pass, self.lighting_uniforms.camera_pos[3]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if has_3d {
|
|
206
|
+
pass.set_pipeline(&self.pipeline_3d);
|
|
207
|
+
pass.set_bind_group(0, &self.uniform_bind_group_3d, &[]);
|
|
208
|
+
pass.set_bind_group(1, &self.lighting_bind_group, &[]);
|
|
209
|
+
pass.set_bind_group(3, &self.joint_bind_group, &[]);
|
|
210
|
+
pass.set_vertex_buffer(0, self.persistent_vb_3d.slice(..));
|
|
211
|
+
pass.set_index_buffer(self.persistent_ib_3d.slice(..), wgpu::IndexFormat::Uint32);
|
|
212
|
+
|
|
213
|
+
if self.draw_calls_3d.is_empty() {
|
|
214
|
+
pass.set_bind_group(2, &self.texture_bind_groups[0], &[]);
|
|
215
|
+
pass.draw_indexed(0..self.indices_3d.len() as u32, 0, 0..1);
|
|
216
|
+
} else {
|
|
217
|
+
let num_calls = self.draw_calls_3d.len();
|
|
218
|
+
for i in 0..num_calls {
|
|
219
|
+
let call = &self.draw_calls_3d[i];
|
|
220
|
+
let next_start = if i + 1 < num_calls {
|
|
221
|
+
self.draw_calls_3d[i + 1].index_start
|
|
222
|
+
} else {
|
|
223
|
+
self.indices_3d.len() as u32
|
|
224
|
+
};
|
|
225
|
+
let count = next_start - call.index_start;
|
|
226
|
+
if count == 0 { continue; }
|
|
227
|
+
let tex_idx = call.texture_idx as usize;
|
|
228
|
+
if tex_idx < self.texture_bind_groups.len() {
|
|
229
|
+
pass.set_bind_group(2, &self.texture_bind_groups[tex_idx], &[]);
|
|
230
|
+
} else {
|
|
231
|
+
pass.set_bind_group(2, &self.texture_bind_groups[0], &[]);
|
|
232
|
+
}
|
|
233
|
+
pass.draw_indexed(call.index_start..next_start, 0, 0..1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Cached models + retained scene graph — both via scene_pipeline.
|
|
239
|
+
let has_cached_models = !self.model_draw_commands.is_empty();
|
|
240
|
+
if has_cached_models || scene.node_count() > 0 {
|
|
241
|
+
// EN-044 — cached models go through the PREPASSED pipeline (no depth
|
|
242
|
+
// write, Equal test), because the depth prepass above already stored
|
|
243
|
+
// their exact depth. That is what lets the hardware early-Z reject the
|
|
244
|
+
// occluded leaf cards instead of shading every one of them.
|
|
245
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
246
|
+
pass.set_pipeline(&self.scene_pipeline_prepassed);
|
|
247
|
+
// wasm: no prepass priming (see above) — classic Less + write.
|
|
248
|
+
#[cfg(target_arch = "wasm32")]
|
|
249
|
+
pass.set_pipeline(&self.scene_pipeline);
|
|
250
|
+
pass.set_bind_group(1, &self.lighting_bind_group, &[]);
|
|
251
|
+
pass.set_bind_group(3, &self.joint_bind_group, &[]);
|
|
252
|
+
|
|
253
|
+
if has_cached_models {
|
|
254
|
+
// Frustum-cull cached-model draws against the camera. These
|
|
255
|
+
// commands (the forest: ~400 draws/frame) previously had no
|
|
256
|
+
// culling in any pass — everything behind the camera still
|
|
257
|
+
// paid bind-group switches + a full VS pass. AABBs are
|
|
258
|
+
// conservative (cache-time local bounds × model matrix), and
|
|
259
|
+
// the unjittered projection is used so TAA's sub-pixel
|
|
260
|
+
// jitter can't flicker a borderline caster (the AABB slop
|
|
261
|
+
// exceeds jitter by orders of magnitude).
|
|
262
|
+
let cam_vp = mat4_multiply(
|
|
263
|
+
self.current_proj_matrix_unjittered,
|
|
264
|
+
self.current_view_matrix,
|
|
265
|
+
);
|
|
266
|
+
let cam_planes = crate::scene::extract_frustum_planes(&cam_vp);
|
|
267
|
+
for cmd in &self.model_draw_commands {
|
|
268
|
+
if let Some(Some(meshes)) = self.model_gpu_cache.get(&cmd.cache_handle) {
|
|
269
|
+
if cmd.mesh_idx < meshes.len() {
|
|
270
|
+
let mesh = &meshes[cmd.mesh_idx];
|
|
271
|
+
// Skinned draws carry a pre-computed joint-union
|
|
272
|
+
// AABB (their rest AABB × model matrix would be
|
|
273
|
+
// wrong once posed); static draws derive theirs.
|
|
274
|
+
let (wmin, wmax) = cmd.bounds_override.unwrap_or_else(|| {
|
|
275
|
+
transform_aabb(&cmd.model, mesh.local_min, mesh.local_max)
|
|
276
|
+
});
|
|
277
|
+
if wmin[0] <= wmax[0]
|
|
278
|
+
&& crate::scene::aabb_outside_frustum(&cam_planes, wmin, wmax)
|
|
279
|
+
{
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
pass.set_bind_group(0, &self.model_uniform_bind_groups[cmd.uniform_slot], &[]);
|
|
283
|
+
pass.set_bind_group(2, &mesh.material_bg, &[]);
|
|
284
|
+
pass.set_vertex_buffer(0, mesh.vb.slice(..));
|
|
285
|
+
pass.set_index_buffer(mesh.ib.slice(..), wgpu::IndexFormat::Uint32);
|
|
286
|
+
pass.draw_indexed(0..mesh.index_count, 0, 0..1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Retained scene-graph nodes are not in the prepass, so they still need
|
|
293
|
+
// the depth-writing pipeline.
|
|
294
|
+
pass.set_pipeline(&self.scene_pipeline);
|
|
295
|
+
scene.render(&mut pass);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
profiler.end("main_hdr_pass");
|
|
299
|
+
|
|
300
|
+
// EN-011 — render every registered planar reflection probe
|
|
301
|
+
// BEFORE the main material pass so the probe RTs are
|
|
302
|
+
// sampleable when materials run. No-op when no probes are
|
|
303
|
+
// registered or no opaque material draws are queued.
|
|
304
|
+
profiler.begin("planar_reflections");
|
|
305
|
+
self.dispatch_planar_reflections(&mut *encoder, scene, profiler);
|
|
306
|
+
profiler.end("planar_reflections");
|
|
307
|
+
|
|
308
|
+
// Phase 2c — schedule the material pass through the render
|
|
309
|
+
// graph. First real consumer of `renderer::graph` from #35.
|
|
310
|
+
// For now a one-node graph; later phases add more nodes
|
|
311
|
+
// (main_hdr, ssao, bloom, translucent, composite) and the
|
|
312
|
+
// graph's topological sort picks the order from read/write
|
|
313
|
+
// declarations.
|
|
314
|
+
//
|
|
315
|
+
// All per-frame borrows that the pass body needs are captured
|
|
316
|
+
// here from `&self` before we build the context that wraps
|
|
317
|
+
// `&mut *encoder` + `&mut profiler`. Rust's borrow checker is
|
|
318
|
+
// happy because the immutable and mutable borrows are
|
|
319
|
+
// disjoint fields of the same struct.
|
|
320
|
+
if !self.material_system.commands.is_empty() {
|
|
321
|
+
use graph::{Graph, PassNode, PassOutput};
|
|
322
|
+
|
|
323
|
+
let hdr_rt_view = &self.hdr_rt_view;
|
|
324
|
+
let material_rt_view = &self.material_rt_view;
|
|
325
|
+
let velocity_rt_view = &self.velocity_rt_view;
|
|
326
|
+
let albedo_rt_view = &self.albedo_rt_view;
|
|
327
|
+
let depth_view = &self.depth_view;
|
|
328
|
+
let material_system = &self.material_system;
|
|
329
|
+
let model_gpu_cache = &self.model_gpu_cache;
|
|
330
|
+
// Camera frustum for instance-tile culling (same unjittered
|
|
331
|
+
// matrices as the cached-model culling above).
|
|
332
|
+
let cam_planes = crate::scene::extract_frustum_planes(&mat4_multiply(
|
|
333
|
+
self.current_proj_matrix_unjittered,
|
|
334
|
+
self.current_view_matrix,
|
|
335
|
+
));
|
|
336
|
+
|
|
337
|
+
struct FrameCtx<'a> {
|
|
338
|
+
encoder: &'a mut wgpu::CommandEncoder,
|
|
339
|
+
profiler: &'a mut crate::profiler::Profiler,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let mut graph: Graph<FrameCtx<'_>> = Graph::new();
|
|
343
|
+
graph.push(
|
|
344
|
+
PassNode::new("material_pass", Box::new(move |ctx: &mut FrameCtx| {
|
|
345
|
+
ctx.profiler.begin("material_pass");
|
|
346
|
+
{
|
|
347
|
+
let mat_ts = ctx.profiler.pass_timestamp_writes("material_pass");
|
|
348
|
+
let mut pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
349
|
+
label: Some("bloom_material_pass"),
|
|
350
|
+
color_attachments: &[
|
|
351
|
+
Some(wgpu::RenderPassColorAttachment {
|
|
352
|
+
view: hdr_rt_view,
|
|
353
|
+
resolve_target: None, depth_slice: None,
|
|
354
|
+
ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store },
|
|
355
|
+
}),
|
|
356
|
+
Some(wgpu::RenderPassColorAttachment {
|
|
357
|
+
view: material_rt_view,
|
|
358
|
+
resolve_target: None, depth_slice: None,
|
|
359
|
+
ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store },
|
|
360
|
+
}),
|
|
361
|
+
Some(wgpu::RenderPassColorAttachment {
|
|
362
|
+
view: velocity_rt_view,
|
|
363
|
+
resolve_target: None, depth_slice: None,
|
|
364
|
+
ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store },
|
|
365
|
+
}),
|
|
366
|
+
Some(wgpu::RenderPassColorAttachment {
|
|
367
|
+
view: albedo_rt_view,
|
|
368
|
+
resolve_target: None, depth_slice: None,
|
|
369
|
+
ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store },
|
|
370
|
+
}),
|
|
371
|
+
],
|
|
372
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
373
|
+
view: depth_view,
|
|
374
|
+
depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store }),
|
|
375
|
+
stencil_ops: None,
|
|
376
|
+
}),
|
|
377
|
+
timestamp_writes: mat_ts,
|
|
378
|
+
occlusion_query_set: None,
|
|
379
|
+
multiview_mask: None,
|
|
380
|
+
});
|
|
381
|
+
material_system.dispatch(&mut pass, Some(&cam_planes), |handle, idx| {
|
|
382
|
+
if let Some(Some(meshes)) = model_gpu_cache.get(&handle) {
|
|
383
|
+
if idx < meshes.len() {
|
|
384
|
+
let mesh = &meshes[idx];
|
|
385
|
+
return Some((
|
|
386
|
+
&mesh.vb, &mesh.ib, mesh.index_count,
|
|
387
|
+
mesh.local_min, mesh.local_max,
|
|
388
|
+
));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
None
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
ctx.profiler.end("material_pass");
|
|
395
|
+
}))
|
|
396
|
+
// Writes HdrColor + the G-buffer so Phase 2d's scheduler
|
|
397
|
+
// can order downstream passes (SSAO, bloom, translucent)
|
|
398
|
+
// correctly once they're nodes too.
|
|
399
|
+
.with_writes(&[
|
|
400
|
+
PassOutput::HdrColor,
|
|
401
|
+
PassOutput::MaterialRt,
|
|
402
|
+
PassOutput::VelocityRt,
|
|
403
|
+
PassOutput::AlbedoRt,
|
|
404
|
+
PassOutput::Depth,
|
|
405
|
+
]),
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
let mut ctx = FrameCtx { encoder: &mut *encoder, profiler: &mut *profiler };
|
|
409
|
+
if let Err(e) = graph.execute(&mut ctx) {
|
|
410
|
+
eprintln!("[graph] material_pass failed: {:?}", e);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
impl Renderer {
|
|
418
|
+
/// Translucent / refractive / additive material pass: after opaque,
|
|
419
|
+
/// before post-FX; loads hdr_rt, depth read-only, back-to-front
|
|
420
|
+
/// sorted; snapshots scene color for reads_scene materials. Split
|
|
421
|
+
/// from end_frame_with_scene.
|
|
422
|
+
pub(super) fn record_translucent_pass(
|
|
423
|
+
&mut self,
|
|
424
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
425
|
+
profiler: &mut crate::profiler::Profiler,
|
|
426
|
+
) {
|
|
427
|
+
// ============================================================
|
|
428
|
+
// Phase 4b — translucent / refractive / additive material pass
|
|
429
|
+
// ============================================================
|
|
430
|
+
//
|
|
431
|
+
// Runs after opaque materials, before post-FX. Loads hdr_rt so
|
|
432
|
+
// opaque output survives; alpha-blends into it. Depth is
|
|
433
|
+
// bound as read-only so translucent draws participate in the
|
|
434
|
+
// depth test without writing.
|
|
435
|
+
//
|
|
436
|
+
// If any submitted translucent material declared
|
|
437
|
+
// `reads_scene = true`, we first snapshot hdr_rt into a
|
|
438
|
+
// swapchain-sized transient and bind that as group 4
|
|
439
|
+
// scene_color_tex for the dispatch. Free after the pass so
|
|
440
|
+
// the transient pool reuses on the next frame.
|
|
441
|
+
if !self.material_system.translucent_commands.is_empty() {
|
|
442
|
+
// Back-to-front by view depth — required for correct alpha
|
|
443
|
+
// compositing; submission order is only kept between
|
|
444
|
+
// equal-depth draws (stable sort).
|
|
445
|
+
self.material_system.sort_translucent();
|
|
446
|
+
profiler.begin("translucent_pass");
|
|
447
|
+
let swap_w = self.surface_config.width;
|
|
448
|
+
let swap_h = self.surface_config.height;
|
|
449
|
+
self.transient_pool.begin_frame(swap_w, swap_h);
|
|
450
|
+
|
|
451
|
+
// Phase 7 — run the impulse decay + splat compute BEFORE
|
|
452
|
+
// we build scene_inputs so the front view reflects this
|
|
453
|
+
// frame's submissions.
|
|
454
|
+
self.impulse_field.update(&self.device, &self.queue, &mut *encoder);
|
|
455
|
+
|
|
456
|
+
// Does any queued translucent material need the scene
|
|
457
|
+
// colour snapshot?
|
|
458
|
+
let needs_scene = self.material_system.translucent_commands
|
|
459
|
+
.iter()
|
|
460
|
+
.any(|c| self.material_system.pipelines
|
|
461
|
+
.get(c.material as usize - 1)
|
|
462
|
+
.and_then(|p| p.as_ref())
|
|
463
|
+
.map(|p| p.reads_scene)
|
|
464
|
+
.unwrap_or(false));
|
|
465
|
+
|
|
466
|
+
// The snapshots mirror hdr_rt/depth, which live at RENDER
|
|
467
|
+
// resolution (surface × render_scale) — not swapchain size.
|
|
468
|
+
// Sizing them from the swapchain overruns the source copy
|
|
469
|
+
// whenever render_scale < 1 (TSR upscaling).
|
|
470
|
+
let (render_w, render_h) = self.render_extent();
|
|
471
|
+
let scene_color_tid = if needs_scene {
|
|
472
|
+
let desc = transient::TransientDesc::new(
|
|
473
|
+
formats::HDR_FORMAT,
|
|
474
|
+
wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
|
|
475
|
+
transient::SizePolicy::Fixed(render_w, render_h),
|
|
476
|
+
);
|
|
477
|
+
Some(self.transient_pool.acquire(&self.device, desc))
|
|
478
|
+
} else {
|
|
479
|
+
None
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// Phase 4c — depth snapshot. wgpu forbids sampling a
|
|
483
|
+
// texture that is also a depth-stencil attachment of the
|
|
484
|
+
// same pass, so we copy the opaque depth buffer into a
|
|
485
|
+
// transient before beginning the translucent pass and
|
|
486
|
+
// bind the transient at group 4 binding 2. Acquired
|
|
487
|
+
// whenever any translucent material reads_scene (same
|
|
488
|
+
// gate as colour) — cheap enough that it's not worth a
|
|
489
|
+
// separate `reads_depth` flag yet.
|
|
490
|
+
let scene_depth_tid = if needs_scene {
|
|
491
|
+
let desc = transient::TransientDesc::new(
|
|
492
|
+
formats::DEPTH_FORMAT,
|
|
493
|
+
wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
|
|
494
|
+
transient::SizePolicy::Fixed(render_w, render_h),
|
|
495
|
+
);
|
|
496
|
+
Some(self.transient_pool.acquire(&self.device, desc))
|
|
497
|
+
} else {
|
|
498
|
+
None
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// Snapshot hdr_rt + live depth -> transients.
|
|
502
|
+
if let (Some(ctid), Some(dtid)) = (scene_color_tid, scene_depth_tid) {
|
|
503
|
+
let color_tex = self.transient_pool.texture(ctid).expect("fresh color transient");
|
|
504
|
+
encoder.copy_texture_to_texture(
|
|
505
|
+
wgpu::TexelCopyTextureInfo {
|
|
506
|
+
texture: &self.hdr_rt_texture,
|
|
507
|
+
mip_level: 0,
|
|
508
|
+
origin: wgpu::Origin3d::ZERO,
|
|
509
|
+
aspect: wgpu::TextureAspect::All,
|
|
510
|
+
},
|
|
511
|
+
wgpu::TexelCopyTextureInfo {
|
|
512
|
+
texture: color_tex,
|
|
513
|
+
mip_level: 0,
|
|
514
|
+
origin: wgpu::Origin3d::ZERO,
|
|
515
|
+
aspect: wgpu::TextureAspect::All,
|
|
516
|
+
},
|
|
517
|
+
wgpu::Extent3d { width: render_w, height: render_h, depth_or_array_layers: 1 },
|
|
518
|
+
);
|
|
519
|
+
let depth_tex = self.transient_pool.texture(dtid).expect("fresh depth transient");
|
|
520
|
+
encoder.copy_texture_to_texture(
|
|
521
|
+
wgpu::TexelCopyTextureInfo {
|
|
522
|
+
texture: &self.depth_texture,
|
|
523
|
+
mip_level: 0,
|
|
524
|
+
origin: wgpu::Origin3d::ZERO,
|
|
525
|
+
aspect: wgpu::TextureAspect::DepthOnly,
|
|
526
|
+
},
|
|
527
|
+
wgpu::TexelCopyTextureInfo {
|
|
528
|
+
texture: depth_tex,
|
|
529
|
+
mip_level: 0,
|
|
530
|
+
origin: wgpu::Origin3d::ZERO,
|
|
531
|
+
aspect: wgpu::TextureAspect::DepthOnly,
|
|
532
|
+
},
|
|
533
|
+
wgpu::Extent3d { width: render_w, height: render_h, depth_or_array_layers: 1 },
|
|
534
|
+
);
|
|
535
|
+
let color_view = self.transient_pool.view(ctid).unwrap();
|
|
536
|
+
let depth_view = self.transient_pool.view(dtid).unwrap();
|
|
537
|
+
let imp_view = self.impulse_field.front_view();
|
|
538
|
+
let imp_samp = self.impulse_field.sampler();
|
|
539
|
+
self.material_system.update_scene_inputs(
|
|
540
|
+
&self.device, color_view, Some(depth_view),
|
|
541
|
+
Some((imp_view, imp_samp)),
|
|
542
|
+
);
|
|
543
|
+
} else {
|
|
544
|
+
// No refractive/depth-reading materials this frame —
|
|
545
|
+
// still need a valid bind group. None → internal stubs.
|
|
546
|
+
self.material_system.update_scene_inputs(
|
|
547
|
+
&self.device, &self.hdr_rt_view, None, None,
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
{
|
|
552
|
+
let t_ts = profiler.pass_timestamp_writes("translucent_pass");
|
|
553
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
554
|
+
label: Some("bloom_translucent_pass"),
|
|
555
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
556
|
+
view: &self.hdr_rt_view,
|
|
557
|
+
resolve_target: None,
|
|
558
|
+
depth_slice: None,
|
|
559
|
+
ops: wgpu::Operations {
|
|
560
|
+
load: wgpu::LoadOp::Load,
|
|
561
|
+
store: wgpu::StoreOp::Store,
|
|
562
|
+
},
|
|
563
|
+
})],
|
|
564
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
565
|
+
view: &self.depth_view,
|
|
566
|
+
depth_ops: Some(wgpu::Operations {
|
|
567
|
+
load: wgpu::LoadOp::Load,
|
|
568
|
+
// Translucents don't write depth — keep
|
|
569
|
+
// the opaque pass's depth pristine so
|
|
570
|
+
// downstream post-FX (SSR/SSGI) still
|
|
571
|
+
// sees the opaque geometry.
|
|
572
|
+
store: wgpu::StoreOp::Store,
|
|
573
|
+
}),
|
|
574
|
+
stencil_ops: None,
|
|
575
|
+
}),
|
|
576
|
+
timestamp_writes: t_ts,
|
|
577
|
+
occlusion_query_set: None,
|
|
578
|
+
multiview_mask: None,
|
|
579
|
+
});
|
|
580
|
+
let cache = &self.model_gpu_cache;
|
|
581
|
+
self.material_system.dispatch_translucent(&mut pass, |handle, idx| {
|
|
582
|
+
if let Some(Some(meshes)) = cache.get(&handle) {
|
|
583
|
+
if idx < meshes.len() {
|
|
584
|
+
let mesh = &meshes[idx];
|
|
585
|
+
return Some((&mesh.vb, &mesh.ib, mesh.index_count));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
None
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if let Some(tid) = scene_color_tid {
|
|
593
|
+
self.transient_pool.release(tid);
|
|
594
|
+
}
|
|
595
|
+
// Round-2 audit: the depth snapshot was acquired every frame but
|
|
596
|
+
// NEVER released — the pool (which has no auto-reclaim by design)
|
|
597
|
+
// allocated a brand-new render-res Depth32Float every frame with
|
|
598
|
+
// refractive water on screen. ~8 MB/frame of texture creation plus
|
|
599
|
+
// an ever-growing slot scan; measured as translucent_pass CPU
|
|
600
|
+
// climbing 0.4 ms → 6.7 ms over ~50 s of play, in every session.
|
|
601
|
+
if let Some(tid) = scene_depth_tid {
|
|
602
|
+
self.transient_pool.release(tid);
|
|
603
|
+
}
|
|
604
|
+
profiler.end("translucent_pass");
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|