Release Type: GitHub (built from source)
Version: master (reproduced against a 4.4.1 fork). New in 4.4 — the D3D11 path now round-trips SPIR-V → HLSL via SPIRV-Cross; 4.3 went straight to HLSL and never hit this.
Platform(s): Windows — Direct3D11 only. (Vulkan/D3D12 never call LegalizeForHlsl, so they don't hit the failure, though the module is still invalid.)
Describe the bug
A Texture.Sample(...) call in a vertex-stage material surface fails to compile on Direct3D11. The SDSL→SPIR-V frontend emits OpImageSampleImplicitLod for .Sample() regardless of stage, but SPIR-V forbids implicit-LOD instructions outside the Fragment / GLCompute / Mesh / Task execution models (no screen-space derivatives exist in the vertex stage). EffectCompiler then hands the invalid module to LegalizeForHlsl, which fails with a bare spvOptimizerRun InternalError — nothing validates the module first, so the real cause is hidden.
The built-in MaterialDisplacementMapFeature does not hit this: it sets context.IsNotPixelStage = true, which routes its compute-color texture graph through the ComputeColorTextureLod* shaders (SampleLevel). That workaround only covers the compute-color path (ComputeColors/ComputeTextureBase.cs); a custom SDSL shader that calls .Sample() directly is lowered by TextureMethodsImplementations.CompileSample, which is stage-unaware and always emits implicit LOD.
To Reproduce
Minimal SDSL surface that triggers it (the texture fetch is legal HLSL in a vertex shader):
shader VertexTextureFetchSurface : IMaterialSurface, PositionStream, Texturing
{
override void Compute()
{
float h = Texture0.Sample(PointSampler, float2(0.5, 0.5)).r;
streams.Position = float4(streams.Position.x, h, streams.Position.z, streams.Position.w);
}
};
…injected into a material's vertex stage via a MaterialFeature using the stock SetStreamFinalModifier API (the same hook MaterialDisplacementMapFeature uses):
context.SetStreamFinalModifier<MyFeature>(MaterialShaderStage.Vertex, new ShaderClassSource("VertexTextureFetchSurface"));
Apply that material to any mesh and run. No texture needs to be bound — the crash is in shader compilation, not rendering. (Full GameStudio repro: shader + feature + steps available if useful.)
Repo with reproduce project: https://github.com/delustra/stride-vtf-spirv-repro
Expected behavior
HLSL/FXC/DXC treat Texture.Sample(...) in a vertex shader as legal and lower it to LOD 0 (no gradients available). Stride should match that — compile and emit OpImageSampleExplicitLod with Lod = 0.0 for implicit-LOD samples reachable from a Vertex / Hull / Domain / Geometry entry point (Fragment / Compute / Mesh / Task left untouched).
Screenshots
N/A.
Log and callstacks
spirv-val on the emitted module (dumped pre-legalization):
OpEntryPoint Entry Point <id> '...VSMain_Wrapper's callgraph contains function <id> '...VertexTextureFetchSurface_Compute', which cannot be used with the current execution model:
ImplicitLod instructions require Fragment, GLCompute, MeshEXT or TaskEXT execution model: ImageSampleImplicitLod
What the engine reports today (LegalizeForHlsl):
InvalidOperationException: spvOptimizerRun failed: InternalError
at Stride.Shaders.Spirv.Tools.SpirvTools.RunAndCopy(...)
at Stride.Shades.Spirv.Tools.SpirvTools.LegalizeForHlsl(...)
at Stride.Shaders.Compiler.EffectCompiler.Compile(...)
at ...RootEffectRenderFeature.PrepareEffectPermutations
Additional context
Root cause: TextureMethodsImplementations.CompileSample (sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/TextureMethodsImplementations.cs) unconditionally emits OpImageSampleImplicitLod; there is no stage awareness on that path. Compare CompileSampleLevel (~line 139), which correctly emits OpImageSampleExplicitLod.
Sibling of #3303 — different invalid instruction (OpImageSampleImplicitLod vs OpExecutionMode LocalSize), different stage, different root cause, but the same failure mode: the 4.4 SDSL→SPIR-V frontend emits spec-invalid SPIR-V that surfaces as a bare InternalError because the module isn't validated before spirv-opt. #3304 fixes the [numthreads] variant; this one needs its own fix.
Suggested fix: a post-merge pass that walks the call-graph from Vertex / Hull / Domain / Geometry entry points and rewrites any reachable OpImageSampleImplicitLod (+ Dref/Proj variants) to the matching …ExplicitLod opcode with Lod = 0.0 — mirroring the HLSL lowering, and orthogonal to the existing IsNotPixelStage compute-color workaround. (A broader hardening worth considering for this whole family: run spirv-val and surface its message before spirv-opt, so variants like #3303 and this one self-diagnose instead of throwing a bare InternalError.)
Release Type: GitHub (built from source)
Version:
master(reproduced against a 4.4.1 fork). New in 4.4 — the D3D11 path now round-trips SPIR-V → HLSL via SPIRV-Cross; 4.3 went straight to HLSL and never hit this.Platform(s): Windows — Direct3D11 only. (Vulkan/D3D12 never call
LegalizeForHlsl, so they don't hit the failure, though the module is still invalid.)Describe the bug
A
Texture.Sample(...)call in a vertex-stage material surface fails to compile on Direct3D11. The SDSL→SPIR-V frontend emitsOpImageSampleImplicitLodfor.Sample()regardless of stage, but SPIR-V forbids implicit-LOD instructions outside the Fragment / GLCompute / Mesh / Task execution models (no screen-space derivatives exist in the vertex stage).EffectCompilerthen hands the invalid module toLegalizeForHlsl, which fails with a barespvOptimizerRun InternalError— nothing validates the module first, so the real cause is hidden.The built-in
MaterialDisplacementMapFeaturedoes not hit this: it setscontext.IsNotPixelStage = true, which routes its compute-color texture graph through theComputeColorTextureLod*shaders (SampleLevel). That workaround only covers the compute-color path (ComputeColors/ComputeTextureBase.cs); a custom SDSL shader that calls.Sample()directly is lowered byTextureMethodsImplementations.CompileSample, which is stage-unaware and always emits implicit LOD.To Reproduce
Minimal SDSL surface that triggers it (the texture fetch is legal HLSL in a vertex shader):
shader VertexTextureFetchSurface : IMaterialSurface, PositionStream, Texturing { override void Compute() { float h = Texture0.Sample(PointSampler, float2(0.5, 0.5)).r; streams.Position = float4(streams.Position.x, h, streams.Position.z, streams.Position.w); } };…injected into a material's vertex stage via a
MaterialFeatureusing the stockSetStreamFinalModifierAPI (the same hookMaterialDisplacementMapFeatureuses):Apply that material to any mesh and run. No texture needs to be bound — the crash is in shader compilation, not rendering. (Full GameStudio repro: shader + feature + steps available if useful.)
Repo with reproduce project: https://github.com/delustra/stride-vtf-spirv-repro
Expected behavior
HLSL/FXC/DXC treat
Texture.Sample(...)in a vertex shader as legal and lower it to LOD 0 (no gradients available). Stride should match that — compile and emitOpImageSampleExplicitLodwithLod = 0.0for implicit-LOD samples reachable from a Vertex / Hull / Domain / Geometry entry point (Fragment / Compute / Mesh / Task left untouched).Screenshots
N/A.
Log and callstacks
spirv-valon the emitted module (dumped pre-legalization):What the engine reports today (
LegalizeForHlsl):Additional context
Root cause:
TextureMethodsImplementations.CompileSample(sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/TextureMethodsImplementations.cs) unconditionally emitsOpImageSampleImplicitLod; there is no stage awareness on that path. CompareCompileSampleLevel(~line 139), which correctly emitsOpImageSampleExplicitLod.Sibling of #3303 — different invalid instruction (
OpImageSampleImplicitLodvsOpExecutionMode LocalSize), different stage, different root cause, but the same failure mode: the 4.4 SDSL→SPIR-V frontend emits spec-invalid SPIR-V that surfaces as a bareInternalErrorbecause the module isn't validated beforespirv-opt. #3304 fixes the[numthreads]variant; this one needs its own fix.Suggested fix: a post-merge pass that walks the call-graph from Vertex / Hull / Domain / Geometry entry points and rewrites any reachable
OpImageSampleImplicitLod(+ Dref/Proj variants) to the matching…ExplicitLodopcode withLod = 0.0— mirroring the HLSL lowering, and orthogonal to the existingIsNotPixelStagecompute-color workaround. (A broader hardening worth considering for this whole family: runspirv-valand surface its message beforespirv-opt, so variants like #3303 and this one self-diagnose instead of throwing a bareInternalError.)