Skip to content

HLSL backend (codegen + D3D11 renderer)#2897

Open
soufianekhiat wants to merge 17 commits into
AcademySoftwareFoundation:mainfrom
soufianekhiat:main
Open

HLSL backend (codegen + D3D11 renderer)#2897
soufianekhiat wants to merge 17 commits into
AcademySoftwareFoundation:mainfrom
soufianekhiat:main

Conversation

@soufianekhiat
Copy link
Copy Markdown

@soufianekhiat soufianekhiat commented May 7, 2026

HLSL GLSL Diff (x8)
hlsl glsl diff

Adds MaterialXGenHlsl and MaterialXRenderHlsl, + Python and JS bindings, plus the matching libraries under libraries/{stdlib,pbrlib,nprlib,lights}/genhlsl/. Existing GLSL / MSL / Slang code is untouched.

The codegen reuses the GLSL .glsl impl files via file="../genglsl/..." and runs a small post-emit pass for the GLSL→HLSL deltas (mix→lerp, dFdx→ddx, vector-splat C-cast, texture()→mx_texture_sample, etc.). Two HLSL-native helpers (mx_math.hlsl, mx_texture.hlsl) fill in the rest.

The renderer is D3D11. FXC for SM 5.x, DXC loaded dynamically for SM 6+. HlslMaterial owns per-stage cbuffers with a CPU mirror so partial uniform writes don't clobber neighbours — D3D11 cbuffers are stateful, no glProgramUniform analogue. HlslTextureHandler is an ImageHandler subclass. HlslRenderer auto-binds camera, lights, env, file textures and multi-mesh geometry from reflection.

Tests: 7 cases / 3369 assertions in [genhlsl], 19 / 213 in [renderhlsl]. Headless CI safe - every D3D-touching case skips
cleanly when tryCreateContext() returns null. The full FXC compile sweep (~22 min) is tagged [!slow] so quick CI runs can skip it.

Validation: 31 materials rendered side-by-side against GLSL on the shaderball (StandardSurface, OpenPBR, glTF PBR, Disney, SimpleHair), plus the 15-material chess_set scene. All visually identical; 30/31 single-material RMSE under 2 on the 0-255 scale. See Compare.

Build: MaterialXGenHlsl cross-platform, MaterialXRenderHlsl gated on WIN32. Stages dxcompiler.dll + dxil.dll from the Windows SDK.

Why not "just emit HLSL with Slang"?

Slang would give us HLSL source, not a D3D11 renderer - we'd still need most of MaterialXRenderHlsl. And FXC/DXC reflection round-trips MaterialX uniform names directly with native emit; through Slang they get mangled, breaking the per-uniform setVariable API that GLSL/MSL already provide.

Disclosure: This PR was created assicted with Claude Opus 4.7.

Cross-platform HLSL codegen as a sibling of GenGlsl/GenMsl/GenSlang.
Reuses GLSL node implementations via a small post-emit pass: three
regex rewrites (single-arg vector splat, texture() to mx_texture_sample,
ClosureData return constructor) plus a token table (mix->lerp,
dFdx/dFdy->ddx/ddy, mod->fmod, fract->frac, inversesqrt->rsqrt).

Adds:
  - source/MaterialXGenHlsl/{HlslShaderGenerator,HlslSyntax}.{h,cpp}
  - libraries/{stdlib,pbrlib,nprlib,lights}/genhlsl/*.mtlx
  - libraries/stdlib/genhlsl/lib/{mx_math,mx_texture}.hlsl
  - libraries/targets/genhlsl.mtlx
  - source/MaterialXTest/MaterialXGenHlsl/* (codegen tests)
  - CHANGELOG.md entry
Mirrors MslResourceBindingContext. When attached to GenContext, splits
uniforms into Private/Public/LightData cbuffers with explicit register
annotations and suppresses inline LightData struct emission (the
binding context emits it once at the cbuffer level).
…ction

Renderer-free compile + reflect entry point. FXC via d3dcompiler_47
(default, SM 5.x); DXC via dxcompiler.dll loaded dynamically (SM 6.x).
Reflection unifies on HlslResourceBinding for both DXBC and DXIL.
Suitable for headless CI to validate that generated HLSL compiles
without requiring a D3D11 device.
HlslContext owns ID3D11Device + DeviceContext with hardware-or-WARP
fallback and tryCreateContext() for headless test environments.
HlslFramebuffer wraps an RTV + DSV + readback path so tests can verify
real pixels. End-to-end draw test compiles a trivial VS+PS, draws a
fullscreen triangle, and reads back the centre pixel.
Bridges D3D11's stateful cbuffer model to MaterialX's per-uniform
update pattern. One ID3D11Buffer per stage per cbuffer with a CPU
mirror so partial writes do not clobber unrelated members. Reflection-
driven member offset lookup (lookupVariableOffset) so callers can name
'u_worldMatrix' and get back its byte offset inside vertexCB.

Includes a generated-shader test that compiles standard_surface_carpaint,
allocates the reflection-driven cbuffer pool, and draws into the
framebuffer to verify the pixel shader actually executed.
SRV + sampler cache keyed by Image::getResourceId. Subclassing
ImageHandler gives Python clients the full bindImage / unbindImage /
releaseRenderResources interface. getBoundSrv / getBoundSampler expose
the COM pointers so the renderer can bind t# / s# slots without
re-walking the cache.
Per-frame validateRender walks the program's reflected bindings and
auto-binds: camera/world matrices into vertexCB; lighting scalars
into pixelCB; environment radiance/irradiance via LightHandler;
file textures from PUBLIC_UNIFORMS via ImageHandler; per-light
parameters via reflection's indexed-name lookup. patchVariable lets
the renderer be cbuffer-agnostic so it works with and without an
attached HlslResourceBindingContext.
One-line CRTP-style specialisation of TextureBaker<HlslRenderer,
HlslShaderGenerator>. All baking machinery comes from the templated
base class; this subclass only wires the backend types in.
PyMaterialXGenHlsl: HlslShaderGenerator, HlslResourceBindingContext,
HlslSyntax. PyMaterialXRenderHlsl: HlslContext, HlslFramebuffer,
HlslProgram, HlslMaterial, HlslTextureHandler (as ImageHandler
subclass), HlslRenderer. D3D COM pointers are never crossed into
Python.
Embind binding for HlslShaderGenerator, gated on EMSCRIPTEN.
Generator only - no renderer in WASM since D3D11 is not portable.
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented May 7, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

Five tests were asserting raw pixel byte values that no longer hold
since the framebuffer defaults to sRGB encoding and the texture handler
builds a mip chain by default:

- ClearAndReadback, Draw Triangle, Material BindCbufferAndDraw,
  DrawsMeshWhenAvailable: opt the framebuffer into linear pass-through
  via setEncodeSrgb(false) before bind so the linear RTV is used.
- Texture SampleAndDraw: a 2x2 source under trilinear filtering blends
  mip 0 with the gray average mip 1, so corner reads don't return the
  original texels. Set ImageSamplingProperties::filterType = CLOSEST
  so the test sees raw texel values.
@soufianekhiat soufianekhiat marked this pull request as ready for review May 8, 2026 10:31
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.

1 participant