Skip to content

Image store parameters are invalid - Not a global variable #522

Description

@pyranota

Expected Behaviour

Naga validation to succeed, shader to be executed by bevy

Example & Steps To Reproduce

  1. Set up simple shader using texture store:
#[inline(never)]
#[spirv(compute(threads(8, 8, 1)))]
pub fn main(
    #[spirv(global_invocation_id)] id: UVec3,
    #[spirv(descriptor_set = 0, binding = 1)] image2d: &Image!(
        2D,
        format = rgba32f,
        sampled = false
    ),
) {
    unsafe {
        image2d.write(ivec2(1, 1), vec4(3., 2., 1., 0.));
    }
}
  1. Compile
  2. naga /../shader/path.spv (or use it in bevy_render)

I'm using rust-gpu main branch, spirv-unknown-vulkan1.4 as a target in build.rs, but the error is the same for any target as far as I can tell.

Not sure if bevy reproduction set up is needed, as it fails with same error naga validation does. Something makes me think it is related to incorrect spv gen

System Info

  • Rust: rustc 1.90.0-nightly (35f603652 2025-06-29)
  • OS: NixOS
  • GPU: AMD Radeon 890M
  • SPIR-V: SPIRV-Tools v2025.5 unknown hash

Backtrace

[2026-02-03T15:08:12Z INFO  naga::front::spv] Generated by 983040 version 10600
[2026-02-03T15:08:12Z INFO  naga::front::spv] Patching...
Function [0] '' is invalid:
	Image store parameters are invalid
	Not a global variable

Activity

  1. Firestar99 commented on Feb 3, 2026

    @Firestar99
    Member

    Since spirv-val succeeds, it's more likely that naga is just failing to parse that spirv. Which is not surprising, there's quite a lot of spirv naga can't parse. (See pipelines in #493 failing cause naga can't understand some spirv)

    You can try to use type=f32 instead of format = rgba32f, like in this alias?

    pub type StorageImage2d = crate::Image!(2D, type=f32, sampled=false, __crate_root=crate);

    You may also need these extra capabilities, set them via SpirvBuilder.capability:

    Capability::StorageImageExtendedFormats,
    Capability::StorageImageReadWithoutFormat,
    Capability::StorageImageWriteWithoutFormat,
  2. pyranota commented on Feb 3, 2026

    @pyranota
    Author

    Thanks for the fast response! I guess in bevy case (it uses naga) I should compile it to something naga understands. Using unknown format for texture and setting capabilities yields the same result (with couple of warnings about unrecognized caps):

    [2026-02-03T15:36:47Z INFO  naga::front::spv] Generated by 983040 version 10600
    [2026-02-03T15:36:47Z WARN  naga::front::spv] Unknown capability StorageImageReadWithoutFormat
    [2026-02-03T15:36:47Z WARN  naga::front::spv] Unknown capability StorageImageWriteWithoutFormat
    [2026-02-03T15:36:47Z INFO  naga::front::spv] Patching...
    Function [0] '' is invalid:
    	Image store parameters are invalid
    	Not a global variable
    
  3. Firestar99 commented on Feb 3, 2026

    @Firestar99
    Member

    Just double checked our compiletest, and while we have one that passes for texel fetches (just make sure to fetch with a lod level, don't use fetch until #490 is merged), none of the image write tests do actually pass for one reason or another. All of my use-cases with naga transpilation to wgsl were only fragment shaders, so never had to write texels either.

  4. pyranota commented on Feb 3, 2026

    @pyranota
    Author

    Can confirm it has exactly the same error here: https://github.com/Rust-GPU/rust-gpu/actions/runs/20504878075/job/58917828710?pr=493#step:6:3598

    I wonder if it's nagas responsibility, as rust-gpu emits valid spir-v and naga strays to have first class support for it?

  5. LegNeato commented on Feb 4, 2026

    @LegNeato
    Collaborator

    It does indeed sound like a naga issue..file one and link it here and we'll discuss?

  6. Firestar99 commented on Feb 4, 2026

    @Firestar99
    Member

    While I generally agree that this is technically a naga problem of not parsing valid spirv, I also like to look at the spirv we're emitting vs spirv emitted by glslc, to figure out what weird thing we're doing that may be breaking naga. And indeed, we are kinda weird.

    Here I've transpiled the output of a compiletest just writing an image to glsl with spirv-cross -V:

    #version 450
    layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
    
    layout(set = 0, binding = 0, std430) readonly buffer fill_color
    {
        vec4 _m0;
    } fill_color_1;
    
    layout(set = 0, binding = 1) uniform readonly writeonly image2D image;
    
    void _spirv_std_image_Image_f32_1_2_0_0_2_0_4_write_u32_glam_u32_uvec2_UVec2_(writeonly image2D _32, uvec2 _33, vec4 _34)
    {
        imageStore(_32, ivec2(uvec2(_33)), _34);
    }
    
    void main()
    {
        vec4 _29 = fill_color_1._m0;
        _spirv_std_image_Image_f32_1_2_0_0_2_0_4_write_u32_glam_u32_uvec2_UVec2_(image, uvec2(gl_GlobalInvocationID.xy), _29);
    }

    First, it'll fail to compile cause our image is both read_only and write_only. Removing read_only, you'll notice that our imageStore does indeed not operate on a global variable but on a parameter passed to the function. If we inline that function call, it works.

    So yea... the fix is to add #[inline] to all the image instrinsics on our side: #523

  7. Firestar99 commented on Feb 4, 2026

    @Firestar99
    Member

    Did some more digging to potentially file a naga bug report (probably won't) by translating it to glsl:

    #version 450
    layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
    
    layout (set = 0, binding = 0, std430) readonly buffer FillColorBuffer
    {
        vec4 fillColor;
    } fill_color_buffer;
    
    layout (set = 0, binding = 1) uniform writeonly image2D image;
    
    void indirection(writeonly image2D image, ivec2 texel, vec4 color)
    {
        imageStore(image, texel, color);
    }
    
    void main()
    {
        indirection(image, ivec2(gl_GlobalInvocationID.xy), fill_color_buffer.fillColor);
    }

    This is valid glsl according to glslc and spirv-val, but uses the OpCapability StorageImageWriteWithoutFormat which is not yet supported by naga.

    Usually we can get rid of that capability by adding layout(..., rgba32f) to the image uniform declaration. If we manually inline the indirection function, it will also get rid of that capability. But due to that function passing an image without explicit format, the capability stays.

    Now you say let's just add layout(rgba32f) to the paramter, but

    image_write_fail.comp:11: error: '' : cannot use layout qualifiers on a function parameter
    

    So you can't even declare images in function parameters to have an explicit image layout.

    Even worse, the emitted spv is invalid, and glslc doesn't even verify it's own output:

    error: line 71: OpFunctionCall Argument <id> '25[%image_0]'s type does not match Function <id> '8[%_ptr_UniformConstant_7]'s parameter type.
      %43 = OpFunctionCall %void %indirection_I21_vi2_vf4_ %image_0 %param %param_0
    

    So I can't even write glsl that generates spv similar to what we generate.

  8. pyranota commented on Feb 8, 2026

    @pyranota
    Author

    Thank you @Firestar99 for looking into this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions