stride3d/stride · error · InvalidOperationException
Not a SPIR-V module
Error message
Not a SPIR-V module
What it means
Thrown by the SpirvProbe constructor when the input byte span is too small (<5 words) or its first word is not the SPIR-V magic number 0x07230203. SpirvProbe only parses genuine SPIR-V modules, so malformed input is rejected immediately before any instruction decoding.
Solutions
- Ensure the shader is compiled to SPIR-V (dxc -spirv or glslang) before handing the bytes to SpirvProbe.
- Check the byte length: a valid module has at least a 20-byte header; log the actual size and first bytes when diagnosing.
- Confirm the loaded file is the SPIR-V artifact, not the DXIL/ISKBlob or source text.
- If bytes may come from an external source, verify magic 0x03 0x02 0x23 0x07 (little-endian) before constructing the probe.
Example fix
// before
var probe = new SpirvProbe(File.ReadAllBytes("shader.dxil"));
// after
var bytes = File.ReadAllBytes("shader.spv");
if (bytes.Length < 20 || BitConverter.ToUInt32(bytes, 0) != 0x07230203)
throw new InvalidOperationException("shader.spv is not SPIR-V");
var probe = new SpirvProbe(bytes); Defensive patterns
Strategy: validation
Validate before calling
static bool IsSpirv(byte[] bytes)
=> bytes.Length >= 20 && BitConverter.ToUInt32(bytes, 0) == 0x07230203; Type guard
bool TryAsSpirv(ReadOnlySpan<byte> bytes, out SpirvProbe? probe)
{
probe = null;
if (bytes.Length < 20 || BitConverter.ToUInt32(bytes.ToArray(), 0) != 0x07230203)
return false;
probe = new SpirvProbe(bytes);
return true;
} Try / catch
try { var probe = new SpirvProbe(shaderBytes); }
catch (InvalidOperationException ex) when (ex.Message == "Not a SPIR-V module")
{
// compile the shader to SPIR-V first; log first bytes for diagnosis
} Prevention
- Always run shaders through dxc -spirv / glslang before probing.
- Check file size and magic bytes right after loading shader artifacts.
- Ensure the build actually produced .spv files (assert in test fixture setup).
When it happens
Trigger: new SpirvProbe(bytes) is given bytecode that is actually DXIL, GLSL text, a truncated buffer, or byte-swapped/wrong-endian data, so words[0] != 0x07230203.
Common situations: Passing dxbc/dxil output to a SPIR-V pipeline; loading a shader file that failed to compile and contains an error string; reading a file with wrong length (zero bytes); missing the SPIR-V codegen step (e.g. no dxc/spirv backend) so raw HLSL bytes flow through.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- SPIRV Magic number not found
- 64bit integers
- A negative number of mip maps is not allowed.
- Base path must be non-empty (use null for passthrough).
- Binary operator not supported for element type
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/af5a2ffd70276801.
Report an issue: GitHub.
Appendix: source
Thrown at sources/shaders/Stride.Shaders.Tests/SpirvProbe.cs:45
private const uint DecorationBuiltIn = 11;
private const uint DecorationLocation = 30;
private const uint DecorationUserSemantic = 5635;
private const uint StorageClassInput = 1;
public record EntryPointInfo(uint ExecutionModel, string Name, uint[] Interface);
public List<EntryPointInfo> EntryPoints { get; } = [];
public Dictionary<uint, uint> Locations { get; } = [];
public Dictionary<uint, string> UserSemantics { get; } = [];
public HashSet<uint> BuiltIns { get; } = [];
public Dictionary<uint, uint> VariableStorageClasses { get; } = [];
public SpirvProbe(ReadOnlySpan<byte> spirv)
{
var words = MemoryMarshal.Cast<byte, uint>(spirv);
if (words.Length < 5 || words[0] != 0x07230203)
throw new InvalidOperationException("Not a SPIR-V module");
for (int i = 5; i < words.Length;)
{
var wordCount = (int)(words[i] >> 16);
var opcode = words[i] & 0xFFFF;
if (wordCount == 0)
throw new InvalidOperationException("Invalid SPIR-V instruction");
var operands = words.Slice(i + 1, wordCount - 1);
switch (opcode)
{
case OpEntryPoint:
{
var name = ReadString(operands.Slice(2), out var nameWords);
EntryPoints.Add(new EntryPointInfo(operands[0], name, operands.Slice(2 + nameWords).ToArray()));
break;
}
case OpVariable:View on GitHub (pinned to 96fad776d2)