@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.
Files changed (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/native/android/Cargo.lock +1848 -0
  4. package/native/android/Cargo.toml +24 -0
  5. package/native/android/src/lib.rs +702 -0
  6. package/native/ios/Cargo.lock +1690 -0
  7. package/native/ios/Cargo.toml +32 -0
  8. package/native/ios/src/lib.rs +1267 -0
  9. package/native/linux/Cargo.lock +3279 -0
  10. package/native/linux/Cargo.toml +29 -0
  11. package/native/linux/src/lib.rs +1331 -0
  12. package/native/macos/Cargo.lock +3310 -0
  13. package/native/macos/Cargo.toml +46 -0
  14. package/native/macos/src/lib.rs +1302 -0
  15. package/native/shared/Cargo.lock +1899 -0
  16. package/native/shared/Cargo.toml +62 -0
  17. package/native/shared/assets/default_font.ttf +0 -0
  18. package/native/shared/build.rs +270 -0
  19. package/native/shared/shaders/common/clouds.wgsl +122 -0
  20. package/native/shared/shaders/common/fog.wgsl +16 -0
  21. package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
  22. package/native/shared/shaders/common/imposter.wgsl +112 -0
  23. package/native/shared/shaders/common/pbr.wgsl +186 -0
  24. package/native/shared/shaders/common/shadows.wgsl +186 -0
  25. package/native/shared/shaders/common/sky.wgsl +8 -0
  26. package/native/shared/shaders/common/tonemap.wgsl +25 -0
  27. package/native/shared/shaders/impulse_field.wgsl +57 -0
  28. package/native/shared/shaders/material_abi.wgsl +383 -0
  29. package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
  30. package/native/shared/src/anim_mixer.rs +61 -0
  31. package/native/shared/src/attach.rs +263 -0
  32. package/native/shared/src/audio/decode.rs +123 -0
  33. package/native/shared/src/audio/mod.rs +863 -0
  34. package/native/shared/src/audio/render.rs +892 -0
  35. package/native/shared/src/audio/spsc.rs +156 -0
  36. package/native/shared/src/audio/stream.rs +226 -0
  37. package/native/shared/src/custom_shaders.rs +104 -0
  38. package/native/shared/src/decals.rs +245 -0
  39. package/native/shared/src/drs.rs +211 -0
  40. package/native/shared/src/engine.rs +261 -0
  41. package/native/shared/src/ffi.rs +116 -0
  42. package/native/shared/src/ffi_core/assets.rs +388 -0
  43. package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
  44. package/native/shared/src/ffi_core/draw.rs +334 -0
  45. package/native/shared/src/ffi_core/game_loop.rs +577 -0
  46. package/native/shared/src/ffi_core/input.rs +234 -0
  47. package/native/shared/src/ffi_core/mod.rs +127 -0
  48. package/native/shared/src/ffi_core/models.rs +1154 -0
  49. package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
  50. package/native/shared/src/ffi_core/scene.rs +626 -0
  51. package/native/shared/src/ffi_core/vfx.rs +212 -0
  52. package/native/shared/src/ffi_core/visual.rs +691 -0
  53. package/native/shared/src/frame_callbacks.rs +122 -0
  54. package/native/shared/src/geometry.rs +236 -0
  55. package/native/shared/src/handles.rs +182 -0
  56. package/native/shared/src/input.rs +448 -0
  57. package/native/shared/src/jolt_sys.rs +822 -0
  58. package/native/shared/src/lib.rs +55 -0
  59. package/native/shared/src/models.rs +1093 -0
  60. package/native/shared/src/models_gltf.rs +1280 -0
  61. package/native/shared/src/particles.rs +391 -0
  62. package/native/shared/src/physics_jolt.rs +1908 -0
  63. package/native/shared/src/picking.rs +298 -0
  64. package/native/shared/src/postfx.rs +345 -0
  65. package/native/shared/src/profiler.rs +492 -0
  66. package/native/shared/src/ragdoll.rs +474 -0
  67. package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
  68. package/native/shared/src/renderer/brdf_lut.rs +154 -0
  69. package/native/shared/src/renderer/draw2d.rs +143 -0
  70. package/native/shared/src/renderer/formats.rs +822 -0
  71. package/native/shared/src/renderer/froxel.rs +421 -0
  72. package/native/shared/src/renderer/gi_bake.rs +653 -0
  73. package/native/shared/src/renderer/graph.rs +462 -0
  74. package/native/shared/src/renderer/hiz.rs +269 -0
  75. package/native/shared/src/renderer/hot_reload.rs +390 -0
  76. package/native/shared/src/renderer/impulse_field.rs +456 -0
  77. package/native/shared/src/renderer/lighting.rs +154 -0
  78. package/native/shared/src/renderer/material_instancing.rs +171 -0
  79. package/native/shared/src/renderer/material_pipeline.rs +700 -0
  80. package/native/shared/src/renderer/material_system.rs +1996 -0
  81. package/native/shared/src/renderer/material_system_tests.rs +601 -0
  82. package/native/shared/src/renderer/material_system_wasm.rs +41 -0
  83. package/native/shared/src/renderer/mod.rs +12556 -0
  84. package/native/shared/src/renderer/model_draw.rs +641 -0
  85. package/native/shared/src/renderer/occlusion.rs +429 -0
  86. package/native/shared/src/renderer/planar_pass.rs +593 -0
  87. package/native/shared/src/renderer/planar_reflection.rs +499 -0
  88. package/native/shared/src/renderer/post_pass.rs +249 -0
  89. package/native/shared/src/renderer/postfx_chain.rs +728 -0
  90. package/native/shared/src/renderer/pt_pass.rs +577 -0
  91. package/native/shared/src/renderer/scene_pass.rs +607 -0
  92. package/native/shared/src/renderer/shader_include.rs +205 -0
  93. package/native/shared/src/renderer/shader_library.rs +135 -0
  94. package/native/shared/src/renderer/shaders/ao.rs +570 -0
  95. package/native/shared/src/renderer/shaders/core.rs +1243 -0
  96. package/native/shared/src/renderer/shaders/env.rs +907 -0
  97. package/native/shared/src/renderer/shaders/gi.rs +810 -0
  98. package/native/shared/src/renderer/shaders/mod.rs +19 -0
  99. package/native/shared/src/renderer/shaders/post.rs +1558 -0
  100. package/native/shared/src/renderer/shaders/pt.rs +1859 -0
  101. package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
  102. package/native/shared/src/renderer/shadow_pass.rs +731 -0
  103. package/native/shared/src/renderer/ssgi_pass.rs +392 -0
  104. package/native/shared/src/renderer/ssr_pass.rs +188 -0
  105. package/native/shared/src/renderer/texture_store.rs +473 -0
  106. package/native/shared/src/renderer/transient.rs +591 -0
  107. package/native/shared/src/renderer/types.rs +941 -0
  108. package/native/shared/src/renderer/util.rs +152 -0
  109. package/native/shared/src/scene.rs +1362 -0
  110. package/native/shared/src/sdf_cache.rs +274 -0
  111. package/native/shared/src/shadows.rs +1036 -0
  112. package/native/shared/src/staging.rs +102 -0
  113. package/native/shared/src/string_header.rs +266 -0
  114. package/native/shared/src/text_renderer.rs +502 -0
  115. package/native/shared/src/textures.rs +197 -0
  116. package/native/tvos/Cargo.lock +1693 -0
  117. package/native/tvos/Cargo.toml +36 -0
  118. package/native/tvos/metal-patched/Cargo.toml +178 -0
  119. package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
  120. package/native/tvos/metal-patched/LICENSE-MIT +25 -0
  121. package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
  122. package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
  123. package/native/tvos/metal-patched/src/argument.rs +366 -0
  124. package/native/tvos/metal-patched/src/blitpass.rs +102 -0
  125. package/native/tvos/metal-patched/src/buffer.rs +71 -0
  126. package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
  127. package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
  128. package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
  129. package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
  130. package/native/tvos/metal-patched/src/computepass.rs +107 -0
  131. package/native/tvos/metal-patched/src/constants.rs +152 -0
  132. package/native/tvos/metal-patched/src/counters.rs +119 -0
  133. package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
  134. package/native/tvos/metal-patched/src/device.rs +2134 -0
  135. package/native/tvos/metal-patched/src/drawable.rs +39 -0
  136. package/native/tvos/metal-patched/src/encoder.rs +2041 -0
  137. package/native/tvos/metal-patched/src/heap.rs +281 -0
  138. package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
  139. package/native/tvos/metal-patched/src/lib.rs +657 -0
  140. package/native/tvos/metal-patched/src/library.rs +902 -0
  141. package/native/tvos/metal-patched/src/mps.rs +575 -0
  142. package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
  143. package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
  144. package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
  145. package/native/tvos/metal-patched/src/renderpass.rs +443 -0
  146. package/native/tvos/metal-patched/src/resource.rs +182 -0
  147. package/native/tvos/metal-patched/src/sampler.rs +165 -0
  148. package/native/tvos/metal-patched/src/sync.rs +178 -0
  149. package/native/tvos/metal-patched/src/texture.rs +352 -0
  150. package/native/tvos/metal-patched/src/types.rs +90 -0
  151. package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
  152. package/native/tvos/src/audio_backend.rs +197 -0
  153. package/native/tvos/src/lib.rs +1891 -0
  154. package/native/visionos/Cargo.lock +1693 -0
  155. package/native/visionos/Cargo.toml +40 -0
  156. package/native/visionos/src/audio_backend.rs +197 -0
  157. package/native/visionos/src/lib.rs +1887 -0
  158. package/native/watchos/Cargo.lock +16 -0
  159. package/native/watchos/Cargo.toml +19 -0
  160. package/native/watchos/shaders/bloom_postfx.metal +99 -0
  161. package/native/watchos/src/BloomWatchApp.swift +1267 -0
  162. package/native/watchos/src/BloomWatchAudio.swift +179 -0
  163. package/native/watchos/src/audio.rs +55 -0
  164. package/native/watchos/src/draw_list.rs +229 -0
  165. package/native/watchos/src/ffi_stubs.rs +915 -0
  166. package/native/watchos/src/ffi_stubs_manual.rs +35 -0
  167. package/native/watchos/src/lib.rs +1124 -0
  168. package/native/watchos/src/models.rs +746 -0
  169. package/native/watchos/src/postfx.rs +95 -0
  170. package/native/watchos/src/scene.rs +534 -0
  171. package/native/watchos/src/textures.rs +184 -0
  172. package/native/web/Cargo.lock +1657 -0
  173. package/native/web/Cargo.toml +43 -0
  174. package/native/web/bloom_glue.js +695 -0
  175. package/native/web/build.sh +131 -0
  176. package/native/web/index.html +35 -0
  177. package/native/web/jolt_bridge.js +1519 -0
  178. package/native/web/src/input_ffi.rs +286 -0
  179. package/native/web/src/lib.rs +1796 -0
  180. package/native/web/src/material_ffi.rs +710 -0
  181. package/native/web/src/parity_ffi.rs +343 -0
  182. package/native/web/src/physics_ffi.rs +643 -0
  183. package/native/web/src/ragdoll_ffi.rs +250 -0
  184. package/native/web/src/render_settings.rs +98 -0
  185. package/native/windows/Cargo.lock +1815 -0
  186. package/native/windows/Cargo.toml +68 -0
  187. package/native/windows/src/lib.rs +1486 -0
  188. package/package.json +4279 -0
  189. package/src/audio/index.ts +315 -0
  190. package/src/core/colors.ts +63 -0
  191. package/src/core/index.ts +1206 -0
  192. package/src/core/keys.ts +63 -0
  193. package/src/core/types.ts +104 -0
  194. package/src/index.ts +171 -0
  195. package/src/math/index.ts +516 -0
  196. package/src/mobile/index.ts +294 -0
  197. package/src/models/index.ts +1258 -0
  198. package/src/physics/index.ts +1134 -0
  199. package/src/scene/index.ts +698 -0
  200. package/src/shapes/index.ts +120 -0
  201. package/src/text/index.ts +48 -0
  202. package/src/textures/index.ts +187 -0
  203. package/src/vfx/index.ts +191 -0
  204. package/src/world/index.ts +24 -0
  205. package/src/world/loader.ts +423 -0
  206. package/src/world/prefab.ts +217 -0
  207. package/src/world/render.ts +172 -0
  208. package/src/world/saver.ts +108 -0
  209. package/src/world/serialize.ts +301 -0
  210. package/src/world/terrain.ts +355 -0
  211. package/src/world/types.ts +160 -0
  212. package/src/world/validate.ts +319 -0
  213. package/src/world/version.ts +114 -0
@@ -0,0 +1,392 @@
1
+ //! Lumen-style screen-probe SSGI: probe placement, Hi-Z/HW/SDF trace,
2
+ //! temporal EMA, and octahedral resolve into ssgi_rt. Split from
3
+ //! end_frame_with_scene (2000-line file policy + render-graph migration
4
+ //! prep). When disabled, clears ssgi_rt to transparent.
5
+
6
+ use super::*;
7
+
8
+ impl Renderer {
9
+ pub(super) fn record_ssgi_passes(
10
+ &mut self,
11
+ encoder: &mut wgpu::CommandEncoder,
12
+ profiler: &mut crate::profiler::Profiler,
13
+ surf_w: u32,
14
+ surf_h: u32,
15
+ ) {
16
+ // ============================================================
17
+ // Ticket 007a: Lumen-style screen-probe SSGI.
18
+ // place → trace (SW Hi-Z) → temporal (EMA ping-pong) → resolve.
19
+ // Resolve writes `ssgi_rt_view` so downstream compositing is
20
+ // unchanged. When disabled we just clear `ssgi_rt_view` to
21
+ // transparent (same fallback shape as the old per-pixel path).
22
+ // ============================================================
23
+ let half_w = (surf_w / 2).max(1);
24
+ let half_h = (surf_h / 2).max(1);
25
+ let gw = self.probe_grid_w;
26
+ let gh = self.probe_grid_h;
27
+ let write_idx = self.probe_history_idx;
28
+ let prev_idx = 1 - write_idx;
29
+
30
+ // PT-1: while the path tracer owns the frame its output already
31
+ // contains full GI — probe SSGI would burn ~2ms to be composited
32
+ // over by nothing (the else-branch clear keeps compose additive-
33
+ // safe either way). pt_owns_frame, not pt_active: progressive mode
34
+ // shows raster frames until accumulation warms up, and those still
35
+ // want SSGI.
36
+ if self.ssgi_enabled && !self.pt_owns_frame() {
37
+ let p00 = self.current_proj_matrix[0][0];
38
+ let p11 = self.current_proj_matrix[1][1];
39
+ let p20 = self.current_proj_matrix[2][0];
40
+ let p21 = self.current_proj_matrix[2][1];
41
+ let inv_view = mat4_invert(self.current_view_matrix);
42
+
43
+ // ---- place ----
44
+ let place_params = ProbePlaceParams {
45
+ inv_view,
46
+ proj_row01: [p00, p11, p20, p21],
47
+ size: [half_w, half_h, gw, gh],
48
+ params: [self.taa_frame_index as f32, PROBE_TILE_SIZE as f32, 0.0, 0.0],
49
+ };
50
+ self.queue.write_buffer(&self.probe_place_uniform, 0, bytemuck::bytes_of(&place_params));
51
+ if self.probe_place_bg_cache.is_none() {
52
+ self.probe_place_bg_cache = Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
53
+ label: Some("probe_place_bg"),
54
+ layout: &self.probe_place_layout,
55
+ entries: &[
56
+ wgpu::BindGroupEntry { binding: 0, resource: self.probe_place_uniform.as_entire_binding() },
57
+ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.hiz_views[0]) },
58
+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.hiz_sampler) },
59
+ wgpu::BindGroupEntry { binding: 3, resource: self.probe_header_buffer.as_entire_binding() },
60
+ ],
61
+ }));
62
+ }
63
+ {
64
+ let ts = profiler.compute_pass_timestamp_writes("probe_place_pass");
65
+ let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
66
+ label: Some("probe_place_pass"), timestamp_writes: ts,
67
+ });
68
+ pass.set_pipeline(&self.probe_place_pipeline);
69
+ pass.set_bind_group(0, self.probe_place_bg_cache.as_ref().unwrap(), &[]);
70
+ pass.dispatch_workgroups((gw + 7) / 8, (gh + 7) / 8, 1);
71
+ }
72
+
73
+ // ---- trace ----
74
+ // Sun direction in world space — inverted because our
75
+ // `light_dir` points from light toward the scene, while the
76
+ // shader's NdotL expects the vector from the shading point
77
+ // toward the light. Normalised because the shader doesn't.
78
+ let ld = self.lighting_uniforms.light_dir;
79
+ let sun_inv_len = 1.0 / (ld[0]*ld[0] + ld[1]*ld[1] + ld[2]*ld[2]).sqrt().max(1e-4);
80
+ let sun_dir_ws = [
81
+ -ld[0] * sun_inv_len,
82
+ -ld[1] * sun_inv_len,
83
+ -ld[2] * sun_inv_len,
84
+ ld[3],
85
+ ];
86
+ // Sun colour = light_color × light intensity (ld.w). Sky
87
+ // colour = ambient × ambient intensity (ambient.w) — a
88
+ // crude dome irradiance, good enough for a one-bounce
89
+ // shading estimate. Both fields are ignored by the SW
90
+ // shader which inherits the same uniform struct layout.
91
+ let lc = self.lighting_uniforms.light_color;
92
+ let sun_intensity = ld[3].max(0.0);
93
+ let sun_color = [
94
+ lc[0] * sun_intensity,
95
+ lc[1] * sun_intensity,
96
+ lc[2] * sun_intensity,
97
+ 0.0,
98
+ ];
99
+ let amb = self.lighting_uniforms.ambient;
100
+ let sky_intensity = amb[3].max(0.0);
101
+ let sky_color = [
102
+ amb[0] * sky_intensity,
103
+ amb[1] * sky_intensity,
104
+ amb[2] * sky_intensity,
105
+ 0.0,
106
+ ];
107
+ let trace_params = ProbeTraceParams {
108
+ view: self.current_view_matrix,
109
+ proj: self.current_proj_matrix,
110
+ inv_view,
111
+ proj_row01: [p00, p11, p20, p21],
112
+ size: [half_w, half_h, gw, gh],
113
+ params: [
114
+ self.taa_frame_index as f32,
115
+ self.ssgi_intensity,
116
+ self.ssgi_radius,
117
+ 10.0, // firefly luma cap
118
+ ],
119
+ sun_dir: sun_dir_ws,
120
+ sun_color,
121
+ sky_color,
122
+ // Ticket 014 V3 — clipmap origin xyz + full extent w.
123
+ // The SDF trace variant reads these; HW + Hi-Z ignore.
124
+ clipmap: [
125
+ self.scene_sdf_clipmap_origin[0],
126
+ self.scene_sdf_clipmap_origin[1],
127
+ self.scene_sdf_clipmap_origin[2],
128
+ SCENE_SDF_CLIPMAP_EXTENT,
129
+ ],
130
+ // Ticket 014 V6/V13 — WSRC cascade cubes. `extent =
131
+ // 0` marks an unbaked cascade; the shader's
132
+ // `pick_cascade` helper skips those and falls through
133
+ // to the next cascade (or returns black if none are
134
+ // ready). First frame after startup all three are
135
+ // unbaked → miss returns black, matching pre-V6.
136
+ wsrc_cascades: [
137
+ [
138
+ self.wsrc_origin[0][0],
139
+ self.wsrc_origin[0][1],
140
+ self.wsrc_origin[0][2],
141
+ if self.wsrc_built[0] { WSRC_CASCADE_EXTENTS[0] } else { 0.0 },
142
+ ],
143
+ [
144
+ self.wsrc_origin[1][0],
145
+ self.wsrc_origin[1][1],
146
+ self.wsrc_origin[1][2],
147
+ if self.wsrc_built[1] { WSRC_CASCADE_EXTENTS[1] } else { 0.0 },
148
+ ],
149
+ [
150
+ self.wsrc_origin[2][0],
151
+ self.wsrc_origin[2][1],
152
+ self.wsrc_origin[2][2],
153
+ if self.wsrc_built[2] { WSRC_CASCADE_EXTENTS[2] } else { 0.0 },
154
+ ],
155
+ ],
156
+ };
157
+ self.queue.write_buffer(&self.probe_trace_uniform, 0, bytemuck::bytes_of(&trace_params));
158
+ // V3 — trace BG now binds the prev-frame history view at
159
+ // binding 11. `prev_idx` ping-pongs every frame so we
160
+ // cache both slots independently.
161
+ if self.probe_trace_bg_cache[prev_idx].is_none() {
162
+ self.probe_trace_bg_cache[prev_idx] = Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
163
+ label: Some("probe_trace_bg"),
164
+ layout: &self.probe_trace_layout,
165
+ entries: &[
166
+ wgpu::BindGroupEntry { binding: 0, resource: self.probe_trace_uniform.as_entire_binding() },
167
+ wgpu::BindGroupEntry { binding: 1, resource: self.probe_header_buffer.as_entire_binding() },
168
+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self.hiz_views[0]) },
169
+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.hiz_views[1]) },
170
+ wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self.hiz_views[2]) },
171
+ wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&self.hiz_views[3]) },
172
+ wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&self.hiz_views[4]) },
173
+ wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::Sampler(&self.hiz_sampler) },
174
+ wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::TextureView(&self.hdr_rt_view) },
175
+ wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
176
+ wgpu::BindGroupEntry { binding: 10, resource: wgpu::BindingResource::TextureView(&self.probe_trace_view) },
177
+ wgpu::BindGroupEntry { binding: 11, resource: wgpu::BindingResource::TextureView(&self.probe_history_views[prev_idx]) },
178
+ ],
179
+ }));
180
+ }
181
+ // HW trace needs both the TLAS (at least one instance) and
182
+ // the instance-data buffer to exist. Fall back to SDF or
183
+ // Hi-Z when either is missing on an HW-enabled adapter
184
+ // (e.g. first frame before the scene has loaded any
185
+ // geometry).
186
+ let use_hw = self.hw_rt_enabled
187
+ && self.probe_trace_hw_pipeline.is_some()
188
+ && self.tlas.is_some()
189
+ && self.tlas_instance_data_buffer.is_some();
190
+ // Ticket 014 V3/V4 — pick SDF sphere-trace over Hi-Z when
191
+ // the scene clipmap is baked AND the instance-data buffer
192
+ // is ready (needed for broad-phase textured hit sampling
193
+ // added in V4). Otherwise fall through to Hi-Z. HW still
194
+ // wins over both when the feature was granted.
195
+ let use_sdf = !use_hw
196
+ && self.scene_sdf_clipmap_built
197
+ && self.tlas_instance_data_buffer.is_some();
198
+ // Log the backend once (and again if it changes, e.g. clipmap
199
+ // finishing its first bake promotes hiz → sdf). Nothing else in
200
+ // the engine reveals which tier actually runs.
201
+ let backend = if use_hw { "hw-ray-query" } else if use_sdf { "sdf-clipmap" } else { "hiz-screen" };
202
+ if self.ssgi_backend_logged != Some(backend) {
203
+ self.ssgi_backend_logged = Some(backend);
204
+ eprintln!("bloom: ssgi trace backend = {}", backend);
205
+ }
206
+
207
+ if use_hw {
208
+ // Build the HW bind group lazily. V3 uses a per-
209
+ // prev_idx slot since the prev-frame history view
210
+ // ping-pongs each frame.
211
+ if self.probe_trace_hw_bg_cache[prev_idx].is_none() {
212
+ let tlas = self.tlas.as_ref().unwrap();
213
+ self.probe_trace_hw_bg_cache[prev_idx] = Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
214
+ label: Some("probe_trace_hw_bg"),
215
+ layout: self.probe_trace_hw_layout.as_ref().unwrap(),
216
+ entries: &[
217
+ wgpu::BindGroupEntry { binding: 0, resource: self.probe_trace_uniform.as_entire_binding() },
218
+ wgpu::BindGroupEntry { binding: 1, resource: self.probe_header_buffer.as_entire_binding() },
219
+ wgpu::BindGroupEntry { binding: 2, resource: tlas.as_binding() },
220
+ wgpu::BindGroupEntry { binding: 3, resource: self.tlas_instance_data_buffer.as_ref().unwrap().as_entire_binding() },
221
+ wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self.probe_trace_view) },
222
+ // Ticket 013 V2: the HW trace samples the
223
+ // *radiance* atlas (pre-lit by card_light_pass)
224
+ // at hit, not the raw albedo atlas.
225
+ wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&self.mesh_card_radiance_view) },
226
+ wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::Sampler(&self.mesh_card_atlas_sampler) },
227
+ // V7/V10 — WSRC atlas + linear sampler.
228
+ wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::TextureView(&self.wsrc_atlas_view) },
229
+ wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::Sampler(&self.wsrc_atlas_sampler) },
230
+ // V3 — prev-frame probe history.
231
+ wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::TextureView(&self.probe_history_views[prev_idx]) },
232
+ ],
233
+ }));
234
+ }
235
+ let ts = profiler.compute_pass_timestamp_writes("probe_trace_hw_pass");
236
+ let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
237
+ label: Some("probe_trace_hw_pass"), timestamp_writes: ts,
238
+ });
239
+ pass.set_pipeline(self.probe_trace_hw_pipeline.as_ref().unwrap());
240
+ pass.set_bind_group(0, self.probe_trace_hw_bg_cache[prev_idx].as_ref().unwrap(), &[]);
241
+ pass.dispatch_workgroups(gw, gh, 1);
242
+ } else if use_sdf {
243
+ // Ticket 014 V3 — SW SDF sphere-trace path.
244
+ // V3 (ticket 016) uses a per-prev_idx slot for the
245
+ // prev-frame history binding.
246
+ if self.probe_trace_sdf_bg_cache[prev_idx].is_none() {
247
+ let nf_samp = self.device.create_sampler(&wgpu::SamplerDescriptor {
248
+ label: Some("clipmap_nonfiltering_sampler"),
249
+ address_mode_u: wgpu::AddressMode::ClampToEdge,
250
+ address_mode_v: wgpu::AddressMode::ClampToEdge,
251
+ address_mode_w: wgpu::AddressMode::ClampToEdge,
252
+ mag_filter: wgpu::FilterMode::Nearest,
253
+ min_filter: wgpu::FilterMode::Nearest,
254
+ mipmap_filter: wgpu::MipmapFilterMode::Nearest,
255
+ ..Default::default()
256
+ });
257
+ let instance_buf = self.tlas_instance_data_buffer.as_ref()
258
+ .expect("V4: instance_data buffer must exist before SDF dispatch");
259
+ self.probe_trace_sdf_bg_cache[prev_idx] = Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
260
+ label: Some("probe_trace_sdf_bg"),
261
+ layout: &self.probe_trace_sdf_layout,
262
+ entries: &[
263
+ wgpu::BindGroupEntry { binding: 0, resource: self.probe_trace_uniform.as_entire_binding() },
264
+ wgpu::BindGroupEntry { binding: 1, resource: self.probe_header_buffer.as_entire_binding() },
265
+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self.scene_sdf_clipmap_view) },
266
+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&nf_samp) },
267
+ wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self.probe_trace_view) },
268
+ wgpu::BindGroupEntry { binding: 5, resource: instance_buf.as_entire_binding() },
269
+ wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&self.mesh_card_radiance_view) },
270
+ wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::Sampler(&self.mesh_card_atlas_sampler) },
271
+ // V6/V10 — WSRC atlas + linear sampler.
272
+ wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::TextureView(&self.wsrc_atlas_view) },
273
+ wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::Sampler(&self.wsrc_atlas_sampler) },
274
+ // V3 — prev-frame probe history.
275
+ wgpu::BindGroupEntry { binding: 10, resource: wgpu::BindingResource::TextureView(&self.probe_history_views[prev_idx]) },
276
+ ],
277
+ }));
278
+ }
279
+ let ts = profiler.compute_pass_timestamp_writes("probe_trace_sdf_pass");
280
+ let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
281
+ label: Some("probe_trace_sdf_pass"), timestamp_writes: ts,
282
+ });
283
+ pass.set_pipeline(&self.probe_trace_sdf_pipeline);
284
+ pass.set_bind_group(0, self.probe_trace_sdf_bg_cache[prev_idx].as_ref().unwrap(), &[]);
285
+ pass.dispatch_workgroups(gw, gh, 1);
286
+ } else {
287
+ let ts = profiler.compute_pass_timestamp_writes("probe_trace_pass");
288
+ let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
289
+ label: Some("probe_trace_pass"), timestamp_writes: ts,
290
+ });
291
+ pass.set_pipeline(&self.probe_trace_pipeline);
292
+ pass.set_bind_group(0, self.probe_trace_bg_cache[prev_idx].as_ref().unwrap(), &[]);
293
+ pass.dispatch_workgroups(gw, gh, 1);
294
+ }
295
+
296
+ // ---- temporal (EMA) ----
297
+ // First frame forces alpha=1 so the history is seeded from
298
+ // the current trace rather than blending against a zero clear.
299
+ let force_refresh = if self.taa_frame_index == 0 { 1.0_f32 } else { 0.0_f32 };
300
+ let temporal_params = ProbeTemporalParams {
301
+ params: [0.25, force_refresh, gw as f32, gh as f32],
302
+ };
303
+ self.queue.write_buffer(&self.probe_temporal_uniform, 0, bytemuck::bytes_of(&temporal_params));
304
+ // Bind group indexed by write_idx: each direction of the
305
+ // ping-pong (read prev, write write) gets its own cached BG.
306
+ if self.probe_temporal_bg_cache[write_idx].is_none() {
307
+ self.probe_temporal_bg_cache[write_idx] = Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
308
+ label: Some("probe_temporal_bg"),
309
+ layout: &self.probe_temporal_layout,
310
+ entries: &[
311
+ wgpu::BindGroupEntry { binding: 0, resource: self.probe_temporal_uniform.as_entire_binding() },
312
+ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.probe_trace_view) },
313
+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self.probe_history_views[prev_idx]) },
314
+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.probe_history_views[write_idx]) },
315
+ ],
316
+ }));
317
+ }
318
+ {
319
+ let ts = profiler.compute_pass_timestamp_writes("probe_temporal_pass");
320
+ let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
321
+ label: Some("probe_temporal_pass"), timestamp_writes: ts,
322
+ });
323
+ pass.set_pipeline(&self.probe_temporal_pipeline);
324
+ pass.set_bind_group(0, self.probe_temporal_bg_cache[write_idx].as_ref().unwrap(), &[]);
325
+ pass.dispatch_workgroups(gw, gh, 1);
326
+ }
327
+
328
+ // ---- resolve ----
329
+ let resolve_params = ProbeResolveParams {
330
+ inv_view,
331
+ proj_row01: [p00, p11, p20, p21],
332
+ size: [half_w, half_h, gw, gh],
333
+ params: [PROBE_TILE_SIZE as f32, 1.0, 0.0, 0.0],
334
+ };
335
+ self.queue.write_buffer(&self.probe_resolve_uniform, 0, bytemuck::bytes_of(&resolve_params));
336
+ if self.probe_resolve_bg_cache[write_idx].is_none() {
337
+ self.probe_resolve_bg_cache[write_idx] = Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
338
+ label: Some("probe_resolve_bg"),
339
+ layout: &self.probe_resolve_layout,
340
+ entries: &[
341
+ wgpu::BindGroupEntry { binding: 0, resource: self.probe_resolve_uniform.as_entire_binding() },
342
+ wgpu::BindGroupEntry { binding: 1, resource: self.probe_header_buffer.as_entire_binding() },
343
+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self.probe_history_views[write_idx]) },
344
+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
345
+ wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self.hiz_views[0]) },
346
+ wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::Sampler(&self.hiz_sampler) },
347
+ ],
348
+ }));
349
+ }
350
+ let ts = profiler.pass_timestamp_writes("probe_resolve_pass");
351
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
352
+ label: Some("probe_resolve_pass"),
353
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
354
+ view: &self.ssgi_rt_view,
355
+ resolve_target: None,
356
+ depth_slice: None,
357
+ ops: wgpu::Operations {
358
+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
359
+ store: wgpu::StoreOp::Store,
360
+ },
361
+ })],
362
+ depth_stencil_attachment: None,
363
+ timestamp_writes: ts,
364
+ occlusion_query_set: None,
365
+ multiview_mask: None,
366
+ });
367
+ pass.set_pipeline(&self.probe_resolve_pipeline);
368
+ pass.set_bind_group(0, self.probe_resolve_bg_cache[write_idx].as_ref().unwrap(), &[]);
369
+ pass.draw(0..3, 0..1);
370
+ } else {
371
+ // SSGI disabled — clear the resolve target so downstream
372
+ // composite reads contribute zero.
373
+ let pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
374
+ label: Some("ssgi_clear"),
375
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
376
+ view: &self.ssgi_rt_view,
377
+ resolve_target: None,
378
+ depth_slice: None,
379
+ ops: wgpu::Operations {
380
+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
381
+ store: wgpu::StoreOp::Store,
382
+ },
383
+ })],
384
+ depth_stencil_attachment: None,
385
+ timestamp_writes: None,
386
+ occlusion_query_set: None,
387
+ multiview_mask: None,
388
+ });
389
+ drop(pass);
390
+ }
391
+ }
392
+ }
@@ -0,0 +1,188 @@
1
+ //! Screen-space reflections: stochastic ray march + temporal denoiser.
2
+ //! Split from end_frame_with_scene (2000-line file policy + render-graph
3
+ //! migration prep). Both entry points no-op when `ssr_enabled` is false.
4
+
5
+ use super::{Renderer, SsrParams, SsrTemporalParams};
6
+
7
+ impl Renderer {
8
+ /// Quarter-res stochastic SSR ray march (GGX-sampled directions,
9
+ /// jittered starts; temporal accumulation makes it converge).
10
+ pub(super) fn record_ssr_march(
11
+ &mut self,
12
+ encoder: &mut wgpu::CommandEncoder,
13
+ profiler: &mut crate::profiler::Profiler,
14
+ ) {
15
+ // ============================================================
16
+ // SSR: view-space ray march of the depth buffer + HDR sample.
17
+ // ============================================================
18
+ // PT-1: skipped while the path tracer owns the frame — it marches
19
+ // the raster-lit HDR, which PT has already overwritten.
20
+ if self.ssr_enabled && !self.pt_owns_frame() {
21
+ let inv_proj = self.current_inv_proj_matrix;
22
+ // EN-021 — view→world rotation for the env-miss fallback: the
23
+ // transpose of the view matrix's 3×3 (rigid view ⇒ inverse
24
+ // rotation = transpose). Column j of the inverse is row j of
25
+ // the view rotation.
26
+ let v = self.current_view_matrix;
27
+ let inv_view_rot = [
28
+ [v[0][0], v[1][0], v[2][0], 0.0],
29
+ [v[0][1], v[1][1], v[2][1], 0.0],
30
+ [v[0][2], v[1][2], v[2][2], 0.0],
31
+ [0.0, 0.0, 0.0, 1.0],
32
+ ];
33
+ let sp = SsrParams {
34
+ inv_proj,
35
+ proj: self.current_proj_matrix,
36
+ // n_steps lowered from 32 → 8 for stochastic SSR: the
37
+ // GGX-sampled ray direction + jittered start offset +
38
+ // temporal accumulation over 4–8 frames fills in the
39
+ // gaps that any single-frame coarse march leaves behind.
40
+ // Thickness tolerance grows proportionally with
41
+ // step_size so the relative-error reject heuristic
42
+ // still works with the larger strides.
43
+ params: [self.ssr_strength, 8.0, 8.0, self.taa_frame_index as f32],
44
+ inv_view_rot,
45
+ // Env max LOD 6.0 matches the material path's roughness×6
46
+ // mip ramp; intensity rides lighting camera_pos.w exactly
47
+ // like sample_env does.
48
+ params2: [6.0, self.lighting_uniforms.camera_pos[3], 0.0, 0.0],
49
+ };
50
+ self.queue.write_buffer(&self.ssr_uniform_buffer, 0, bytemuck::bytes_of(&sp));
51
+
52
+ if self.ssr_bg_cache.is_none() {
53
+ // EN-021 — env panorama (or the 1×1 default) for the miss
54
+ // fallback. The cache is invalidated wherever the lighting
55
+ // bind group swaps env sources.
56
+ let env_view = self
57
+ .sky_texture
58
+ .as_ref()
59
+ .map(|t| t.create_view(&wgpu::TextureViewDescriptor::default()))
60
+ .unwrap_or_else(|| {
61
+ self._scene_env_default_texture
62
+ .create_view(&wgpu::TextureViewDescriptor::default())
63
+ });
64
+ self.ssr_bg_cache = Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
65
+ label: Some("ssr_bg"),
66
+ layout: &self.ssr_layout,
67
+ entries: &[
68
+ wgpu::BindGroupEntry { binding: 0, resource: self.ssr_uniform_buffer.as_entire_binding() },
69
+ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.depth_view) },
70
+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.ssao_depth_sampler) },
71
+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.hdr_rt_view) },
72
+ wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
73
+ wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&self.material_rt_view) },
74
+ wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
75
+ wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::TextureView(&self.albedo_rt_view) },
76
+ wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
77
+ wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::TextureView(&env_view) },
78
+ wgpu::BindGroupEntry { binding: 10, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
79
+ ],
80
+ }));
81
+ }
82
+ let bg = self.ssr_bg_cache.as_ref().unwrap();
83
+ let ssr_ts = profiler.pass_timestamp_writes("ssr_pass");
84
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
85
+ label: Some("ssr_pass"),
86
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
87
+ view: &self.ssr_rt_view,
88
+ resolve_target: None,
89
+ depth_slice: None,
90
+ ops: wgpu::Operations {
91
+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
92
+ store: wgpu::StoreOp::Store,
93
+ },
94
+ })],
95
+ depth_stencil_attachment: None,
96
+ timestamp_writes: ssr_ts,
97
+ occlusion_query_set: None,
98
+ multiview_mask: None,
99
+ });
100
+ pass.set_pipeline(&self.ssr_pipeline);
101
+ pass.set_bind_group(0, bg, &[]);
102
+ pass.draw(0..3, 0..1);
103
+ } else {
104
+ // SSR disabled — clear the RT so TAA's read returns 0
105
+ // (transparent black). One-time clear is cheaper than a
106
+ // full clear+pipeline switch every frame.
107
+ let pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
108
+ label: Some("ssr_clear"),
109
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
110
+ view: &self.ssr_rt_view,
111
+ resolve_target: None,
112
+ depth_slice: None,
113
+ ops: wgpu::Operations {
114
+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
115
+ store: wgpu::StoreOp::Store,
116
+ },
117
+ })],
118
+ depth_stencil_attachment: None,
119
+ timestamp_writes: None,
120
+ occlusion_query_set: None,
121
+ multiview_mask: None,
122
+ });
123
+ drop(pass);
124
+ }
125
+ }
126
+
127
+ /// SSR temporal denoiser: 3x3 pre-filter + neighborhood-clamped
128
+ /// history blend; compose reads ssr_history[cur].
129
+ pub(super) fn record_ssr_temporal(
130
+ &mut self,
131
+ encoder: &mut wgpu::CommandEncoder,
132
+ ) {
133
+ // ============================================================
134
+ // SSR temporal denoiser: blend the noisy single-ray SSR with
135
+ // the reprojected previous history so 4–8 frames of GGX-sampled
136
+ // rays converge to a smooth reflection. 3×3 pre-filter of the
137
+ // noisy current frame + neighborhood clamp of reprojected
138
+ // history. Compose then reads ssr_history[cur] instead of
139
+ // ssr_rt.
140
+ // ============================================================
141
+ // PT-1: same gate as the march — no fresh rays, nothing to blend.
142
+ if self.ssr_enabled && !self.pt_owns_frame() {
143
+ let prev_idx = 1 - self.ssr_history_idx;
144
+ let cur_idx = self.ssr_history_idx;
145
+
146
+ // First frame: alpha=1 so we initialize history from the
147
+ // current noisy frame rather than blending with zeros.
148
+ let alpha = if self.taa_frame_index == 0 { 1.0_f32 } else { 0.1_f32 };
149
+ let tp = SsrTemporalParams {
150
+ params: [alpha, 0.0, 0.0, 0.0],
151
+ };
152
+ self.queue.write_buffer(&self.ssr_temporal_uniform_buffer, 0, bytemuck::bytes_of(&tp));
153
+
154
+ let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
155
+ label: Some("ssr_temporal_bg"),
156
+ layout: &self.ssr_temporal_layout,
157
+ entries: &[
158
+ wgpu::BindGroupEntry { binding: 0, resource: self.ssr_temporal_uniform_buffer.as_entire_binding() },
159
+ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.ssr_rt_view) },
160
+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
161
+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.ssr_history_views[prev_idx]) },
162
+ wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
163
+ wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&self.velocity_rt_view) },
164
+ wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::Sampler(&self.composite_sampler) },
165
+ ],
166
+ });
167
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
168
+ label: Some("ssr_temporal_pass"),
169
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
170
+ view: &self.ssr_history_views[cur_idx],
171
+ resolve_target: None,
172
+ depth_slice: None,
173
+ ops: wgpu::Operations {
174
+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
175
+ store: wgpu::StoreOp::Store,
176
+ },
177
+ })],
178
+ depth_stencil_attachment: None,
179
+ timestamp_writes: None,
180
+ occlusion_query_set: None,
181
+ multiview_mask: None,
182
+ });
183
+ pass.set_pipeline(&self.ssr_temporal_pipeline);
184
+ pass.set_bind_group(0, &bg, &[]);
185
+ pass.draw(0..3, 0..1);
186
+ }
187
+ }
188
+ }