Skip to content

Add native WGSL (genwgsl) shader generator with dedicated data library - #2996

Open
ashwinbhat wants to merge 42 commits into
AcademySoftwareFoundation:mainfrom
autodesk-forks:bhata/wgsl_generator_with_library
Open

Add native WGSL (genwgsl) shader generator with dedicated data library#2996
ashwinbhat wants to merge 42 commits into
AcademySoftwareFoundation:mainfrom
autodesk-forks:bhata/wgsl_generator_with_library

Conversation

@ashwinbhat

@ashwinbhat ashwinbhat commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

The current glsl to wgsl pathway offered in MaterialX is not suitable for runtime systems.
There is an elaborate pipeline from MaterialX> GLSL -> SPIR-V -> WGSL -> [Browser]. The browser internally might convert WGSL -> SPIRV before creating byte code.

The transplied WGSL is verbose and difficult to read. The GLSL data structures are flattened sometimes due to the SPIR-V path.

This Introduces a C++ MaterialXGenWgsl backend with genwgsl shader target that has a a dedicated WGSL data library.

The WGSL data library is pre-transpiled using a helper script (glsl_to_wgsl.py) that uses Naga to create the shader nodes in WGSL data library

The MaterialX three.js viewer backend now uses WgslShaderGenerator. Unlike GLSLES, three.js does not support direct raw shaders. therefore we have an adapter to create Three.js TSL.

Note about AI use:
The script glsl_to_wgsl.py was authored using AI tools that uses naga and then performs cleanup of the shader code using simple lookup.
Screenshots:
chess-set
materialx-webgpu-collage

WebGPU viewer is host here: https://ashwinbhat.github.io/MaterialX/index-webgpu.html

@jstone-lucasfilm

Copy link
Copy Markdown
Member

Thanks for this substantial contribution, @ashwinbhat, and indeed this seems more promising than your previous #2983.

Before moving forward with detailed review, I think it's worthwhile to consider a simplification of this idea:

What if the GLSL-to-WGSL transformation step occurred only in GitHub CI, rather than on the developer's machine? The machine-generated WGSL files would then become a derived artifact rather than a maintained set of code in the repository. The source GLSL files would remain the single source of truth for hardware shading, CI would run the transpiler on each PR and package the results into our build artifacts, and developers not working with WGSL could rely upon CI to validate that they haven't broken the WGSL target with their changes. Since the generated files would no longer be committed, drift between the two libraries becomes impossible by construction, rather than managed through community conventions.

On the build side, we might then default MATERIALX_BUILD_GEN_WGSL to OFF, with GitHub CI providing Naga and setting it to ON, so that ordinary source builds require no Python or Rust toolchain. The hand-written portion of the library (the lib/ helpers, the image and light nodes, and the adapted BSDFs) would remain in the repository as maintained code, analogous to the small libraries of hand-written code in MSL and Slang today.

This direction would preserve what I see as the core contributions of this PR -- the native HwShaderGenerator-derived backend, the resource binding model, and the WebGPU viewer integration -- while shifting the transpiled library from maintained code to build infrastructure. Would you be open to exploring this path?

@ashwinbhat

Copy link
Copy Markdown
Contributor Author

Thanks for this substantial contribution, @ashwinbhat, and indeed this seems more promising than your previous #2983.

Before moving forward with detailed review, I think it's worthwhile to consider a simplification of this idea:

What if the GLSL-to-WGSL transformation step occurred only in GitHub CI, rather than on the developer's machine? The machine-generated WGSL files would then become a derived artifact rather than a maintained set of code in the repository. The source GLSL files would remain the single source of truth for hardware shading, CI would run the transpiler on each PR and package the results into our build artifacts, and developers not working with WGSL could rely upon CI to validate that they haven't broken the WGSL target with their changes. Since the generated files would no longer be committed, drift between the two libraries becomes impossible by construction, rather than managed through community conventions.

On the build side, we might then default MATERIALX_BUILD_GEN_WGSL to OFF, with GitHub CI providing Naga and setting it to ON, so that ordinary source builds require no Python or Rust toolchain. The hand-written portion of the library (the lib/ helpers, the image and light nodes, and the adapted BSDFs) would remain in the repository as maintained code, analogous to the small libraries of hand-written code in MSL and Slang today.

This direction would preserve what I see as the core contributions of this PR -- the native HwShaderGenerator-derived backend, the resource binding model, and the WebGPU viewer integration -- while shifting the transpiled library from maintained code to build infrastructure. Would you be open to exploring this path?

Thanks @jstone-lucasfilm for the valuable feedback. I've removed the generated wgsl files and moved the generation to GitHub CI.

The MaterialX WebGPU viewer is hosted here: https://ashwinbhat.github.io/MaterialX/index-webgpu.html

@kwokcb kwokcb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow all the remapping from GLSL to WGSL but assume it's good.

For building:

  • It would still be useful to have a script or set of instructions on how to build locally if you want to change and test anything. e.g. if I modify or add a GLSL file how do you rebuild it to test.

For release a few questions

  • There is a comment about switching between web pages
    • Curious why are there 2 web pages -- seems like it's the simplest way to webpack ?
  • Are there any issues with instantiating both a WebGL or WebGPU generators at the same time if desired on web ?
  • Are all the generated WGSL files installed (visible to look at) in the release ?

* Configure genContext for WebGPU material generation.
* @returns {boolean} Whether the surface is transparent.
*/
function configureWebGPUGenContext(mx, gen, genContext, elem)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is getting large and hard to read. Would it make sense to have a separate file for WebGPU specific functions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can move it into a separate file as a future refactoring step

// Rendering backend, selected at build time per bundle (see webpack.config.js). The WebGL
// bundle uses classic THREE.WebGLRenderer + RawShaderMaterial (ESSL); the WebGPU bundle
// aliases `three` to three/webgpu and uses WebGPURenderer + NodeMaterial (WGSL via the
// upstream WgslShaderGenerator). A toggle switches between the two HTML pages.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why 2 web pages ? Does it make sense to instead do a run-time switch, or allow for 2 different canvases with different associated renderers ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out how to get the UX and other elements to work seamlessly. We can improve this in future if the core generator is approved.

python python/Scripts/generateshader.py resources/Materials/Examples --target glsl --validator C:/vcpkg/installed/x64-windows-release/tools/glslang/glslangValidator.exe
python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --target essl --validator C:/vcpkg/installed/x64-windows-release/tools/glslang/glslangValidator.exe
python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --target vulkan --validator C:/vcpkg/installed/x64-windows-release/tools/glslang/glslangValidator.exe
python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --target wgsl --validator "C:/vcpkg/installed/x64-windows-release/tools/glslang/glslangValidator.exe --target-env vulkan1.3 --quiet"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this removed ? If wgsl is available should it not be run still.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current validator uses glsllang that does not support wgsl syntax.
Note that the current generator was generating "glsl" code but the new one will generate "wgsl" code

- name: Create Archive Name
run: echo "MATERIALX_ARCHIVE=MaterialX-${RELEASE_TAG//v}" >> $GITHUB_ENV

- name: Install Python

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if it's possible to call into a shared setup from main and release ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a config that is no Python based, so I kept it separate.

@ashwinbhat

ashwinbhat commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

I don't follow all the remapping from GLSL to WGSL but assume it's good.

For building:

  • It would still be useful to have a script or set of instructions on how to build locally if you want to change and test anything. e.g. if I modify or add a GLSL file how do you rebuild it to test.

I'll a document in the developer docs that adds steps/instructions and details about the remapping.

  • Are there any issues with instantiating both a WebGL or WebGPU generators at the same time if desired on web ?
  • Curious why are there 2 web pages -- seems like it's the simplest way to webpack ?
    WebGL and WebGPU are separate shader generators so it should work. The reason for separate pages is because the workflows are different

WebGL : THREE.WebGLRenderer + RawShaderMaterial (ESSL)
WebGPU :THREE.WebGPURenderer + TSL/NodeMaterial (WGSL)

  • Are all the generated WGSL files installed (visible to look at) in the release ?

Yes they should be part of zip, there should be a way to verify this.

@ashwinbhat

Copy link
Copy Markdown
Contributor Author

@kwokcb I added a developer doc which will be helpful to answer some of your question about local developer workflow

@jstone-lucasfilm

Copy link
Copy Markdown
Member

Thanks for turning this around so quickly, @ashwinbhat. Moving the node fragments to CI generation is exactly the direction I was hoping for, and the WebGPU viewer looks great.

One remaining piece I'd like to resolve before diving into detailed review: the BSDF microfacet library still lands in the repository as a committed, hand-written WGSL copy of genglsl/lib/mx_microfacet*.glsl. This hardware shading library is arguably one of the most critical parts of the MaterialX project, and I'd rather not have two references that readers must navigate and keep in agreement.

The goal I'd propose is that the GLSL microfacet library remains the single source of truth, and the WGSL form is a CI-generated artifact that is never committed -- the same treatment the WGSL node fragments now get. From reading glsl_to_wgsl.py, this looks within reach: the main obstacle (WGSL's lack of overloading) is already solved for call sites via CALL_MAP, and the genuinely WGSL-specific residue in the lib is small.

Here's a proposed set of next steps to consider:

  1. Extend the transpiler to also generate the lib/ helpers, so nothing under genwgsl/lib/ is committed.
  2. Upstream any genuine divergences into the GLSL source, e.g. mx_orthonormal_basis has already drifted (the sign branch moved to fix a tangent-frame artifact), and that fix currently lives only in the WGSL copy.

It's worth noting that generation from GLSL makes drift like the mx_orthonormal_basis case impossible by construction. In the longer term I'd see render-comparison coverage for the WGSL target (building on our nightly MSL/OSL comparisons) as a natural validation gate, though that's future context rather than a request for this PR.

Does this seem like a reasonable path to you?

@ashwinbhat

ashwinbhat commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for turning this around so quickly, @ashwinbhat. Moving the node fragments to CI generation is exactly the direction I was hoping for, and the WebGPU viewer looks great.

One remaining piece I'd like to resolve before diving into detailed review: the BSDF microfacet library still lands in the repository as a committed, hand-written WGSL copy of genglsl/lib/mx_microfacet*.glsl. This hardware shading library is arguably one of the most critical parts of the MaterialX project, and I'd rather not have two references that readers must navigate and keep in agreement.

The goal I'd propose is that the GLSL microfacet library remains the single source of truth, and the WGSL form is a CI-generated artifact that is never committed -- the same treatment the WGSL node fragments now get. From reading glsl_to_wgsl.py, this looks within reach: the main obstacle (WGSL's lack of overloading) is already solved for call sites via CALL_MAP, and the genuinely WGSL-specific residue in the lib is small.

Here's a proposed set of next steps to consider:

  1. Extend the transpiler to also generate the lib/ helpers, so nothing under genwgsl/lib/ is committed.
  2. Upstream any genuine divergences into the GLSL source, e.g. mx_orthonormal_basis has already drifted (the sign branch moved to fix a tangent-frame artifact), and that fix currently lives only in the WGSL copy.

It's worth noting that generation from GLSL makes drift like the mx_orthonormal_basis case impossible by construction. In the longer term I'd see render-comparison coverage for the WGSL target (building on our nightly MSL/OSL comparisons) as a natural validation gate, though that's future context rather than a request for this PR.

Does this seem like a reasonable path to you?

hi @jstone-lucasfilm it seems reasonable but from my findings, lib/ helpers are very difficult to transpile using naga as they are missing a lot of context and other data structures that is emitted by shader generators.

I agree it makes sense update the mx_orthonormal_basis

It might be more valuable to have wgsl specific implementations of some microfacet library functions that are better authored for Web instead of porting the glsl versions as-is. Naga does create very verbose shaders that increases the overall source length.

@ashwinbhat

Copy link
Copy Markdown
Contributor Author

Hi @jstone-lucasfilm
Based on your suggestion, I have modified the WGSL "pre-transpile" pipeline. The subset of libraries for WGSL now are math and fragments that need texture support.
The transpile is done via naga. Naga, produces "correct" but very verbose, hard to read WGSL due to static single assignment (SSA) style code with temporaries, hoisted var declarations, redundant nested blocks, mangled identifier names.
I've introduced a helper that "cleans-up" the naga transpiled code by understanding the WGSL grammar. It doesn't regenerate code, only deletes/renames/reindents etc.
Let me know if this are suitable changes and towards the direction you envision.

@jstone-lucasfilm

Copy link
Copy Markdown
Member

Thanks for turning this around, @ashwinbhat. Generating the microfacet library from genglsl is exactly the direction I was hoping for, and the cleanup pass makes the output far more readable than raw Naga output.

Two issues I'd like to resolve before diving into detailed review:

The first is the hand-written library remaining under stdlib/genwgsl/lib, still around 1,200 lines with mx_noise alone at 716. The skip list justifies these as "whole dir maintained by hand (was HANDWRITTEN_LIB_DIRS)", which reads as a record of the tool's earlier state rather than a technical constraint, and differs in character from the pbrlib and node entries above it. Looking through the sources, only mx_math appears genuinely blocked and only in part -- the mx_mod overloads and mx_isinf need hand-writing, while mx_matrix_mul, mx_square and mx_srgb_encode are ordinary GLSL, so perhaps an mx_math_platform.wgsl split mirroring mx_shadow_platform. mx_noise, mx_flake, mx_hsv and mx_geometry have no samplers, no $-tokens and no derivatives, and mxgenwgsl.py already carries a LIB_USE_SIBLING_BODIES entry for mx_flake and mx_noise that transpileLibs can never reach, since isHandWrittenLib short-circuits first. mx_hextile I'd set aside for now. Its dFdx and dFdy calls are only a spelling difference -- MslShaderGenerator and SlangShaderGenerator already map those tokens, and we could do the same for WGSL or wrap them in mx_math alongside mx_mod and mx_isinf. But WGSL enforces uniform control flow for derivatives as a hard error rather than leaving it undefined, so that one deserves its own look.

Was the stdlib pass attempted after the lib refactor, or is this the original setting carried forward? I'd suggest dropping those four from skip_transpile.txt and running the tool: if they transpile, close to a thousand lines leave the repository, and if they don't, we learn the reason the skip list is currently missing. My concern is the one we discussed for the microfacet library -- mx_noise has 58 function definitions whose numerics must match genglsl exactly, and coverage matches today just as it did for the microfacet library right up until mx_orthonormal_basis drifted.

The second is mx_orthonormal_basis itself, where I think you've found a real bug that isn't WGSL-specific. mx_generate_prefilter_env builds its normal from the latlong projection, where N.z is cos(latitude) * cos(longitude). That crosses zero at longitude +/-90 degrees, so the N.z < 0 branch runs down two vertical lines of the environment map, with the tangent frame flipping across each one. That flip affects GLSL, ESSL, MSL and Slang today, and is latent only because FIS is our default environment method. Moving the branch to -0.9999999 hides it on the map's wrap seam rather than removing it, and it costs precision: the original sign choice exists to keep sign + N.z away from zero, and the new threshold lets it cancel in f32, so the basis stops being orthonormal near that seam. Since mx_generate_prefilter_env already has uv in hand, I'd suggest building the frame from the latlong parameterization itself -- a tangent along the longitude direction, with the bitangent from the cross product -- which is continuous across the whole map and fixes the seam for every hardware target at once.

Could we take that as its own PR against main, with before-and-after images? The same for the tf_thickness / tf_ior rename -- I'm not opposed to the names, but it's a change to a public data library and I'd rather it stood on its own than rode along as a transpiler simplification. Together those would let this PR stop modifying pbrlib/genglsl entirely.

@ashwinbhat

Copy link
Copy Markdown
Contributor Author

Thanks @jstone-lucasfilm for the detailed feedback on the stdlib genwgsl/lib helpers.

Experiment results — all four transpile cleanly from genglsl

Removed mx_noise, mx_flake, mx_hsv, and mx_geometry from skip_transpile.txt and ran mxgenwgsl.py. All four transpile with zero failures (naga 30.0.0). The sibling-body path for mx_noise/mx_flake works as intended now that isHandWrittenLib no longer short-circuits them.

Committed hand-written copies of those files (~1,000 lines) have been deleted; CI/local transpile regenerates them from genglsl.

mx_math split (mx_math_platform.wgsl)

Following your suggestion, WGSL-specific helpers now live in a small hand-written platform file:

  • mx_mod_* overloads (GLSL #define mx_mod mod has no WGSL equivalent)
  • mx_isinf (naga rejects GLSL isinf())
  • unsuffixed mx_matrix_mul(mat4x4f, vec4f) for Hw geometric nodes

The rest of mx_math.glsl (mx_matrix_mul overloads, mx_square, mx_srgb_encode) transpiles from genglsl; generated mx_math.wgsl #includes the platform file.

Also moved to generated

mx_transform_uv and mx_transform_uv_vflip (trivial one-liners) — same treatment.

Intentionally still hand-written in stdlib/lib

  • mx_hextile.wgsl — deferred (dFdx/dFdy uniform-control-flow; as discussed)
  • mx_math_platform.wgsl — platform gaps only (~45 lines)

Validation

  • Full mxgenwgsl.py run: 14 lib + 98 node files, 0 failures
  • test_mxwgslcleanup.py: all tests pass
  • JsMaterialXGenShader builds with MATERIALX_BUILD_GEN_WGSL=ON

Ready for detailed review. The mx_orthonormal_basis / tf_* items remain scoped for separate PRs as you suggested.

@ashwinbhat

ashwinbhat commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

hi @jstone-lucasfilm PR 3023 tackles the mx_generate_prefilter_env issue. We don't need the tf_* renames, I had added them for clarity but can skip them.

Introduce a native WebGPU/WGSL shader generator as a new "genwgsl" target,
replacing the old GLSL-rewriting WgslShaderGenerator in MaterialXGenGlsl.

- MaterialXGenWgsl is HwShaderGenerator based generator that emits complete
  standalone WGSL so that there is no transpiling required.
- MATERIALX_BUILD_GEN_WGSL option to enable this project
- Add js and python binding.

The main addition is a helper python script that uses naga to convert glsl data library to wgsl data library. The script is not a general purpose converter.
It run in few phases
1. wraps a glsl fragment into a shader and uses naga to generate wgsl.
2. the generated wgsl is cleaned up to keep doc comments, and maintian similar code style as glsl
3. Add a banner to alert future authors to not edit the wgsl directly.
Note: only stright forward simple conversion is done. there are cases with glsl overloads in math and lib that have hand written wgsl counterparts.
see source\MaterialXGenWgsl\tools\glsl_to_wgsl.py and associated readme.md
Added genwgsl library what was transpiled offline.
Add an opt-in `MATERIALX_GENERATE_WGSL_LIBRARY` build option that regenerates and validates the library at build time.
This needs Cargo the official build tool and package manager for Rust.
MATERIALX_CARGO_PATH can be set to define path to Cargo.
See https://doc.rust-lang.org/cargo/getting-started/installation.html
Render MaterialX materials over WebGPU by using the native WgslShaderGenerator
The main change here to support Three.js wgslFn: WGSL function node, a TSL function.
https://threejs.org/docs/#FunctionNode

- mxtsladapter.js: converts the complete WGSL module to TSL-portable format
- wgslmanifest.js: reconstruct the reflection manifest in JS from the generated WGSL +
  Shader uniform ports
- viewer.js/index.js: dual WebGL (ESSL) + WebGPU (WGSL) backends selected per bundle;
  WebGPU path uses WebGPURenderer + the TSL bridge.
We might look into naga-cli
These will be generated as part of the CI.
Note: there are still a few wgsl files that are hand written since their glsl counterparts use overloads, defines and storage qualifiers
- Add generate_wgsl rule that installs naga-cli and updates data library with wgsl
TODO: check if cargo is availble on CI runners
mxwgslcleanup uses tree-sitter WGSL grammar parser and only available on python 3.10+
Move mx_noise, mx_flake, mx_hsv, mx_geometry, mx_math, and mx_transform_uv*
CI transpilation from genglsl.
Add mx_math_platform for WGSL :mx_mod,mx_isinf and mat4*vec4 mx_matrix_mul
tf_thickness was renamed to thinfilm_thickness for clarity. Restoring this
…nd naga via cmake

Steps:
1. Activate Emscripten
call C:\emsdk\emsdk.bat install 4.0.8
call C:\emsdk\emsdk.bat activate 4.0.8

2. Build Emscripten bindings with WGSL

cd /d C:\source\MaterialX
cmake -S . -B javascript/build -DMATERIALX_BUILD_JS=ON -DMATERIALX_BUILD_GEN_WGSL=ON -DMATERIALX_EMSDK_PATH=C:\emsdk -G Ninja
cmake --build javascript/build --target install --config RelWithDebInfo --parallel 8

Verify WASM output exists:
 javascript/build/bin/JsMaterialXCore.{js,wasm}
 javascript/build/bin/JsMaterialXGenShader.{js,wasm,data}

3. Install JS dependencies
cd javascript
npm install

4. Build the viewer bundles
cd MaterialXView
npm run build

5. Run locally
npm run start
Then open http://localhost:8080/index-webgpu.html
@ashwinbhat
ashwinbhat force-pushed the bhata/wgsl_generator_with_library branch from 714c93f to 43eb7ce Compare August 12, 2026 20:40
@jstone-lucasfilm

Copy link
Copy Markdown
Member

Thanks for your continued work on this, @ashwinbhat. I've now been through the full branch, and the structure matches what we discussed in July: a native HwShaderGenerator-derived backend, a generated rather than committed node library, and a working WebGPU viewer. Before moving into detailed review, there are a few items I'd like to resolve, since each either diverges from what we agreed earlier in the thread or leaves the WGSL target unverified:

  1. The -0.9999999 branch in mx_orthonormal_basis is still present in pbrlib/genglsl/lib/mx_microfacet.glsl. Now that Fix prefilter env tangent-frame seam and add mx_isinf wrapper #3023 has landed and is this PR's base, the prefilter seam is fixed at its source, so this change no longer serves a purpose for WGSL, and it would alter the tangent frame for all hardware shader generators. Reverting it lets the PR stop modifying pbrlib/genglsl entirely, as we discussed.

  2. Four hand-written pbrlib helpers have no consumer: mx_generate_prefilter_env.wgsl, mx_generate_albedo_table.wgsl, mx_shadow.wgsl, and mx_shadow_platform.wgsl. WgslShaderGenerator never references hwWriteEnvPrefilter, hwWriteAlbedoTable, or hwShadowMap, and the viewer doesn't either, so these files are unreachable today. Additionally, the prefilter file has already diverged from its GLSL source, still building its frame with mx_orthonormal_basis rather than the latlong tangent from Fix prefilter env tangent-frame seam and add mx_isinf wrapper #3023. I'd suggest removing them from this PR and reintroducing them together with the hwWriteEnvPrefilter / hwWriteAlbedoTable / hwShadowMap branches in WgslShaderGenerator and a viewer pass that exercises them, so that every hand-written file in skip_transpile.txt is reachable by some generator option and covered by the same validation as the rest of the library.

  3. The color4 variant in stdlib/genwgsl/mx_hextiledimage.wgsl averages alpha and applies Schlick gain to the average, which is the GLSL behavior from before Fixes alpha blending issue in hextiledimage_color4 #2786; main now weights alpha with mx_hextile_compute_blend_weights(vec3(1.0), ...). This is the drift case we've been working to make structurally impossible, so it's worth fixing here, and it may be useful to note in skip_transpile.txt which GLSL revision each hand-written file mirrors. Relatedly, WgslShaderGenerator::emitConstants hardcodes M_FLOAT_EPS = 1e-6 while mx_math.glsl defines 1e-8, and since naga inlines the GLSL value into transpiled code, a generated shader currently carries both epsilons.

  4. Nothing in CI validates that the generated WGSL compiles. The Windows generateshader.py --target wgsl --validator step was removed without a replacement, so naga is installed on every job but never run against a generated shader, and the new [genwgsl] test checks only for the @vertex and fn vertexMain substrings in a single material. Two additions would close this gap: passing naga as the validator for the wgsl target in the existing shader validation step, and a WgslShaderGeneratorTester over the TestSuite and Examples in the pattern of the MSL and Slang tests, so that the prefilter, transmission, and hextile paths are exercised rather than standard_surface_default alone under FIS.

  5. In release.yml, the workflow runs mxgenwgsl.py into the working tree and then calls git archive ... ${RELEASE_TAG}, which exports only tracked files at the tag. The generated WGSL therefore won't reach the release archives that the Developer Guide says it ships in, and the archive step will need to include the generated tree explicitly.

  6. The CMake fallback runs cargo install naga-cli without the --locked --version pin that CI uses, so wheel builds on runners with cargo available will compile an unpinned naga; matching the CI pin keeps the generated library reproducible. And since -DMATERIALX_BUILD_GEN_WGSL=ON writes roughly 110 files into libraries/, a .gitignore rule for generated genwgsl/**/*.wgsl (with negations for the hand-written set) would keep developers from committing them by accident.

I have a few smaller notes on documentation consistency and unused code that can wait for detailed review, and one larger design question about the per-uniform binding model against WebGPU's default uniform buffer limit that I'd like to discuss separately once these are in. Does this seem like a reasonable set of next steps?

@jstone-lucasfilm

Copy link
Copy Markdown
Member

Thanks for the fast turnaround on the August items, @ashwinbhat, and I think we're ready to start on the detailed review that we discussed above.

The first topic I'd like to work through is the hand-written files still listed in skip_transpile.txt, since it's the same concern we resolved for the microfacet library in July and I think the remaining cases are within reach of the same solution. Setting aside the image nodes, the light shaders, and mx_math_platform.wgsl, which are small and genuinely WGSL-specific, six files remain as maintained copies of GLSL shading code: the three mx_environment_*.wgsl helpers, lib/mx_hextile.wgsl, and the two hextile nodes, about 400 lines in total.

One of these has already drifted. mx_environment_fis.wgsl computes its sample PDF by hand as mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV), while mx_environment_fis.glsl has called mx_ggx_VNDF_reflection_PDF since #2779. The two are numerically identical today, so nothing renders differently, but this is the pattern we saw with mx_orthonormal_basis: the hand copy captures GLSL at the moment it was written, and the next change to the PDF on main won't reach WebGPU. That it happened in the FIS integrator, despite the attention this file has had in the thread, is I think the strongest argument for generating it.

Reading mxgenwgsl.py, I don't believe naga is the obstacle. The skip list cites sampler bindings for the environment files and dFdx/dFdy for hextile, but the lib pass already stubs $texSamplerSignature and $albedoTable when it generates mx_microfacet_specular.wgsl, and NAGA_LIB_STUBS already carries an entry for every $env* token, so the parse side of the problem is solved. Likewise the hand-written mx_hextile.wgsl calls dpdx and dpdy at exactly the point the GLSL does, so the hand port doesn't change anything about uniform control flow. What actually blocks the environment files is that NAGA_LIB_STUBS is lossy: $envRadianceSamples becomes 16, $envMatrix becomes mat4(1.0), and the mx_latlong_map_lookup rewrite discards the direction and transform arguments, so the tokens can't be restored afterward. The node path already solves this with reversible MTLXTOK_ sentinels.

With that in mind, here's what I'd propose:

  1. Make the lib token expansion reversible, using the same sentinel mechanism as the node path, and drop the three mx_environment_*.wgsl files from skip_transpile.txt. This removes the FIS drift by construction, and mx_environment_prefilter and mx_environment_none follow for free.

  2. Add a reversible stub for the split texture and sampler signature, so that $texSamplerSignature and the textureGrad / texture calls round-trip through naga, and generate lib/mx_hextile.wgsl, the two hextile nodes, and the six image nodes the same way. If a specific naga error does block hextile, I'd suggest recording that error in skip_transpile.txt in place of the current rationale, so the next reader knows what would need to change.

  3. Move the hand-written mx_latlong_map_lookup body out of applyLibWgslPatches. At the moment that one function is maintained in two places, the GLSL source and the transpiler's Python patch table, with the generated output carrying whichever one was edited last. Either mx_math_platform.wgsl or the generator's own texture-lookup emission would be a better home than a regex replacement.

  4. Take a look at whether WgslSurfaceNode can be folded back into HwSurfaceNode. It's a near-verbatim copy with the shadow and ambient-occlusion branches removed and the syntax adjusted, while MSL and Slang register HwSurfaceNode directly. The lighting integration loop is as central to hardware shading as the microfacet library, and the PR has already shown the right pattern for this with Syntax::getLibraryFunctionName in the geometric nodes, so I'm hopeful the remaining differences can be handled through similar hooks.

I have a handful of smaller notes on the transpiler, CI, and packaging that I'll hold until these are in, so we can keep this thread focused.

Thanks for the great work on this PR so far, and let me know if these proposed improvements sound reasonable to you!

@ashwinbhat ashwinbhat self-assigned this Sep 10, 2026
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.

3 participants