Skip to content

HLSL: cbuffer with sub-word array stride causes unrecoverable exception (float[N] with stride 4) #2658

Description

@Selene0623

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.cppflattened_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.cppemit_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):

  1. 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.

  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions