@woosh/meep-engine 3.35.0 → 3.37.0

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 (103) hide show
  1. package/package.json +1 -1
  2. package/src/engine/Engine.d.ts +2 -0
  3. package/src/engine/Engine.d.ts.map +1 -1
  4. package/src/engine/Engine.js +5 -1
  5. package/src/engine/input/devices/XRInputDevice.d.ts +91 -0
  6. package/src/engine/input/devices/XRInputDevice.d.ts.map +1 -0
  7. package/src/engine/input/devices/XRInputDevice.js +479 -0
  8. package/src/engine/input/devices/xr/XRGamepadMapping.d.ts +14 -0
  9. package/src/engine/input/devices/xr/XRGamepadMapping.d.ts.map +1 -0
  10. package/src/engine/input/devices/xr/XRGamepadMapping.js +9 -0
  11. package/src/engine/input/devices/xr/XRHandedness.d.ts +15 -0
  12. package/src/engine/input/devices/xr/XRHandedness.d.ts.map +1 -0
  13. package/src/engine/input/devices/xr/XRHandedness.js +10 -0
  14. package/src/engine/input/devices/xr/XRInputAction.d.ts +50 -0
  15. package/src/engine/input/devices/xr/XRInputAction.d.ts.map +1 -0
  16. package/src/engine/input/devices/xr/XRInputAction.js +116 -0
  17. package/src/engine/input/devices/xr/XRInputButton.d.ts +26 -0
  18. package/src/engine/input/devices/xr/XRInputButton.d.ts.map +1 -0
  19. package/src/engine/input/devices/xr/XRInputButton.js +53 -0
  20. package/src/engine/input/devices/xr/XRInputCoordinates.d.ts +23 -0
  21. package/src/engine/input/devices/xr/XRInputCoordinates.d.ts.map +1 -0
  22. package/src/engine/input/devices/xr/XRInputCoordinates.js +35 -0
  23. package/src/engine/input/devices/xr/XRInputHandle.d.ts +93 -0
  24. package/src/engine/input/devices/xr/XRInputHandle.d.ts.map +1 -0
  25. package/src/engine/input/devices/xr/XRInputHandle.js +268 -0
  26. package/src/engine/input/devices/xr/XRInputPose.d.ts +59 -0
  27. package/src/engine/input/devices/xr/XRInputPose.d.ts.map +1 -0
  28. package/src/engine/input/devices/xr/XRInputPose.js +77 -0
  29. package/src/engine/input/devices/xr/XRInputTypes.d.ts +76 -0
  30. package/src/engine/input/devices/xr/XRInputTypes.d.ts.map +1 -0
  31. package/src/engine/input/devices/xr/XRInputTypes.js +31 -0
  32. package/src/engine/input/devices/xr/XRInputValueSource.d.ts +11 -0
  33. package/src/engine/input/devices/xr/XRInputValueSource.d.ts.map +1 -0
  34. package/src/engine/input/devices/xr/XRInputValueSource.js +11 -0
  35. package/src/engine/input/devices/xr/XRTargetRayMode.d.ts +17 -0
  36. package/src/engine/input/devices/xr/XRTargetRayMode.d.ts.map +1 -0
  37. package/src/engine/input/devices/xr/XRTargetRayMode.js +12 -0
  38. package/src/engine/input/devices/xr/XRVisibilityState.d.ts +15 -0
  39. package/src/engine/input/devices/xr/XRVisibilityState.d.ts.map +1 -0
  40. package/src/engine/input/devices/xr/XRVisibilityState.js +10 -0
  41. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.d.ts +15 -1
  42. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.d.ts.map +1 -1
  43. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +9 -0
  44. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.d.ts +17 -4
  45. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.d.ts.map +1 -1
  46. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +26 -0
  47. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.d.ts +17 -2
  48. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.d.ts.map +1 -1
  49. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.d.ts +5 -0
  50. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.d.ts.map +1 -1
  51. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.d.ts +2 -0
  52. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.d.ts.map +1 -1
  53. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +2 -0
  54. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.d.ts +7 -0
  55. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.d.ts.map +1 -1
  56. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.d.ts +5 -0
  57. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.d.ts.map +1 -1
  58. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.d.ts +5 -0
  59. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.d.ts.map +1 -1
  60. package/src/engine/input/ecs/ism/device/XRInputDeviceAdapter.d.ts +30 -0
  61. package/src/engine/input/ecs/ism/device/XRInputDeviceAdapter.d.ts.map +1 -0
  62. package/src/engine/input/ecs/ism/device/XRInputDeviceAdapter.js +87 -0
  63. package/src/engine/input/ecs/ism/device/XR_INPUT_DEVICE_IDS.d.ts +15 -0
  64. package/src/engine/input/ecs/ism/device/XR_INPUT_DEVICE_IDS.d.ts.map +1 -0
  65. package/src/engine/input/ecs/ism/device/XR_INPUT_DEVICE_IDS.js +22 -0
  66. package/src/engine/input/ecs/ism/trigger/InputTrigger.d.ts +11 -1
  67. package/src/engine/input/ecs/ism/trigger/InputTrigger.d.ts.map +1 -1
  68. package/src/engine/input/ecs/ism/trigger/InputTrigger.js +7 -0
  69. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.d.ts +16 -2
  70. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.d.ts.map +1 -1
  71. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.d.ts +22 -2
  72. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.d.ts.map +1 -1
  73. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.d.ts +16 -2
  74. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.d.ts.map +1 -1
  75. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.d.ts +24 -4
  76. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.d.ts.map +1 -1
  77. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.d.ts +6 -8
  78. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.d.ts.map +1 -1
  79. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +5 -0
  80. package/src/engine/input/ecs/ism/trigger/InputTriggerXRButton.d.ts +15 -0
  81. package/src/engine/input/ecs/ism/trigger/InputTriggerXRButton.d.ts.map +1 -0
  82. package/src/engine/input/ecs/ism/trigger/InputTriggerXRButton.js +25 -0
  83. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  84. package/src/shade/renderer/animation/chunk_pose_accumulator.js +14 -24
  85. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.js +16 -15
  86. package/src/shade/renderer/deferred/indirect/chunk_lpv_sample_light.js +1 -1
  87. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +1123 -1122
  88. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +202 -201
  89. package/src/shade/renderer/light/environment/chunk_roughness_to_mip_ratio.js +24 -0
  90. package/src/shade/renderer/light/environment/chunk_sphere_probe_roughness_to_lod.js +2 -25
  91. package/src/shade/renderer/light/environment/shader_project_environment_sh.d.ts +2 -2
  92. package/src/shade/renderer/light/environment/shader_project_environment_sh.d.ts.map +1 -1
  93. package/src/shade/renderer/light/environment/shader_project_environment_sh.js +50 -19
  94. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  95. package/src/shade/renderer/particles/DESIGN.md +1 -1
  96. package/src/shade/renderer/particles/bounds/chunk_particle_bounds.js +10 -9
  97. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.js +10 -8
  98. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_add_f32_storage.js +25 -0
  99. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_max_f32_storage.js +28 -0
  100. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_min_f32_storage.js +28 -0
  101. package/src/shade/renderer/shader/chunk/geometry/plane/{plane_ray_raycast.js → chunk_plane_ray_raycast.js} +1 -1
  102. package/src/shade/renderer/shadow/map/csm/shader/shader_csm_caster_range.js +3 -3
  103. package/src/shade/renderer/animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.js +0 -59
@@ -1,3 +1,4 @@
1
+ import { WebGPUExtensionType } from "../../../descriptor/WebGPUExtensionType.js";
1
2
  import { chunk_get_ibl_irradiance } from "../../deferred/indirect/chunk_get_ibl_irradiance.js";
2
3
  import { PI_DECLARATION } from "../../shader/chunk/math/PI_DECLARATION.js";
3
4
  import { chunk_sphere_fibonacci_point } from "../../shader/chunk/geometry/sphere/chunk_sphere_fibonacci_point.js";
@@ -21,12 +22,12 @@ export const ENVIRONMENT_SH_TYPE = WebGPUArray.from(vec4f, 4);
21
22
 
22
23
  /**
23
24
  * Directions the projection samples, spread evenly over the sphere (`sphere_fibonacci_point`).
24
- * The field is the environment at the roughness diffuse reads it at, so this is far more than its
25
- * two bands can hold, and it costs nothing that recurs: the pass runs when the environment changes.
25
+ * The field is already prefiltered at the roughness diffuse reads it at; these samples project
26
+ * that smooth field into two bands. The pass runs only when the environment changes.
26
27
  *
27
28
  * @type {number}
28
29
  */
29
- export const ENVIRONMENT_SH_SAMPLE_COUNT = 1024;
30
+ export const ENVIRONMENT_SH_SAMPLE_COUNT = 256;
30
31
 
31
32
  const resources = new ShaderResourceSetDescriptor();
32
33
 
@@ -39,28 +40,56 @@ const body = CodeChunk.from(
39
40
  //language=WGSL
40
41
  `
41
42
  const SAMPLE_COUNT : u32 = ${ENVIRONMENT_SH_SAMPLE_COUNT}u;
43
+ // The minimum subgroup size is four, so this holds every subgroup's partial sum.
44
+ const MAX_SUBGROUP_PARTIALS : u32 = SAMPLE_COUNT / 4u;
45
+
46
+ var<workgroup> wg_partial_count: atomic<u32>;
47
+ var<workgroup> wg_sh_0: array<vec3<f32>, MAX_SUBGROUP_PARTIALS>;
48
+ var<workgroup> wg_sh_1: array<vec3<f32>, MAX_SUBGROUP_PARTIALS>;
49
+ var<workgroup> wg_sh_2: array<vec3<f32>, MAX_SUBGROUP_PARTIALS>;
50
+ var<workgroup> wg_sh_3: array<vec3<f32>, MAX_SUBGROUP_PARTIALS>;
51
+
52
+ // One workgroup projects the sphere, with one invocation per sample.
53
+ @compute @workgroup_size(${ENVIRONMENT_SH_SAMPLE_COUNT})
54
+ fn main(@builtin(local_invocation_index) thread_index: u32) {
55
+ let direction = sphere_fibonacci_point(f32(thread_index), f32(SAMPLE_COUNT));
56
+
57
+ // exactly what the forward mesh path takes as a surface's diffuse irradiance under IBL
58
+ let irradiance = get_ibl_irradiance(direction);
59
+ let basis = sh2_basis_at(direction);
60
+
61
+ let sh_0_partial = subgroupAdd(irradiance * basis[0]);
62
+ let sh_1_partial = subgroupAdd(irradiance * basis[1]);
63
+ let sh_2_partial = subgroupAdd(irradiance * basis[2]);
64
+ let sh_3_partial = subgroupAdd(irradiance * basis[3]);
65
+
66
+ // Workgroup variables start zeroed. One elected lane claims a distinct slot per subgroup;
67
+ // local invocation indices need not map to subgroups in contiguous blocks.
68
+ if (subgroupElect()) {
69
+ let slot = atomicAdd(&wg_partial_count, 1u);
70
+ wg_sh_0[slot] = sh_0_partial;
71
+ wg_sh_1[slot] = sh_1_partial;
72
+ wg_sh_2[slot] = sh_2_partial;
73
+ wg_sh_3[slot] = sh_3_partial;
74
+ }
75
+
76
+ workgroupBarrier();
77
+
78
+ if (thread_index != 0u) {
79
+ return;
80
+ }
42
81
 
43
- // One invocation: the projection is 1024 reads that happen when the environment changes and never
44
- // otherwise, and a single lane needs no reduction and runs whole on the emulator.
45
- @compute @workgroup_size(1)
46
- fn main() {
47
82
  var sh_0 = vec3<f32>(0.0);
48
83
  var sh_1 = vec3<f32>(0.0);
49
84
  var sh_2 = vec3<f32>(0.0);
50
85
  var sh_3 = vec3<f32>(0.0);
86
+ let partial_count = atomicLoad(&wg_partial_count);
51
87
 
52
- for (var i = 0u; i < SAMPLE_COUNT; i++) {
53
- let direction = sphere_fibonacci_point(f32(i), f32(SAMPLE_COUNT));
54
-
55
- // exactly what the forward mesh path takes as a surface's diffuse irradiance under IBL
56
- let irradiance = get_ibl_irradiance(direction);
57
-
58
- let basis = sh2_basis_at(direction);
59
-
60
- sh_0 += irradiance * basis[0];
61
- sh_1 += irradiance * basis[1];
62
- sh_2 += irradiance * basis[2];
63
- sh_3 += irradiance * basis[3];
88
+ for (var i = 0u; i < partial_count; i++) {
89
+ sh_0 += wg_sh_0[i];
90
+ sh_1 += wg_sh_1[i];
91
+ sh_2 += wg_sh_2[i];
92
+ sh_3 += wg_sh_3[i];
64
93
  }
65
94
 
66
95
  // each sample stands for an equal share of the sphere
@@ -83,6 +112,8 @@ fn main() {
83
112
  PI_DECLARATION,
84
113
  ]);
85
114
 
115
+ body.addExtension(WebGPUExtensionType.Subgroups);
116
+
86
117
  /**
87
118
  * The environment as RGB SH2 of radiance ({@link ENVIRONMENT_SH_TYPE}): the form the renderer's other
88
119
  * indirect lighting — Brick4, the light probe volume — already hands over, so the froxel GI stage
@@ -13,7 +13,7 @@ export class TinyGltf {
13
13
  * @readonly
14
14
  * @type {TextureFormat}
15
15
  */
16
- static readonly "__#250@#UNCOMPRESSED_TARGET": TextureFormat;
16
+ static readonly "__#254@#UNCOMPRESSED_TARGET": TextureFormat;
17
17
  /**
18
18
  * @param {AssetManager} assetManager
19
19
  * @param {AssetRequestScope} [scope]
@@ -353,7 +353,7 @@ finished appending to the alive list:
353
353
  `particle_billboard_radius`, the largest offset `particle_billboard_offset` can produce for that
354
354
  size, velocity and projection, over every corner, every roll and any camera. The box has to hold
355
355
  the *quads*, not the centres, and it has to be camera-independent, because it is read on a later
356
- frame. The merge is `aabb_atomic_min_f32` / `aabb_atomic_max_f32`, the same CAS-spin the
356
+ frame. The merge is `atomic_min_f32_storage` / `atomic_max_f32_storage`, the same CAS-spin the
357
357
  skinned-mesh bounds refresh uses; both load first and return without a CAS once the box holds the
358
358
  contribution, so what survives is load traffic, not exchanges. Dispatched indirectly from
359
359
  `sim_dispatch_args`, the args the coherence reset wrote from the GPU's own alive count.
@@ -1,5 +1,6 @@
1
1
  import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
2
- import { chunk_aabb_atomic_f32 } from "../../animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.js";
2
+ import { chunk_atomic_min_f32_storage } from "../../shader/chunk/atomic/chunk_atomic_min_f32_storage.js";
3
+ import { chunk_atomic_max_f32_storage } from "../../shader/chunk/atomic/chunk_atomic_max_f32_storage.js";
3
4
  import { AABB3_STRUCT } from "../../shader/chunk/geometry/aabb3/AABB3_STRUCT.js";
4
5
  import {
5
6
  PARTICLE_BOUNDS_BASE,
@@ -26,7 +27,7 @@ import { chunk_particle_render_math } from "../shaders/chunk_particle_render_mat
26
27
  *
27
28
  * ## Why f32 in the buffer, and not an order-preserving encoding
28
29
  *
29
- * `aabb_atomic_min_f32` / `aabb_atomic_max_f32` are the engine's, shared with the skinned-mesh
30
+ * `atomic_min_f32_storage` / `atomic_max_f32_storage` are the engine's, shared with the skinned-mesh
30
31
  * bounds refresh, and they CAS-spin over the plain float so the words are readable as floats at
31
32
  * every moment of the frame. The sign-flip encoding that would let `atomicMin` do it in one
32
33
  * instruction leaves the buffer in a form only a decoder understands, and the publish pass reads
@@ -71,12 +72,12 @@ fn particle_bounds_contribute(row : u32, center : vec3<f32>, radius : f32) {
71
72
  let lo = center - vec3<f32>(radius);
72
73
  let hi = center + vec3<f32>(radius);
73
74
 
74
- aabb_atomic_min_f32(&counters[base + 0u], lo.x);
75
- aabb_atomic_min_f32(&counters[base + 1u], lo.y);
76
- aabb_atomic_min_f32(&counters[base + 2u], lo.z);
77
- aabb_atomic_max_f32(&counters[base + 3u], hi.x);
78
- aabb_atomic_max_f32(&counters[base + 4u], hi.y);
79
- aabb_atomic_max_f32(&counters[base + 5u], hi.z);
75
+ atomic_min_f32_storage(&counters[base + 0u], lo.x);
76
+ atomic_min_f32_storage(&counters[base + 1u], lo.y);
77
+ atomic_min_f32_storage(&counters[base + 2u], lo.z);
78
+ atomic_max_f32_storage(&counters[base + 3u], hi.x);
79
+ atomic_max_f32_storage(&counters[base + 4u], hi.y);
80
+ atomic_max_f32_storage(&counters[base + 5u], hi.z);
80
81
  }
81
82
 
82
83
  // What the frame accumulated for one row. Empty (see \`particle_bounds_is_empty\`) when nothing
@@ -117,7 +118,7 @@ fn particle_bounds_reset(row : u32) {
117
118
  fn particle_bounds_is_empty(box : ${AABB3_STRUCT.wgsl_ref}) -> bool {
118
119
  return any(box.min > box.max);
119
120
  }
120
- `, [chunk_aabb_atomic_f32, AABB3_STRUCT.declaration_chunk]);
121
+ `, [chunk_atomic_min_f32_storage, chunk_atomic_max_f32_storage, AABB3_STRUCT.declaration_chunk]);
121
122
 
122
123
  /**
123
124
  * One live particle's contribution to its emitter's box: where its quad is, and how far the quad
@@ -1,7 +1,8 @@
1
1
  import { WebGPUExtensionType } from "../../descriptor/WebGPUExtensionType.js";
2
2
  import { atomic_u32, f32, u32 } from "../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
3
3
  import { WebGPUArray } from "../shader/type/WebGPUArray.js";
4
- import { chunk_aabb_atomic_f32 } from "../animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.js";
4
+ import { chunk_atomic_min_f32_storage } from "../shader/chunk/atomic/chunk_atomic_min_f32_storage.js";
5
+ import { chunk_atomic_max_f32_storage } from "../shader/chunk/atomic/chunk_atomic_max_f32_storage.js";
5
6
  import { CodeChunk } from "../shader/compiler/CodeChunk.js";
6
7
  import { ComputeShader } from "../shader/ComputeShader.js";
7
8
  import { ShaderResourceSetDescriptor } from "../shader/resource/ShaderResourceSetDescriptor.js";
@@ -107,17 +108,18 @@ fn main(
107
108
  group_high = max(group_high, wg_high[s]);
108
109
  }
109
110
 
110
- aabb_atomic_min_f32(&scene_bounds[0], group_low.x);
111
- aabb_atomic_min_f32(&scene_bounds[1], group_low.y);
112
- aabb_atomic_min_f32(&scene_bounds[2], group_low.z);
113
- aabb_atomic_max_f32(&scene_bounds[3], group_high.x);
114
- aabb_atomic_max_f32(&scene_bounds[4], group_high.y);
115
- aabb_atomic_max_f32(&scene_bounds[5], group_high.z);
111
+ atomic_min_f32_storage(&scene_bounds[0], group_low.x);
112
+ atomic_min_f32_storage(&scene_bounds[1], group_low.y);
113
+ atomic_min_f32_storage(&scene_bounds[2], group_low.z);
114
+ atomic_max_f32_storage(&scene_bounds[3], group_high.x);
115
+ atomic_max_f32_storage(&scene_bounds[4], group_high.y);
116
+ atomic_max_f32_storage(&scene_bounds[5], group_high.z);
116
117
  }
117
118
  }
118
119
  `, [
119
120
  chunk_scene_database_access,
120
- chunk_aabb_atomic_f32,
121
+ chunk_atomic_min_f32_storage,
122
+ chunk_atomic_max_f32_storage,
121
123
  ]);
122
124
 
123
125
  body.addExtension(WebGPUExtensionType.Subgroups);
@@ -0,0 +1,25 @@
1
+ import { CodeChunk } from "../../compiler/CodeChunk.js";
2
+
3
+ /**
4
+ * Atomic f32 addition over a read-write storage atomic<u32> cell containing f32 bits.
5
+ *
6
+ * Each compare-exchange attempt adds to the current float value and writes the sum
7
+ * as plain f32 bits. Failed or spurious exchanges retry from a fresh atomic load,
8
+ * so concurrent additions are not lost.
9
+ *
10
+ * @type {CodeChunk}
11
+ */
12
+ export const chunk_atomic_add_f32_storage = CodeChunk.from(
13
+ //language=WGSL
14
+ `
15
+ fn atomic_add_f32_storage(cell: ptr<storage, atomic<u32>, read_write>, value: f32) {
16
+ loop {
17
+ let old_u = atomicLoad(cell);
18
+ let old_f = bitcast<f32>(old_u);
19
+ let result = atomicCompareExchangeWeak(cell, old_u, bitcast<u32>(old_f + value));
20
+ if result.exchanged {
21
+ break;
22
+ }
23
+ }
24
+ }
25
+ `);
@@ -0,0 +1,28 @@
1
+ import { CodeChunk } from "../../compiler/CodeChunk.js";
2
+
3
+ /**
4
+ * Atomic f32 maximum over a read-write storage atomic<u32> cell containing f32 bits.
5
+ *
6
+ * Compare-exchange retries keep the cell in plain f32 representation. If the stored
7
+ * value already bounds the contribution, the function exits without an exchange.
8
+ * Infinities are supported; the stored value and contribution must not be NaN.
9
+ *
10
+ * @type {CodeChunk}
11
+ */
12
+ export const chunk_atomic_max_f32_storage = CodeChunk.from(
13
+ //language=WGSL
14
+ `
15
+ fn atomic_max_f32_storage(cell: ptr<storage, atomic<u32>, read_write>, value: f32) {
16
+ loop {
17
+ let old_u = atomicLoad(cell);
18
+ let old_f = bitcast<f32>(old_u);
19
+ if value <= old_f {
20
+ break;
21
+ }
22
+ let result = atomicCompareExchangeWeak(cell, old_u, bitcast<u32>(value));
23
+ if result.exchanged {
24
+ break;
25
+ }
26
+ }
27
+ }
28
+ `);
@@ -0,0 +1,28 @@
1
+ import { CodeChunk } from "../../compiler/CodeChunk.js";
2
+
3
+ /**
4
+ * Atomic f32 minimum over a read-write storage atomic<u32> cell containing f32 bits.
5
+ *
6
+ * Compare-exchange retries keep the cell in plain f32 representation. If the stored
7
+ * value already bounds the contribution, the function exits without an exchange.
8
+ * Infinities are supported; the stored value and contribution must not be NaN.
9
+ *
10
+ * @type {CodeChunk}
11
+ */
12
+ export const chunk_atomic_min_f32_storage = CodeChunk.from(
13
+ //language=WGSL
14
+ `
15
+ fn atomic_min_f32_storage(cell: ptr<storage, atomic<u32>, read_write>, value: f32) {
16
+ loop {
17
+ let old_u = atomicLoad(cell);
18
+ let old_f = bitcast<f32>(old_u);
19
+ if value >= old_f {
20
+ break;
21
+ }
22
+ let result = atomicCompareExchangeWeak(cell, old_u, bitcast<u32>(value));
23
+ if result.exchanged {
24
+ break;
25
+ }
26
+ }
27
+ }
28
+ `);
@@ -4,7 +4,7 @@ import { CodeChunk } from "../../../compiler/CodeChunk.js";
4
4
  * Ported from meep
5
5
  * @type {CodeChunk}
6
6
  */
7
- export const plane_ray_raycast = CodeChunk.from(
7
+ export const chunk_plane_ray_raycast = CodeChunk.from(
8
8
  //language=WGSL
9
9
  `
10
10
  fn plane_ray_raycast(
@@ -6,7 +6,7 @@ import { CodeChunk } from "../../../../shader/compiler/CodeChunk.js";
6
6
  import { COMPUTE_SHADER_DEFAULT_GROUP_SIZE } from "../../../../shader/COMPUTE_SHADER_DEFAULT_GROUP_SIZE.js";
7
7
  import { ComputeShader } from "../../../../shader/ComputeShader.js";
8
8
  import { ShaderResourceSetDescriptor } from "../../../../shader/resource/ShaderResourceSetDescriptor.js";
9
- import { chunk_aabb_atomic_f32 } from "../../../../animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.js";
9
+ import { chunk_atomic_min_f32_storage } from "../../../../shader/chunk/atomic/chunk_atomic_min_f32_storage.js";
10
10
  import { LIGHT_DATABASE_SPEC } from "../../../../light/LIGHT_DATABASE_SPEC.js";
11
11
  import { MESHES_TABLE } from "../../../../scene/chunk_scene_database_access.js";
12
12
  import { CSM_CASTER_RANGE_UNKNOWN } from "../CSM_CASTER_RANGE_STRUCT.js";
@@ -124,13 +124,13 @@ fn main(
124
124
 
125
125
  // one atomic per subgroup rather than one per row, and none where no row was inside
126
126
  if lane_id == 0u && near < RANGE_UNKNOWN {
127
- aabb_atomic_min_f32(&ranges[settings.base + c], near);
127
+ atomic_min_f32_storage(&ranges[settings.base + c], near);
128
128
  }
129
129
  }
130
130
  }
131
131
  `, [
132
132
  MESHES_TABLE.chunk_iterate(WG),
133
- chunk_aabb_atomic_f32,
133
+ chunk_atomic_min_f32_storage,
134
134
  ]);
135
135
 
136
136
  accumulate_body.addExtension(WebGPUExtensionType.Subgroups);
@@ -1,59 +0,0 @@
1
- import { CodeChunk } from "../../../shader/compiler/CodeChunk.js";
2
-
3
- /**
4
- * Per-element f32 atomic min/max via CAS-spin over a `atomic<u32>`
5
- * storage cell, bitcast through f32 for comparison.
6
- *
7
- * WGSL has native u32/i32 atomics only. For float min/max the
8
- * sign-bit-flip encoding (cheaper, only `atomicMin`/`atomicMax`
9
- * needed) is a tempting alternative, but it leaves the buffer in
10
- * "encoded" form during the dispatch chain — readers in the same
11
- * frame would need to know whether the value is encoded or decoded.
12
- * The CAS-spin keeps the storage in plain f32 the whole time, which
13
- * matches what the rasterizer's meshlet-culling shader already
14
- * expects in `meshlet.bounds_box`.
15
- *
16
- * Contention model: every contributor reads → compares → conditionally
17
- * writes. When the existing value already bounds the contribution
18
- * (typical for late contributors as the box settles), the comparison
19
- * exits early without any CAS. The worst case is many threads
20
- * hammering the same slot in the first few iterations; the loop is
21
- * bounded by collisions, not by thread count.
22
- *
23
- * `value` is treated as a normal f32 — NaNs are not handled (vertex
24
- * positions are never NaN in correctly-loaded geometry). +inf / -inf
25
- * are valid (they're our scatter sentinels).
26
- *
27
- * @type {CodeChunk}
28
- */
29
- export const chunk_aabb_atomic_f32 = CodeChunk.from(
30
- //language=WGSL
31
- `
32
- fn aabb_atomic_min_f32(cell: ptr<storage, atomic<u32>, read_write>, value: f32) {
33
- loop {
34
- let old_u = atomicLoad(cell);
35
- let old_f = bitcast<f32>(old_u);
36
- if value >= old_f {
37
- break;
38
- }
39
- let result = atomicCompareExchangeWeak(cell, old_u, bitcast<u32>(value));
40
- if result.exchanged {
41
- break;
42
- }
43
- }
44
- }
45
-
46
- fn aabb_atomic_max_f32(cell: ptr<storage, atomic<u32>, read_write>, value: f32) {
47
- loop {
48
- let old_u = atomicLoad(cell);
49
- let old_f = bitcast<f32>(old_u);
50
- if value <= old_f {
51
- break;
52
- }
53
- let result = atomicCompareExchangeWeak(cell, old_u, bitcast<u32>(value));
54
- if result.exchanged {
55
- break;
56
- }
57
- }
58
- }
59
- `);