@plasius/gpu-renderer 0.2.49 → 0.2.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,22 @@ All notable changes to this project will be documented in this file.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ - **Added**
15
+ - (placeholder)
16
+
17
+ - **Changed**
18
+ - (placeholder)
19
+
20
+ - **Fixed**
21
+ - (placeholder)
22
+
23
+ - **Security**
24
+ - (placeholder)
25
+
26
+ ## [0.2.50] - 2026-10-18
27
+
28
+ - Fix fixed-wavefront dielectric queue overflow and concurrent per-path writes by selecting one probability-weighted continuation; preserve total internal reflection energy and avoid spurious metal refraction siblings.
29
+
14
30
  - **Added**
15
31
  - (placeholder)
16
32
 
@@ -1219,3 +1235,4 @@ All notable changes to this project will be documented in this file.
1219
1235
  [0.2.47]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.47
1220
1236
  [0.2.48]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.48
1221
1237
  [0.2.49]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.49
1238
+ [0.2.50]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.50
package/README.md CHANGED
@@ -231,9 +231,13 @@ console.log(plan.wavefront.queueLayout.strategy);
231
231
  Wavefront materials support authored `KHR_materials_*` factors and extension
232
232
  textures, bounded nested media, Beer-Lambert attenuation, and reference
233
233
  transport helpers for reflected/transmitted branching and spectral dispersion.
234
- GPU rays carry up to four nested media and enqueue at most two dielectric
235
- continuations per hit; deeper nesting and full spectral rendering remain
236
- bounded by those explicit limits.
234
+ GPU rays carry up to four medium identifiers and enqueue one selected
235
+ continuation per hit. Dielectric lobes are weighted by their selection
236
+ probability; total internal reflection retains unit throughput. The standalone
237
+ CPU reference helper can describe both branches, but the fixed GPU path never
238
+ enqueues an untracked sibling into its tile-sized queue. Physically qualified
239
+ nested media, rough dielectric transport and full spectral rendering remain
240
+ separate work. See [the Amber correction](docs/amber-transport-correction.md).
237
241
 
238
242
  ```js
239
243
  import {
package/dist/index.cjs CHANGED
@@ -6042,7 +6042,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord) -> ScatterResult {
6042
6042
  if (cannotRefract || transmissionSelector < transmissionReflectChance) {
6043
6043
  return ScatterResult(
6044
6044
  vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
6045
- 1.0,
6045
+ select(transmissionReflectChance, 1.0, cannotRefract),
6046
6046
  ray.mediumRefId,
6047
6047
  RAY_FLAG_DELTA_SAMPLE,
6048
6048
  SCATTER_LOBE_DELTA_REFLECTION,
@@ -6050,7 +6050,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord) -> ScatterResult {
6050
6050
  }
6051
6051
  return ScatterResult(
6052
6052
  vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
6053
- 1.0,
6053
+ 1.0 - transmissionReflectChance,
6054
6054
  transmitted_medium_ref_id(ray, hit),
6055
6055
  RAY_FLAG_DELTA_SAMPLE,
6056
6056
  SCATTER_LOBE_DELTA_TRANSMISSION,
@@ -6442,7 +6442,6 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
6442
6442
  }
6443
6443
  record_deferred_path_throughput(ray, continuationThroughput);
6444
6444
  let throughput = ray.throughput.xyz * continuationThroughput;
6445
- let transmissionBranch = scatter.lobeKind == SCATTER_LOBE_DELTA_TRANSMISSION;
6446
6445
  let nextMediumStack = select(
6447
6446
  transitioned_medium_stack(ray, hit),
6448
6447
  ray.mediumStack,
@@ -6481,66 +6480,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
6481
6480
  nextMediumStack
6482
6481
  );
6483
6482
 
6484
- // Keep both sides of a dielectric event when queue capacity permits. The
6485
- // selected continuation remains the primary sample; this bounded second
6486
- // record preserves a reflected/transmitted branch for explicit transport.
6487
- if (
6488
- scatter.lobeKind == SCATTER_LOBE_DELTA_REFLECTION ||
6489
- scatter.lobeKind == SCATTER_LOBE_DELTA_TRANSMISSION
6490
- ) {
6491
- let secondaryIndex = atomicAdd(&counters.nextCount, 1u);
6492
- if (secondaryIndex < config.tilePixelCount) {
6493
- let secondaryIsReflection = transmissionBranch;
6494
- let secondaryDirection = select(
6495
- refract_direction(ray.direction.xyz, surface_shading_normal(hit),
6496
- select(1.0 / max(hit.material.w, 1.01), max(hit.material.w, 1.01), hit.frontFace == 1u)),
6497
- reflect(ray.direction.xyz, surface_shading_normal(hit)),
6498
- secondaryIsReflection
6499
- );
6500
- let secondaryThroughput = select(
6501
- surface_delta_transmission_throughput(hit, continuationViewDirection),
6502
- surface_delta_reflection_throughput(hit, continuationViewDirection),
6503
- secondaryIsReflection
6504
- ) * segmentTransmittance;
6505
- let secondaryStack = select(
6506
- transitioned_medium_stack(ray, hit),
6507
- ray.mediumStack,
6508
- secondaryIsReflection
6509
- );
6510
- let secondaryDepth = select(
6511
- transitioned_medium_stack_depth(ray, hit),
6512
- ray.mediumStackDepth,
6513
- secondaryIsReflection
6514
- );
6515
- let secondaryMedium = select(
6516
- transmitted_medium_ref_id(ray, hit),
6517
- ray.mediumRefId,
6518
- secondaryIsReflection
6519
- );
6520
- nextQueue[secondaryIndex] = RayRecord(
6521
- ray.rayId,
6522
- ray.rayId,
6523
- ray.sourcePixelId,
6524
- ray.sampleId,
6525
- ray.bounce + 1u,
6526
- secondaryMedium,
6527
- scatter.flags,
6528
- secondaryDepth,
6529
- vec4<f32>(
6530
- offset_origin(
6531
- hit.position.xyz,
6532
- hit.geometricNormal.xyz,
6533
- hit.shadingNormal.xyz,
6534
- secondaryDirection
6535
- ),
6536
- 1.0
6537
- ),
6538
- vec4<f32>(secondaryDirection, 0.0),
6539
- vec4<f32>(ray.throughput.xyz * secondaryThroughput, scatter.pdf),
6540
- secondaryStack
6541
- );
6542
- }
6543
- }
6483
+ // One continuation per incoming path keeps the tile queue bounded and gives
6484
+ // each accumulation/deferred record a single writer. The selected dielectric
6485
+ // lobe is probability-weighted; a sibling must never be silently dropped.
6544
6486
  }
6545
6487
 
6546
6488
  @compute @workgroup_size(1)
@@ -7843,10 +7785,20 @@ fn surface_continuation_throughput(
7843
7785
  scatter: ScatterResult
7844
7786
  ) -> vec3<f32> {
7845
7787
  if ((scatter.flags & RAY_FLAG_DELTA_SAMPLE) != 0u) {
7788
+ let refractive = hit.materialKind == 2u || hit.materialKind == 3u || hit.materialExtension.z > 0.001;
7789
+ if (refractive) {
7790
+ let normal = surface_shading_normal(hit);
7791
+ let eta = select(max(hit.material.w, 1.01), 1.0 / max(hit.material.w, 1.01), hit.frontFace == 1u);
7792
+ let cosine = min(dot(viewDirection, normal), 1.0);
7793
+ let cannotRefract = eta * sqrt(max(0.0, 1.0 - cosine * cosine)) > 1.0;
7794
+ if (cannotRefract) {
7795
+ return vec3<f32>(1.0);
7796
+ }
7797
+ }
7846
7798
  if (scatter.lobeKind == SCATTER_LOBE_DELTA_TRANSMISSION) {
7847
- return surface_delta_transmission_throughput(hit, viewDirection);
7799
+ return surface_delta_transmission_throughput(hit, viewDirection) / scatter.pdf;
7848
7800
  }
7849
- return surface_delta_reflection_throughput(hit, viewDirection);
7801
+ return surface_delta_reflection_throughput(hit, viewDirection) / scatter.pdf;
7850
7802
  }
7851
7803
  if (scatter.pdf <= 0.000001) {
7852
7804
  return vec3<f32>(0.0);