Summary
SPIRV-Cross crashes with "The cbuffer layout is not packoffset-compatible" when a cbuffer member is an array with element stride smaller than 16 bytes (e.g., float[126] with stride 4). This is valid HLSL — DXIL shaders can have tightly packed float[N] arrays in cbuffers — but SPIRV-Cross assumes all cbuffer array elements are 16-byte aligned.
To Reproduce
Given a SPIR-V blob where a Uniform buffer block contains:
OpTypeArray %float %count
OpMemberDecorate %MyBlock 0 Offset 0
OpDecorate %array_stride ArrayStride 4 ; stride = 4 bytes (not 16!)
Running SPIRV-Cross HLSL backend:
spirv-cross input.spv --hlsl --shader-model 60
Throws:
The cbuffer layout is not packoffset-compatible.
Root Cause
Two places in the code assume vec4 (16-byte) alignment for cbuffer arrays:
1. spirv_glsl.cpp — flattened_access_chain_offset() (two branches: ptr_chain and plain array)
For dynamic indices with sub-word stride:
// word_stride = 4, array_stride = 16 (assumed)
if (word_stride >= array_stride)
// 4 >= 16 → false, falls through to:
SPIRV_CROSS_THROW("Access chain ...");
A sub-word stride (word_stride < array_stride) is treated as an error, but it is valid — the array elements are just smaller than a float4. The correct translation is:
index * 4 / 16 = index / 4
2. spirv_hlsl.cpp — emit_buffer_block()
When BufferPackingHLSLCbufferPackOffset and BufferPackingHLSLCbuffer both return BufferPackingStandard = false (due to the non-16-byte stride), the function falls through to:
SPIRV_CROSS_THROW("The cbuffer layout is not packoffset-compatible.");
Instead of trying packoffset-based packing, it should fall back to flattening (emitting uniform float4 name[size] as a global uniform).
Fix Applied
Patched spirv-hlsl.cpp and spirv-glsl.cpp in our local fork at /tmp/opencode/SPIRV-Cross/ (MinGW cross-compiled, ~6856KB PE64):
-
spirv_hlsl.cpp (emit_buffer_block): When both BufferPackingHLSLCbuffer* modes fail, insert var.self into flattened_buffer_blocks and call emit_buffer_block_flattened(var) instead of throwing. This produces uniform float4 _name[size]; — valid HLSL, no explicit binding, placed in $Globals.
-
spirv_glsl.cpp (flattened_access_chain_offset): For dynamic indices with sub-word stride (word_stride % array_stride == 0 && array_stride < word_stride), generate (index) * stride / word_stride instead of throwing. Correctly maps float[index] → float4[index / 4].
Results
| Metric |
Before |
After |
| D3D12 shaders decompiled |
42,668/53,551 (79.7%) |
53,545/53,551 (99.99%) |
| Remaining failures |
10,883 |
6 (dxil-spirv parse errors, not SPIRV-Cross) |
| Flattened cbuffers |
N/A |
9,867 shaders use uniform float4[N] pattern |
| Compile-back test |
N/A |
100/100 random samples compile to DXIL via dxc -T [vs|ps|cs]_6_0 |
Related
Environment
- SPIRV-Cross: v0.6 (git ~68413a9)
- Target: HLSL SM 6.0 (DXIL)
- Host: Linux x86_64, MinGW cross-compiled for Windows PE target (toolchain:
x86_64-w64-mingw32-g++)
- Input: Watch Dogs: Legion D3D12 shaders (53,551 files) with DXIL-containing DXBC container
Summary
SPIRV-Cross crashes with
"The cbuffer layout is not packoffset-compatible"when a cbuffer member is an array with element stride smaller than 16 bytes (e.g.,float[126]with stride 4). This is valid HLSL — DXIL shaders can have tightly packedfloat[N]arrays in cbuffers — but SPIRV-Cross assumes all cbuffer array elements are 16-byte aligned.To Reproduce
Given a SPIR-V blob where a Uniform buffer block contains:
Running SPIRV-Cross HLSL backend:
Throws:
Root Cause
Two places in the code assume vec4 (16-byte) alignment for cbuffer arrays:
1.
spirv_glsl.cpp—flattened_access_chain_offset()(two branches:ptr_chainand plain array)For dynamic indices with sub-word stride:
A sub-word stride (
word_stride < array_stride) is treated as an error, but it is valid — the array elements are just smaller than a float4. The correct translation is:2.
spirv_hlsl.cpp—emit_buffer_block()When
BufferPackingHLSLCbufferPackOffsetandBufferPackingHLSLCbufferboth returnBufferPackingStandard= false (due to the non-16-byte stride), the function falls through to:Instead of trying packoffset-based packing, it should fall back to flattening (emitting
uniform float4 name[size]as a global uniform).Fix Applied
Patched
spirv-hlsl.cppandspirv-glsl.cppin our local fork at/tmp/opencode/SPIRV-Cross/(MinGW cross-compiled, ~6856KB PE64):spirv_hlsl.cpp(emit_buffer_block): When bothBufferPackingHLSLCbuffer*modes fail, insertvar.selfintoflattened_buffer_blocksand callemit_buffer_block_flattened(var)instead of throwing. This producesuniform float4 _name[size];— valid HLSL, no explicit binding, placed in$Globals.spirv_glsl.cpp(flattened_access_chain_offset): For dynamic indices with sub-word stride (word_stride % array_stride == 0 && array_stride < word_stride), generate(index) * stride / word_strideinstead of throwing. Correctly mapsfloat[index]→float4[index / 4].Results
uniform float4[N]patterndxc -T [vs|ps|cs]_6_0Related
Environment
x86_64-w64-mingw32-g++)