@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,941 @@
1
+ //! Uniform + vertex types used throughout the renderer.
2
+ //!
3
+ //! Pure POD data (`bytemuck::Pod`-derived structs) — no Renderer state,
4
+ //! no wgpu resource ownership, no behavior beyond tiny constructors /
5
+ //! VertexBufferLayout descriptors. Split out of the 11 500-line
6
+ //! renderer monolith so the wiring and the data types are separable.
7
+ //!
8
+ //! `pub` items that external modules import (`Vertex3D`,
9
+ //! `SceneMaterialUniforms`) are re-exported from `renderer/mod.rs`
10
+ //! with `pub use types::*;` so their public paths
11
+ //! (`crate::renderer::Vertex3D`, etc.) stay stable.
12
+
13
+ use crate::renderer::IDENTITY_MAT4;
14
+
15
+ // ============================================================
16
+ // Constants
17
+ // ============================================================
18
+
19
+ pub(super) const MAX_UNIFORM_SLOTS: usize = 8;
20
+
21
+ // ============================================================
22
+ // Vertex and Uniform types
23
+ // ============================================================
24
+
25
+ #[repr(C)]
26
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
27
+ pub(super) struct Uniforms2D {
28
+ pub(super) screen_size: [f32; 2],
29
+ pub(super) _pad: [f32; 2],
30
+ pub(super) view_proj: [[f32; 4]; 4],
31
+ }
32
+
33
+ #[repr(C)]
34
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
35
+ pub(super) struct Uniforms3D {
36
+ pub(super) mvp: [[f32; 4]; 4],
37
+ pub(super) model: [[f32; 4]; 4],
38
+ pub(super) prev_mvp: [[f32; 4]; 4],
39
+ pub(super) model_tint: [f32; 4],
40
+ /// x = joint-buffer base offset for this draw (added to vertex joint
41
+ /// indices by the scene VS), y = 1.0 for skinned cached draws else
42
+ /// 0.0, zw unused. Lets GPU-resident skinned models share the static
43
+ /// cached-model path: the VB keeps RAW joint indices and the per-draw
44
+ /// uniform carries the frame's pose offset instead.
45
+ pub(super) misc: [f32; 4],
46
+ }
47
+
48
+ /// Scene-pipeline per-material factors — the scalar parts of a glTF
49
+ /// PBR material that get multiplied onto the corresponding texture
50
+ /// samples. Sized to a multiple of 16 bytes for UBO alignment.
51
+ #[repr(C)]
52
+ #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
53
+ pub struct SceneMaterialUniforms {
54
+ /// x = metallic_factor, y = roughness_factor,
55
+ /// z = has_mr_texture (1.0 = sample mr_tex and multiply, 0.0 = ignore
56
+ /// mr_tex and use factors directly),
57
+ /// w = alpha_cutoff (0.0 = OPAQUE mode, >0 = MASK/BLEND — fragments
58
+ /// whose base-colour alpha is below this are discarded).
59
+ pub metal_rough: [f32; 4],
60
+ /// rgb = emissive_factor, w = padding
61
+ pub emissive: [f32; 4],
62
+ }
63
+
64
+ impl SceneMaterialUniforms {
65
+ pub fn new(
66
+ metallic: f32,
67
+ roughness: f32,
68
+ emissive: [f32; 3],
69
+ has_mr_texture: bool,
70
+ alpha_cutoff: f32,
71
+ ) -> Self {
72
+ Self {
73
+ metal_rough: [
74
+ metallic,
75
+ roughness,
76
+ if has_mr_texture { 1.0 } else { 0.0 },
77
+ alpha_cutoff,
78
+ ],
79
+ emissive: [emissive[0], emissive[1], emissive[2], 0.0],
80
+ }
81
+ }
82
+ }
83
+
84
+ // Raised from 4/16: scenes were hard-capped at 16 point lights, the
85
+ // audit's top graphics blocker. Arrays stay in a uniform buffer so the
86
+ // cap raise works on every backend including WebGL2 (whose 16KB minimum
87
+ // UBO size this still fits: 256*32B + 8*32B + header < 9KB). Shaders
88
+ // loop only over the live count, so small scenes pay nothing. Per-pixel
89
+ // cost for genuinely large light counts is the follow-up (froxel
90
+ // clustering); this change removes the capability ceiling.
91
+ pub(crate) const MAX_DIR_LIGHTS: usize = 8;
92
+ pub(crate) const MAX_POINT_LIGHTS: usize = 256;
93
+
94
+ #[repr(C)]
95
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
96
+ pub(super) struct DirLight {
97
+ pub(super) direction: [f32; 4], // xyz + intensity
98
+ pub(super) color: [f32; 4], // rgb + _pad
99
+ }
100
+
101
+ #[repr(C)]
102
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
103
+ pub(super) struct PointLight {
104
+ pub(super) position: [f32; 4], // xyz + range
105
+ pub(super) color: [f32; 4], // rgb + intensity
106
+ }
107
+
108
+ #[repr(C)]
109
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
110
+ pub(super) struct LightingUniforms {
111
+ pub(super) ambient: [f32; 4], // rgb + intensity
112
+ pub(super) light_dir: [f32; 4], // xyz + intensity (legacy, kept for compat)
113
+ pub(super) light_color: [f32; 4], // rgb + _pad (legacy)
114
+ pub(super) dir_light_count: [f32; 4], // [count, 0, 0, 0]
115
+ pub(super) dir_lights: [DirLight; MAX_DIR_LIGHTS], // additional directional lights
116
+ pub(super) point_light_count: [f32; 4], // [count, 0, 0, 0]
117
+ pub(super) point_lights: [PointLight; MAX_POINT_LIGHTS], // point lights
118
+ /// Camera world-space position (xyz) + env intensity multiplier
119
+ /// (w). Scene shader uses xyz to compute V = normalize(camera_pos
120
+ /// - world_pos) for GGX specular, and multiplies w into every env
121
+ /// sample so IBL stays in sync with the sky pass when the user
122
+ /// scales their HDR. Written once per frame before the main pass.
123
+ pub(super) camera_pos: [f32; 4],
124
+ /// Cascaded shadow map: 3 light view-projection matrices (one per
125
+ /// cascade). Scene shader selects the tightest cascade based on
126
+ /// the fragment's view-space depth and projects through the
127
+ /// corresponding matrix for shadow-map UV.
128
+ pub(super) shadow_cascade_vps: [[[f32; 4]; 4]; 3],
129
+ /// View-space Z split distances for cascade selection (xyz = split
130
+ /// distances for cascades 0/1/2, w = unused). Fragment at depth z
131
+ /// uses cascade i where z <= cascade_splits[i].
132
+ pub(super) shadow_cascade_splits: [f32; 4],
133
+ /// Camera view matrix — passed to the shader so the fragment shader
134
+ /// can compute view-space Z for cascade selection without an extra
135
+ /// buffer binding.
136
+ pub(super) shadow_view_matrix: [[f32; 4]; 4],
137
+ /// Wind for foliage sway in the built-in scene vertex shader:
138
+ /// xy = wind direction in the XZ plane (magnitude scales sway),
139
+ /// z = amplitude, w = elapsed time (seconds) for the sway phase.
140
+ /// Appended last so existing field offsets stay stable.
141
+ pub(super) wind: [f32; 4],
142
+ /// Cloud deck for the built-in scene shader: x = shadow strength,
143
+ /// y = deck height (m), z = feature scale, w = drift speed (m/s).
144
+ /// Strength 0 = the scene ignores the clouds. Appended last so existing
145
+ /// field offsets stay stable.
146
+ pub(super) cloud: [f32; 4],
147
+ /// x = delta_time (seconds). The scene VS needs LAST frame's wind offset to
148
+ /// emit a correct motion vector for swaying foliage — without it TAA sees
149
+ /// velocity 0 on every moving leaf and ghosts them. Appended last so existing
150
+ /// field offsets stay stable.
151
+ pub(super) frame_misc: [f32; 4],
152
+ }
153
+
154
+ impl LightingUniforms {
155
+ pub(super) fn defaults() -> Self {
156
+ Self {
157
+ ambient: [1.0, 1.0, 1.0, 0.3],
158
+ light_dir: [0.5, 1.0, 0.3, 0.7],
159
+ light_color: [1.0, 1.0, 1.0, 0.0],
160
+ dir_light_count: [0.0; 4],
161
+ dir_lights: [DirLight { direction: [0.0; 4], color: [0.0; 4] }; MAX_DIR_LIGHTS],
162
+ point_light_count: [0.0; 4],
163
+ point_lights: [PointLight { position: [0.0; 4], color: [0.0; 4] }; MAX_POINT_LIGHTS],
164
+ // w = env_intensity multiplier for IBL + sky. 1.0 matches
165
+ // the path-traced reference; apps with bright HDR envs
166
+ // typically dial to 0.2–0.5 via set_env_intensity.
167
+ camera_pos: [0.0, 0.0, 0.0, 1.0],
168
+ shadow_cascade_vps: [IDENTITY_MAT4; 3],
169
+ shadow_cascade_splits: [8.0, 25.0, 80.0, 0.0],
170
+ shadow_view_matrix: IDENTITY_MAT4,
171
+ wind: [0.0, 0.0, 0.0, 0.0],
172
+ cloud: [0.0, 420.0, 0.0035, 8.0],
173
+ frame_misc: [0.0; 4],
174
+ }
175
+ }
176
+ }
177
+
178
+ #[repr(C)]
179
+ #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
180
+ pub struct Vertex2D {
181
+ pub position: [f32; 2],
182
+ pub uv: [f32; 2],
183
+ pub color: [f32; 4],
184
+ }
185
+
186
+ impl Vertex2D {
187
+ pub fn desc() -> wgpu::VertexBufferLayout<'static> {
188
+ wgpu::VertexBufferLayout {
189
+ array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
190
+ step_mode: wgpu::VertexStepMode::Vertex,
191
+ attributes: &[
192
+ wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float32x2 },
193
+ wgpu::VertexAttribute { offset: 8, shader_location: 1, format: wgpu::VertexFormat::Float32x2 },
194
+ wgpu::VertexAttribute { offset: 16, shader_location: 2, format: wgpu::VertexFormat::Float32x4 },
195
+ ],
196
+ }
197
+ }
198
+ }
199
+
200
+ #[repr(C)]
201
+ #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
202
+ pub struct Vertex3D {
203
+ pub position: [f32; 3],
204
+ pub normal: [f32; 3],
205
+ pub color: [f32; 4],
206
+ pub uv: [f32; 2],
207
+ pub joints: [f32; 4], // bone indices (as floats for simplicity)
208
+ pub weights: [f32; 4], // bone weights (sum to 1.0, or all 0.0 for unskinned)
209
+ pub tangent: [f32; 4], // xyz = tangent direction, w = bitangent sign (±1). All zero = no tangent data; scene shader then skips normal mapping.
210
+ }
211
+
212
+ impl Default for Vertex3D {
213
+ fn default() -> Self {
214
+ bytemuck::Zeroable::zeroed()
215
+ }
216
+ }
217
+
218
+ impl Vertex3D {
219
+ pub fn desc() -> wgpu::VertexBufferLayout<'static> {
220
+ wgpu::VertexBufferLayout {
221
+ array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
222
+ step_mode: wgpu::VertexStepMode::Vertex,
223
+ attributes: &[
224
+ wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float32x3 }, // position
225
+ wgpu::VertexAttribute { offset: 12, shader_location: 1, format: wgpu::VertexFormat::Float32x3 }, // normal
226
+ wgpu::VertexAttribute { offset: 24, shader_location: 2, format: wgpu::VertexFormat::Float32x4 }, // color
227
+ wgpu::VertexAttribute { offset: 40, shader_location: 3, format: wgpu::VertexFormat::Float32x2 }, // uv
228
+ wgpu::VertexAttribute { offset: 48, shader_location: 4, format: wgpu::VertexFormat::Float32x4 }, // joints
229
+ wgpu::VertexAttribute { offset: 64, shader_location: 5, format: wgpu::VertexFormat::Float32x4 }, // weights
230
+ wgpu::VertexAttribute { offset: 80, shader_location: 6, format: wgpu::VertexFormat::Float32x4 }, // tangent
231
+ ],
232
+ }
233
+ }
234
+ }
235
+
236
+ /// Per-instance data for materials compiled with `wants_instancing = true`.
237
+ /// Bound at vertex buffer slot 1, step_mode = Instance. Layout is fixed
238
+ /// at engine V1; future extensions can parameterise from a material desc.
239
+ ///
240
+ /// Per-vertex attributes use shader_location 0..6. Per-instance
241
+ /// attributes start at shader_location 7. The TS-side flat layout is 9
242
+ /// floats per instance (pos.xyz, rot_y, scale, tint.rgba); the Rust
243
+ /// side pads each instance to 12 floats so the GPU stride matches the
244
+ /// 48-byte vec4-aligned layout.
245
+ #[repr(C)]
246
+ #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
247
+ pub struct InstanceData3D {
248
+ pub position: [f32; 3], // world-space position
249
+ pub rot_y: f32, // Y-axis rotation in radians
250
+ pub scale: f32, // uniform scale multiplier (1.0 = no scale)
251
+ pub tint: [f32; 4], // RGBA tint multiplier (1,1,1,1 = no tint)
252
+ /// EN-026 — was pure padding to the 16-byte boundary; now carried to the
253
+ /// shader as `@location(11) instance_extra: vec3<f32>`. The three floats
254
+ /// were already being uploaded, so exposing them costs nothing: no stride
255
+ /// change, no extra bandwidth. Particles use them for (atlas frame,
256
+ /// velocity-stretch length, random seed); anything else can leave them 0
257
+ /// and simply not declare location 11 — a vertex buffer may carry
258
+ /// attributes the shader does not consume.
259
+ pub extra: [f32; 3],
260
+ }
261
+
262
+ impl InstanceData3D {
263
+ pub fn desc() -> wgpu::VertexBufferLayout<'static> {
264
+ wgpu::VertexBufferLayout {
265
+ array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
266
+ step_mode: wgpu::VertexStepMode::Instance,
267
+ attributes: &[
268
+ wgpu::VertexAttribute { offset: 0, shader_location: 7, format: wgpu::VertexFormat::Float32x3 }, // position
269
+ wgpu::VertexAttribute { offset: 12, shader_location: 8, format: wgpu::VertexFormat::Float32 }, // rot_y
270
+ wgpu::VertexAttribute { offset: 16, shader_location: 9, format: wgpu::VertexFormat::Float32 }, // scale
271
+ wgpu::VertexAttribute { offset: 20, shader_location: 10, format: wgpu::VertexFormat::Float32x4 }, // tint
272
+ wgpu::VertexAttribute { offset: 36, shader_location: 11, format: wgpu::VertexFormat::Float32x3 }, // extra (EN-026)
273
+ ],
274
+ }
275
+ }
276
+ }
277
+
278
+ // ============================================================
279
+ // Draw call tracking
280
+ // ============================================================
281
+
282
+ pub(super) struct DrawCall2D {
283
+ pub(super) texture_idx: u32,
284
+ pub(super) uniform_idx: u32,
285
+ pub(super) index_start: u32,
286
+ }
287
+
288
+ pub(super) struct DrawCall3D {
289
+ pub(super) texture_idx: u32,
290
+ pub(super) index_start: u32,
291
+ /// First vertex of this segment in `vertices_3d` — lets the shadow
292
+ /// pass lazily scan primitive-only segments for bounds.
293
+ pub(super) vertex_start: u32,
294
+ /// World AABB of the segment's content. Non-skinned verts contribute
295
+ /// their (already world-space) positions; skinned model draws
296
+ /// contribute the union of their joint-transformed rest AABBs (a
297
+ /// rigorous conservative bound: a skinned vertex is a convex blend
298
+ /// of per-joint transforms of the same rest position).
299
+ /// `wmin[0] > wmax[0]` = not yet computed.
300
+ pub(super) wmin: [f32; 3],
301
+ pub(super) wmax: [f32; 3],
302
+ /// Segment contains skinned (animated) vertices → its rendered
303
+ /// output changes every frame, so shadow caching must treat it as
304
+ /// always-dirty for the cascades it touches.
305
+ pub(super) has_skinned: bool,
306
+ /// FNV-1a over the non-skinned vertex positions appended to this
307
+ /// segment — a cheap content identity so static immediate geometry
308
+ /// (e.g. pickups re-submitted identically every frame) doesn't
309
+ /// dirty the shadow cascades it sits in.
310
+ pub(super) content_hash: u64,
311
+ /// True when bounds/hash were maintained inline (model draws).
312
+ /// False → primitive-only segment, scanned on demand.
313
+ pub(super) bounded: bool,
314
+ }
315
+
316
+ pub(super) const FNV_OFFSET: u64 = 0xcbf29ce484222325;
317
+
318
+ #[inline]
319
+ pub(super) fn fnv1a_bytes(mut h: u64, bytes: &[u8]) -> u64 {
320
+ for &b in bytes {
321
+ h ^= b as u64;
322
+ h = h.wrapping_mul(0x100000001b3);
323
+ }
324
+ h
325
+ }
326
+
327
+ #[derive(PartialEq, Clone, Copy)]
328
+ pub enum RenderMode {
329
+ ScreenSpace,
330
+ Mode2D,
331
+ Mode3D,
332
+ }
333
+
334
+ // ============================================================
335
+ // Pass / compute uniform params (moved out of mod.rs, EN-052)
336
+ // ============================================================
337
+ // Pure POD uniform structs for the sky, GI, SSR, post, and PT
338
+ // passes plus the card-ortho helper. Fields are pub(super) so the
339
+ // renderer and its pass submodules can build them by literal.
340
+
341
+ #[repr(C)]
342
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
343
+ pub(super) struct PrefilterUniforms {
344
+ /// x = roughness (∈ [0, 1]), y = sample count, zw = mip resolution
345
+ pub(super) params: [f32; 4],
346
+ }
347
+
348
+ // ============================================================
349
+ // Sky / equirectangular HDR background shader
350
+ // ============================================================
351
+ //
352
+ // Renders a fullscreen triangle with z=1 (far plane) and samples the
353
+ // environment map by the world-space view direction reconstructed from
354
+ // inverse VP. Tone-maps with the same ACES curve the rest of the
355
+ // renderer uses so the background blends seamlessly with lit
356
+ // geometry. Always overwrites depth — the 3D opaque pass drawn after
357
+ // will occlude wherever it has actual geometry.
358
+
359
+ #[repr(C)]
360
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
361
+ pub(super) struct SkyUniforms {
362
+ /// Camera right vector × tan(fovy/2) × aspect — pre-scaled so the
363
+ /// fragment shader just multiplies by NDC.x to get the horizontal
364
+ /// offset from the forward direction.
365
+ /// `.w` = camera world position X (see below).
366
+ pub(super) right: [f32; 4],
367
+ /// Camera up vector × tan(fovy/2). `.w` = camera world position Y.
368
+ pub(super) up: [f32; 4],
369
+ /// Camera forward unit vector. `.w` = camera world position Z.
370
+ ///
371
+ /// The camera POSITION rides in the spare `.w` lanes of the three basis
372
+ /// vectors. The sky pass never needed it — a sky at infinity only cares
373
+ /// which way you are looking — but the cloud deck is anchored in world
374
+ /// space so that its shadow lands under it, and that makes the camera's
375
+ /// position load-bearing. Packing it here keeps the bind group unchanged.
376
+ pub(super) forward: [f32; 4],
377
+ /// x = intensity multiplier, y = elapsed seconds (the cloud deck's clock);
378
+ /// zw padding.
379
+ pub(super) intensity: [f32; 4],
380
+ /// Cloud deck: x = shadow strength, y = deck height (m), z = feature scale
381
+ /// (noise units per metre), w = drift speed (m/s). Same vec4 the world
382
+ /// materials get, so the sky and the ground cannot disagree.
383
+ pub(super) cloud: [f32; 4],
384
+ /// xy = wind direction in the XZ plane — the clouds drift downwind, the
385
+ /// same way the grass is leaning.
386
+ pub(super) wind: [f32; 4],
387
+ }
388
+
389
+ // EN-005 Phase 2 — uniforms for the procedural sky path.
390
+ //
391
+ // `SkyViewParams` drives the sky-view LUT compute shader (recomputed
392
+ // when the sun moves). `SunUniforms` drives the per-frame sun-disk
393
+ // composite in the procedural sky fragment shader.
394
+
395
+ #[repr(C)]
396
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
397
+ pub(super) struct SkyViewParams {
398
+ /// xyz = sun direction (world space, unit), w = sun intensity scale.
399
+ pub(super) sun: [f32; 4],
400
+ /// x = rayleigh density mult, y = mie density mult, z = ground albedo, w unused.
401
+ pub(super) knobs: [f32; 4],
402
+ }
403
+
404
+ #[repr(C)]
405
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
406
+ pub(super) struct SunUniforms {
407
+ /// xyz = sun direction (unit), w = sun intensity.
408
+ pub(super) sun: [f32; 4],
409
+ /// x = sun angular radius (rad), y = limb darkening (0..1), zw unused.
410
+ pub(super) params: [f32; 4],
411
+ }
412
+
413
+ // EN-005 V2 — aerial-perspective compute uniforms.
414
+ //
415
+ // Driven each frame from the renderer (camera + sun + atmosphere
416
+ // knobs). Layout must mirror `AerialParams` in
417
+ // AERIAL_PERSPECTIVE_SHADER_WGSL exactly.
418
+ #[repr(C)]
419
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
420
+ pub(super) struct AerialParams {
421
+ /// xyz = camera world position (engine units, metres assumed),
422
+ /// w = max distance the LUT covers (km).
423
+ pub(super) cam_pos: [f32; 4],
424
+ /// World→clip inverse, used per-voxel to reconstruct view rays
425
+ /// from NDC.
426
+ pub(super) inv_vp: [[f32; 4]; 4],
427
+ /// xyz = sun direction (unit), w = sun intensity scalar.
428
+ pub(super) sun: [f32; 4],
429
+ /// x = rayleigh density mult, y = mie density mult, zw unused.
430
+ pub(super) knobs: [f32; 4],
431
+ }
432
+
433
+
434
+ #[repr(C)]
435
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
436
+ pub(super) struct HizLinearizeParams {
437
+ /// xy = inv_size, z = proj[2][2], w = proj[3][2]
438
+ pub(super) params: [f32; 4],
439
+ /// xy = mip-0 size, zw unused
440
+ pub(super) size: [u32; 4],
441
+ }
442
+
443
+ #[repr(C)]
444
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
445
+ pub(super) struct HizDownsampleParams {
446
+ /// xy = dst-mip size, zw unused
447
+ pub(super) size: [u32; 4],
448
+ }
449
+
450
+ #[repr(C)]
451
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
452
+ pub(super) struct SsaoParams {
453
+ /// xy = inv_size (1/half_w, 1/half_h), z = radius (world units),
454
+ /// w = strength
455
+ pub(super) params: [f32; 4],
456
+ /// x = proj[0][0], y = proj[1][1], z = proj[2][0] (TAA jitter),
457
+ /// w = proj[2][1] (TAA jitter). Column-major: proj[col][row].
458
+ pub(super) proj_row01: [f32; 4],
459
+ /// x = proj[2][2], y = proj[3][2], z = 1/proj[0][0], w = 1/proj[1][1]
460
+ pub(super) proj_z: [f32; 4],
461
+ /// Light direction in view space (xyz, w unused). For contact shadows.
462
+ pub(super) light_dir_vs: [f32; 4],
463
+ /// x = half-res width, y = half-res height, z = frame phase
464
+ /// (`frame_index % 4`), w = "force-refresh" flag (non-zero on
465
+ /// first few frames, resize, or any host-side history invalidation).
466
+ pub(super) size: [u32; 4],
467
+ /// x = temporal blend alpha (≈0.25 steady, 4-frame EMA).
468
+ /// y = per-frame Halton-5 rotation of the direction basis
469
+ /// (uncorrelated with TAA's Halton-2/3 pixel jitter).
470
+ /// zw unused.
471
+ pub(super) temporal: [f32; 4],
472
+ }
473
+
474
+ // ============================================================
475
+ // SSAO Bilateral Blur post-process
476
+ // ============================================================
477
+
478
+ #[repr(C)]
479
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
480
+ pub(super) struct SsaoBlurParams {
481
+ /// xy = texel_size (of the half-res SSAO RT), z = depth_sigma, w = unused.
482
+ pub(super) params: [f32; 4],
483
+ }
484
+
485
+ // ============================================================
486
+ // Depth of Field (DoF) post-process
487
+ // ============================================================
488
+
489
+ #[repr(C)]
490
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
491
+ pub(super) struct DofParams {
492
+ /// x = focus_distance, y = aperture, z = max_blur_radius (UV), w = unused
493
+ pub(super) params: [f32; 4],
494
+ /// Inverse projection matrix — used to linearize depth.
495
+ pub(super) inv_proj: [[f32; 4]; 4],
496
+ }
497
+
498
+ // ============================================================
499
+ // Motion Blur post-process
500
+ // ============================================================
501
+ //
502
+ // Reads the TAA/DoF output (color) and the per-pixel velocity buffer.
503
+ // For each pixel, samples 8 taps along the velocity direction with a
504
+ // tent (linear) weight, blending them into a directionally-blurred
505
+ // result. Default OFF — no perf cost when disabled.
506
+
507
+ #[repr(C)]
508
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
509
+ pub(super) struct MotionBlurParams {
510
+ /// x = strength, y = max_blur (UV), zw = unused.
511
+ pub(super) params: [f32; 4],
512
+ }
513
+
514
+ // ============================================================
515
+ // Screen-Space Subsurface Scattering (SSS) post-process
516
+ // ============================================================
517
+ //
518
+ // Single-pass 9-tap disc blur applied after the motion blur pass
519
+ // (pre-composite). Uses a chromatic diffusion profile where red
520
+ // scatters furthest (kernel width 1×), green 0.5×, blue 0.25×,
521
+ // simulating the spectral absorption of skin/wax/leaves.
522
+ // Depth-guided bilateral weighting prevents color bleeding across
523
+ // depth discontinuities (hard edges stay sharp). Default OFF.
524
+
525
+ #[repr(C)]
526
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
527
+ pub(super) struct SssParams {
528
+ /// x = strength, y = width, z = falloff, w = unused.
529
+ pub(super) params: [f32; 4],
530
+ }
531
+
532
+ // ============================================================
533
+ // SSGI (Screen-Space Global Illumination) post-process
534
+ // ============================================================
535
+
536
+ // Ticket 007a: per-pass uniform params for the screen-probe SSGI chain.
537
+
538
+ #[repr(C)]
539
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
540
+ pub(super) struct ProbePlaceParams {
541
+ pub(super) inv_view: [[f32; 4]; 4],
542
+ /// x = proj[0][0], y = proj[1][1], z = proj[2][0], w = proj[2][1]
543
+ pub(super) proj_row01: [f32; 4],
544
+ /// x = half_w, y = half_h, z = grid_w, w = grid_h
545
+ pub(super) size: [u32; 4],
546
+ /// x = frame_index, y = tile_size (16.0), zw unused
547
+ pub(super) params: [f32; 4],
548
+ }
549
+
550
+ #[repr(C)]
551
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
552
+ pub(super) struct ProbeTraceParams {
553
+ pub(super) view: [[f32; 4]; 4],
554
+ pub(super) proj: [[f32; 4]; 4],
555
+ pub(super) inv_view: [[f32; 4]; 4],
556
+ pub(super) proj_row01: [f32; 4],
557
+ pub(super) size: [u32; 4],
558
+ /// x = frame_index, y = intensity, z = max_march_t (world units),
559
+ /// w = firefly luma cap.
560
+ pub(super) params: [f32; 4],
561
+ /// Ticket 007b HW path — sun direction in world space (xyz) +
562
+ /// intensity (w). Ignored by the SW-HiZ shader, consumed by HW + SDF.
563
+ pub(super) sun_dir: [f32; 4],
564
+ /// Sun colour (xyz), reserved (w). Used for analytical NdotL at hit.
565
+ pub(super) sun_color: [f32; 4],
566
+ /// Sky dome colour (xyz), reserved (w). Flat analytic sky for
567
+ /// up-facing hits where the ray hits a surface with an up normal.
568
+ pub(super) sky_color: [f32; 4],
569
+ /// Ticket 014 V3 — clipmap origin (xyz) + full extent (w). Used
570
+ /// by the SDF sphere-trace variant to map world-space march
571
+ /// positions into clipmap sample UVs.
572
+ pub(super) clipmap: [f32; 4],
573
+ /// Ticket 014 V6/V13 — WSRC cascade cubes. Each entry is
574
+ /// (origin xyz, extent w). Cascades are ordered near→far; miss
575
+ /// paths pick the smallest cascade whose cube contains the
576
+ /// ray-terminal position. `extent = 0.0` marks an unbaked
577
+ /// cascade (shader falls through to the next one). HW + Hi-Z
578
+ /// carry these for uniform-buffer size parity; Hi-Z ignores
579
+ /// them.
580
+ pub(super) wsrc_cascades: [[f32; 4]; 3],
581
+ }
582
+
583
+ /// PT-2 — fixed size of the kernel's texture binding array. Real texture
584
+ /// indices at or above this clamp to 0 (white); unused tail slots are
585
+ /// padded with the white texture so no PARTIALLY_BOUND feature is needed.
586
+ pub(super) const PT_MAX_TEXTURES: usize = 256;
587
+
588
+ /// Uniform for the path-tracing megakernel — WGSL mirror `PtParams` in
589
+ /// shaders/pt.rs. Field-for-field, 16-byte aligned, 672 bytes.
590
+ #[repr(C)]
591
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
592
+ pub(super) struct PtParamsCpu {
593
+ pub(super) inv_vp: [[f32; 4]; 4],
594
+ /// PT-3 — previous frame's unjittered VP (transposed like inv_vp)
595
+ /// for temporal history reprojection in realtime mode.
596
+ pub(super) prev_vp: [[f32; 4]; 4],
597
+ /// xyz camera world pos, w unused.
598
+ pub(super) cam_pos: [f32; 4],
599
+ /// xyz unit vector toward the sun, w unused.
600
+ pub(super) sun_dir: [f32; 4],
601
+ /// rgb premultiplied by intensity, w unused.
602
+ pub(super) sun_color: [f32; 4],
603
+ /// rgb ambient-derived sky tint, w unused.
604
+ pub(super) sky_color: [f32; 4],
605
+ /// x/y = TRACE grid dims (half in realtime mode), z=frame_index,
606
+ /// w=accum_count.
607
+ pub(super) size: [u32; 4],
608
+ /// x=mode (1 progressive / 2 realtime), y=max_bounces,
609
+ /// z=point_light_count (≤16), w=debug view (BLOOM_PT_DEBUG).
610
+ pub(super) cfg: [f32; 4],
611
+ /// PT-3 half-res: x/y = full G-buffer dims. z = 1 → hybrid sun
612
+ /// (shadow cascades instead of traced sun rays). w unused.
613
+ pub(super) ext: [u32; 4],
614
+ /// Raster shadow cascade VPs, transposed at upload like inv_vp.
615
+ pub(super) shadow_vps: [[[f32; 4]; 4]; 3],
616
+ /// 16 lights × (pos_range vec4, color_int vec4).
617
+ pub(super) lights: [[f32; 4]; 32],
618
+ }
619
+
620
+ #[repr(C)]
621
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
622
+ pub(super) struct ProbeTemporalParams {
623
+ /// x = alpha (EMA), y = force_refresh (1→alpha=1), z = grid_w (f32), w = grid_h (f32)
624
+ pub(super) params: [f32; 4],
625
+ }
626
+
627
+ #[repr(C)]
628
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
629
+ pub(super) struct ProbeResolveParams {
630
+ pub(super) inv_view: [[f32; 4]; 4],
631
+ pub(super) proj_row01: [f32; 4],
632
+ /// x = half_w, y = half_h, z = grid_w, w = grid_h
633
+ pub(super) size: [u32; 4],
634
+ /// x = tile_size (16.0), y = intensity, zw unused
635
+ pub(super) params: [f32; 4],
636
+ }
637
+
638
+ /// On-GPU `ProbeHeader` layout (must match PROBE_HELPERS_WGSL's struct).
639
+ /// 32 bytes per probe.
640
+ #[repr(C)]
641
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
642
+ pub(super) struct ProbeHeaderCpu {
643
+ pub(super) world_pos: [f32; 4],
644
+ pub(super) normal: [f32; 4],
645
+ }
646
+
647
+ /// Ticket 013 V3 — CardCaptureParams. ortho_vp + base_color + emissive.
648
+ /// 96 bytes; we allocate 128 for uniform-alignment headroom.
649
+ #[repr(C)]
650
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
651
+ pub(super) struct CardCaptureParams {
652
+ pub(super) ortho_vp: [[f32; 4]; 4],
653
+ pub(super) base_color: [f32; 4], // rgb = factor, w = has_base_texture (0/1)
654
+ pub(super) emissive: [f32; 4], // rgb = emissive_factor, w = has_emissive_texture (0/1)
655
+ }
656
+
657
+ /// Ticket 013 V2 — orthographic projection for the 6 signed axes.
658
+ /// `face_axis` encoding:
659
+ /// 0 → +X, 1 → -X, 2 → +Y, 3 → -Y, 4 → +Z, 5 → -Z.
660
+ /// For each face we pick the two orthogonal AABB axes and map them
661
+ /// to clip-space [-1, +1]. The ±pair for each axis differ only in
662
+ /// the sign of the "u" clip axis so that when the HW shader picks
663
+ /// axis N at hit and projects the hit into card UV, the UV lines up
664
+ /// with the mesh geometry as seen FROM that face.
665
+ pub(super) fn build_card_ortho_v2(face_axis: u32, bmin: [f32; 3], bmax: [f32; 3]) -> [[f32; 4]; 4] {
666
+ let wx = (bmax[0] - bmin[0]).max(1e-4);
667
+ let wy = (bmax[1] - bmin[1]).max(1e-4);
668
+ let wz = (bmax[2] - bmin[2]).max(1e-4);
669
+ let cx = bmin[0] + bmax[0];
670
+ let cy = bmin[1] + bmax[1];
671
+ let cz = bmin[2] + bmax[2];
672
+ match face_axis {
673
+ // +X → project onto YZ; clip.x = y, clip.y = z.
674
+ 0 => [
675
+ [0.0, 0.0, 0.0, 0.0],
676
+ [2.0/wy, 0.0, 0.0, 0.0],
677
+ [0.0, 2.0/wz, 0.0, 0.0],
678
+ [-cy/wy, -cz/wz, 0.0, 1.0],
679
+ ],
680
+ // -X → flip u so the -X view is the mirror of +X. clip.x = -y, clip.y = z.
681
+ 1 => [
682
+ [0.0, 0.0, 0.0, 0.0],
683
+ [-2.0/wy, 0.0, 0.0, 0.0],
684
+ [0.0, 2.0/wz, 0.0, 0.0],
685
+ [cy/wy, -cz/wz, 0.0, 1.0],
686
+ ],
687
+ // +Y → project onto XZ; clip.x = x, clip.y = z.
688
+ 2 => [
689
+ [2.0/wx, 0.0, 0.0, 0.0],
690
+ [0.0, 0.0, 0.0, 0.0],
691
+ [0.0, 2.0/wz, 0.0, 0.0],
692
+ [-cx/wx, -cz/wz, 0.0, 1.0],
693
+ ],
694
+ // -Y → flip u; clip.x = -x, clip.y = z.
695
+ 3 => [
696
+ [-2.0/wx, 0.0, 0.0, 0.0],
697
+ [0.0, 0.0, 0.0, 0.0],
698
+ [0.0, 2.0/wz, 0.0, 0.0],
699
+ [cx/wx, -cz/wz, 0.0, 1.0],
700
+ ],
701
+ // +Z → project onto XY; clip.x = x, clip.y = y.
702
+ 4 => [
703
+ [2.0/wx, 0.0, 0.0, 0.0],
704
+ [0.0, 2.0/wy, 0.0, 0.0],
705
+ [0.0, 0.0, 0.0, 0.0],
706
+ [-cx/wx, -cy/wy, 0.0, 1.0],
707
+ ],
708
+ // -Z → flip u; clip.x = -x, clip.y = y.
709
+ _ => [
710
+ [-2.0/wx, 0.0, 0.0, 0.0],
711
+ [0.0, 2.0/wy, 0.0, 0.0],
712
+ [0.0, 0.0, 0.0, 0.0],
713
+ [cx/wx, -cy/wy, 0.0, 1.0],
714
+ ],
715
+ }
716
+ }
717
+
718
+ /// Ticket 014 — per-mesh SDF bake uniform.
719
+ #[repr(C)]
720
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
721
+ pub(super) struct SdfBakeParams {
722
+ pub(super) aabb_min: [f32; 4],
723
+ pub(super) aabb_max: [f32; 4],
724
+ /// x = triangle_count, y = sdf_resolution (32), zw unused
725
+ pub(super) counts: [u32; 4],
726
+ }
727
+
728
+ /// Fullscreen-lag fix — an in-flight amortized scene-SDF-clipmap bake.
729
+ /// A job bakes `SCENE_SDF_CLIPMAP_LAYERS_PER_FRAME` voxel Z-layers per
730
+ /// frame into the staging texture; when the last slice lands the staging
731
+ /// contents are copied over the live clipmap and the origin flips
732
+ /// atomically, so traces never observe a half-baked field. The bind
733
+ /// group keeps the job's transient buffers alive.
734
+ pub(super) struct SdfClipmapBakeJob {
735
+ pub(super) origin: [f32; 3],
736
+ pub(super) aabb_min: [f32; 4],
737
+ pub(super) aabb_max: [f32; 4],
738
+ pub(super) uniform: wgpu::Buffer,
739
+ pub(super) bind_group: wgpu::BindGroup,
740
+ pub(super) next_z: u32,
741
+ }
742
+
743
+ /// Ticket 014 V6 — uniform for `WSRC_BAKE_WGSL`. Analytic sun × shadow
744
+ /// + analytic sky computed per probe-octel. Shadow VPs + splits +
745
+ /// flags mirror CARD_LIGHT_WGSL so the shader can re-use the same
746
+ /// cascade-sampling helper.
747
+ #[repr(C)]
748
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
749
+ pub(super) struct WsrcBakeParams {
750
+ pub(super) sun_dir: [f32; 4],
751
+ pub(super) sun_color: [f32; 4],
752
+ pub(super) sky_color: [f32; 4],
753
+ /// xyz = WSRC cube origin, w = full extent.
754
+ pub(super) grid: [f32; 4],
755
+ /// Cascade 0..2 VPs. Only cascade 2 is actually sampled by V6
756
+ /// (widest cascade covers the whole 120 m cube), but we carry
757
+ /// all three to keep the uniform layout identical to the card-
758
+ /// lighting params.
759
+ pub(super) shadow_vps: [[[f32; 4]; 4]; 3],
760
+ /// xyz = view-space split distances (unused by V6 because it
761
+ /// always samples cascade 2), w = 0.
762
+ pub(super) shadow_splits: [f32; 4],
763
+ /// x = shadow bias, y = shadows_enabled (0/1), zw unused.
764
+ pub(super) flags: [f32; 4],
765
+ /// EN-023 — xyz = scene-average albedo (mean of card-instance flat
766
+ /// albedos) for the SW bake's ground-bounce term; w unused. The HW
767
+ /// bake ignores it (it traces real geometry).
768
+ pub(super) ground_albedo: [f32; 4],
769
+ }
770
+
771
+ /// Uniform struct for the card-lighting compute pass. Matches
772
+ /// CARD_LIGHT_WGSL.
773
+ #[repr(C)]
774
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
775
+ pub(super) struct CardLightParams {
776
+ pub(super) sun_dir: [f32; 4],
777
+ pub(super) sun_color: [f32; 4],
778
+ pub(super) sky_color: [f32; 4],
779
+ /// [atlas_size, slot_size, slots_per_row, active_slot_count]
780
+ pub(super) atlas_info: [u32; 4],
781
+ /// Shadow cascade VPs (3× mat4) — identity when shadows disabled.
782
+ pub(super) shadow_vps: [[[f32; 4]; 4]; 3],
783
+ /// xyz = view-space split distances for cascades 0/1/2, w = 0.
784
+ pub(super) shadow_splits: [f32; 4],
785
+ /// Camera view matrix — needed to convert card-texel world pos
786
+ /// into view-space Z for cascade selection.
787
+ pub(super) view_matrix: [[f32; 4]; 4],
788
+ /// x = shadow bias, y = shadows_enabled (0/1), zw unused.
789
+ pub(super) flags: [f32; 4],
790
+ }
791
+
792
+ /// Ticket 013 V3 — per-slot metadata consumed by `card_light_pass`.
793
+ /// Baked at capture time; carries enough state for the lighting
794
+ /// shader to reconstruct each texel's world-space position and query
795
+ /// the shadow cascade at that point. 128 bytes per slot × 4096 slots
796
+ /// = 512 KB — fits comfortably.
797
+ #[repr(C)]
798
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
799
+ pub(super) struct CardSlotMetaCpu {
800
+ /// xyz = world-space card-face normal, w = signed axis (0..6 as f32).
801
+ pub(super) normal_ws: [f32; 4],
802
+ /// Object-space AABB min (xyz) + padding (w).
803
+ pub(super) aabb_min: [f32; 4],
804
+ /// Object-space AABB max (xyz) + padding (w).
805
+ pub(super) aabb_max: [f32; 4],
806
+ /// Mesh's world transform. Multiplied into the object-space
807
+ /// card-plane position to land in world space for shadow lookup.
808
+ pub(super) transform: [[f32; 4]; 4],
809
+ }
810
+
811
+ /// Ticket 007b — per-TLAS-instance GI shading input. Indexed by the
812
+ /// hit's `instance_custom_data` in the HW trace shader. Layout must
813
+ /// match the `InstanceGIData` struct in SSGI_PROBE_TRACE_HW_WGSL.
814
+ #[repr(C)]
815
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
816
+ pub(super) struct InstanceGiDataCpu {
817
+ /// Flat albedo (per-mesh, pre-baked from material base-color).
818
+ /// Used as a fallback when `card_slot.w < 0` (no card captured).
819
+ pub(super) albedo: [f32; 3],
820
+ /// Scalar emissive luminance — multiplied by `albedo` at the hit.
821
+ pub(super) emissive_luma: f32,
822
+ /// Flat world-space mesh normal. Rough — averaged over vertex
823
+ /// normals at BLAS build time. Used when the card atlas is
824
+ /// unavailable; ticket 013's textured path still drives lighting
825
+ /// from this flat normal but multiplies the sampled albedo in.
826
+ pub(super) normal_ws: [f32; 3],
827
+ pub(super) _pad0: f32,
828
+ /// Ticket 013 — card slot for textured hit shading.
829
+ /// `card_slot.xy` = atlas slot coord (0..CARD_SLOTS_PER_ROW).
830
+ /// `card_slot.z` = dominant axis (0=X, 1=Y, 2=Z).
831
+ /// `card_slot.w` = flag (1.0 = card captured, 0.0 = no card → fall
832
+ /// back to `albedo` flat value).
833
+ pub(super) card_slot: [f32; 4],
834
+ /// Object-space AABB min (xyz) + unused pad (w). The HW paths
835
+ /// transform hits into object space (hit.world_to_object) and
836
+ /// compare against THESE — do not world-ify them.
837
+ pub(super) card_aabb_min: [f32; 4],
838
+ /// Object-space AABB max (xyz) + unused pad (w).
839
+ pub(super) card_aabb_max: [f32; 4],
840
+ /// EN-023 — WORLD-space AABB min/max. The SDF trace has no
841
+ /// world_to_object (it marches a world-space clipmap), so its
842
+ /// broad-phase compares the world hit against these. With the old
843
+ /// object-space-only bounds, every transformed instance fell
844
+ /// through to the flat-gray analytic fallback — zero colored
845
+ /// bounce on non-RT adapters (round-2 audit F4).
846
+ pub(super) world_aabb_min: [f32; 4],
847
+ pub(super) world_aabb_max: [f32; 4],
848
+ /// PT-2 — geometry window into the PT megabuffers + texture id.
849
+ /// x = first vertex (Vertex3D-stride slot) in pt_geo_vertices,
850
+ /// y = first index in pt_geo_indices, z = index count,
851
+ /// w = albedo texture index (renderer texture store; 0 = white).
852
+ /// z == 0 marks "no geometry window" (kernel falls back to the
853
+ /// flat normal + card albedo, i.e. PT-1 behaviour).
854
+ pub(super) geo: [u32; 4],
855
+ /// PT-2 — x = roughness, y = metalness, z/w unused.
856
+ pub(super) mat_params: [f32; 4],
857
+ }
858
+
859
+ #[repr(C)]
860
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
861
+ pub(super) struct SsrTemporalParams {
862
+ /// x = blend_alpha (0.1), yzw unused
863
+ pub(super) params: [f32; 4],
864
+ }
865
+
866
+ #[repr(C)]
867
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
868
+ pub(super) struct SsrParams {
869
+ pub(super) inv_proj: [[f32; 4]; 4],
870
+ pub(super) proj: [[f32; 4]; 4],
871
+ /// x=strength, y=max_dist, z=n_steps, w=frame index
872
+ pub(super) params: [f32; 4],
873
+ /// EN-021 — view→world rotation (transpose of the view 3×3) for the
874
+ /// env-miss fallback's direction lookup.
875
+ pub(super) inv_view_rot: [[f32; 4]; 4],
876
+ /// EN-021 — x = env max LOD, y = env intensity, zw unused.
877
+ pub(super) params2: [f32; 4],
878
+ }
879
+
880
+ #[repr(C)]
881
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
882
+ pub(super) struct SceneComposeParams {
883
+ pub(super) misc: [f32; 4],
884
+ pub(super) inv_vp: [[f32; 4]; 4],
885
+ pub(super) fog_color_density: [f32; 4],
886
+ pub(super) fog_params: [f32; 4],
887
+ pub(super) sun_shaft_uv_strength: [f32; 4],
888
+ pub(super) sun_shaft_color: [f32; 4],
889
+ }
890
+
891
+ #[repr(C)]
892
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
893
+ pub(super) struct TaaParams {
894
+ /// x = blend factor (current-frame weight), yzw padding.
895
+ pub(super) params: [f32; 4],
896
+ pub(super) inv_vp: [[f32; 4]; 4],
897
+ pub(super) prev_vp: [[f32; 4]; 4],
898
+ }
899
+
900
+ #[repr(C)]
901
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
902
+ pub(super) struct UpscaleParams {
903
+ /// x = mode (0 = bilinear, 1 = Catmull-Rom), yzw padding.
904
+ pub(super) params: [f32; 4],
905
+ }
906
+
907
+ #[repr(C)]
908
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
909
+ pub(super) struct RcasParams {
910
+ /// x = sharpen strength (0 off, 1 max), yzw padding.
911
+ pub(super) params: [f32; 4],
912
+ }
913
+
914
+ #[repr(C)]
915
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
916
+ pub(super) struct ExposureParams {
917
+ pub(super) params: [f32; 4],
918
+ }
919
+
920
+ #[repr(C)]
921
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
922
+ pub(super) struct BloomParams {
923
+ /// xy = source texel size, z = filter radius (upsample),
924
+ /// w = HDR threshold (downsample-threshold variant).
925
+ pub(super) params: [f32; 4],
926
+ }
927
+
928
+ #[repr(C)]
929
+ #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
930
+ pub(super) struct CompositeParams {
931
+ /// x = tonemap kind (0 ACES / 1 AgX), y = auto-exposure toggle,
932
+ /// z = manual exposure, w = auto-exposure target key.
933
+ pub(super) params: [f32; 4],
934
+ /// Filmic-look knobs — see WGSL comment.
935
+ /// x = chromatic-aberration strength, y = vignette strength,
936
+ /// z = vignette softness, w = grain strength.
937
+ pub(super) filmic: [f32; 4],
938
+ /// x = grain seed (frame index, animates the noise),
939
+ /// y = sharpen strength, zw padding.
940
+ pub(super) misc: [f32; 4],
941
+ }