Skip to content

Fix Sobol sampler and D65 illuminant for Metal GPU backend#38

Open
jkrumbiegel wants to merge 3 commits intoJuliaGraphics:masterfrom
jkrumbiegel:jk/fix-metal-sampling
Open

Fix Sobol sampler and D65 illuminant for Metal GPU backend#38
jkrumbiegel wants to merge 3 commits intoJuliaGraphics:masterfrom
jkrumbiegel:jk/fix-metal-sampling

Conversation

@jkrumbiegel
Copy link

Summary

  • Sobol sampler: Runtime NTuple indexing always returns 0 on Metal. Replace PERMUTATIONS_4WAY (nested NTuples) with bit-packed UInt32 values appended to the Sobol matrices GPU array. Also replace @nexprs unrolled loops with regular for loops (the unrolled form causes Metal compiler crashes or miscompilation with non-constant kernel arguments).
  • D65 illuminant: Same NTuple issue — the D65 spectrum was stored as NTuple{107, Float32} indexed at runtime. Add d65_values field to RGBToSpectrumTable as a GPU array, with GPU-compatible sample_d65 overloads.

How these changes were tested

  • Isolated kernel test confirms CPU and Metal produce identical Sobol sample values
  • CPU and Metal render of 3-sphere scene produce identical mean pixel values (~0.327)
  • 20-material gallery scene (glass, volumetrics, metals, coated, emissive) renders correctly on Metal
  • Tested at multiple sample counts (4, 8, 16, 32 spp)

Created with the help of Claude Code

jkrumbiegel and others added 2 commits March 1, 2026 21:02
Two issues fixed:

1. Runtime NTuple indexing returns 0 on Metal. Replace PERMUTATIONS_4WAY
   (nested NTuples indexed at runtime) with PACKED_PERMUTATIONS_4WAY
   (bit-packed UInt32 values appended to the Sobol matrices GPU array).
   lookup_permutation now takes the matrices array and uses bit shifts.

2. @nexprs unrolled loops cause Metal compiler crashes or miscompilation
   when the loop body references non-constant kernel arguments. Replace
   with regular for loops in sobol_sample.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The D65 illuminant spectrum was stored as NTuple{107, Float32} and indexed
at runtime, which returns 0 on Metal (same root cause as the Sobol fix).

Add d65_values field to RGBToSpectrumTable as a GPU array, and add
GPU-compatible sample_d65/sample_d65_spectral overloads that read from
the array instead of the NTuple constant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jkrumbiegel jkrumbiegel force-pushed the jk/fix-metal-sampling branch from 486d1af to 1f22f31 Compare March 1, 2026 20:02
@lazarusA
Copy link
Collaborator

lazarusA commented Mar 2, 2026

great!

20-material gallery scene (glass, volumetrics, metals, coated, emissive) renders correctly on Metal

would you mind sharing the code for all these?

It would be nice to test and show the output across all backends, so starting with a set of references as in Makie make sense.

@jkrumbiegel
Copy link
Author

jkrumbiegel commented Mar 2, 2026

The 20 materials test scene is in RayMakie in examples/materials.jl. And this PR goes together with JuliaGeometry/Raycore.jl#13 if you want to try it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants