Skip to content

Size the visibility range binding for the buffer type it was given - #25687

Open
glashoff wants to merge 1 commit into
bevyengine:mainfrom
glashoff:fix/webgl2-visibility-range-min-binding-size
Open

Size the visibility range binding for the buffer type it was given#25687
glashoff wants to merge 1 commit into
bevyengine:mainfrom
glashoff:fix/webgl2-visibility-range-min-binding-size

Conversation

@glashoff

@glashoff glashoff commented Sep 5, 2026

Copy link
Copy Markdown

Objective

On WebGL2, a mesh with a crossfading VisibilityRange gets a pipeline that fails validation:

In Device::create_render_pipeline, label = 'pbr_opaque_mesh_pipeline'
  Error matching ShaderStages(VERTEX) shader requirements against the pipeline
    Shader global ResourceBinding { group: 0, binding: 14 } is not available in the pipeline layout
      Buffer structure size 1024, added to one element of an unbound array, if it's the last
      field, ended up greater than the given `min_binding_size`, which is 16

Fixes #21309.

Solution

Binding 14 is the visibility range buffer, and it is declared two ways depending on how many
storage buffers the device has. mesh_view_bindings.wesl:

const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u;
@if(AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_6)
@group(0) @binding(14) var<storage> visibility_ranges: array<vec4<f32>>;
@else
@group(0) @binding(14) var<uniform> visibility_ranges: array<vec4<f32>, VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE>;

layout_entries gave that binding Some(Vec4::min_size()) — 16 bytes — whichever branch had
been taken. That is right only for the storage declaration, where the array is runtime-sized and
one element genuinely is the minimum the shader can be said to require. The uniform declaration
is fixed at VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE elements, so the shader requires all 1024
bytes, and a layout promising 16 is one a 16-byte buffer could satisfy.

Only the layout changes. The buffer bound there is already 1024 bytes, because
write_render_visibility_ranges pads it to exactly VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE
elements on this path, so the larger min_binding_size is one it already satisfies.

The size now follows the binding type. VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE was private to
bevy_render, so this exports it rather than writing the number out again here.

Testing

AI usage

The gap was found by Claude Opus 5, and the change and the verification runs above were drafted
and carried out with it.

I have read every line of this change and stand behind it.

Reported as bevyengine#21309: the `visibility_range` example, built for
`wasm32-unknown-unknown` with the `webgl2` feature, quits before it draws a
frame:

    In Device::create_render_pipeline, label = 'pbr_opaque_mesh_pipeline'
      Error matching ShaderStages(VERTEX) shader requirements against the
      pipeline
        Shader global ResourceBinding { group: 0, binding: 14 } is not
        available in the pipeline layout
          Buffer structure size 1024, added to one element of an unbound
          array, if it's the last field, ended up greater than the given
          `min_binding_size`, which is 16

Binding 14 is declared two ways in `mesh_view_bindings.wesl`: a runtime-sized
`array<vec4<f32>>` where six storage buffers are available, and a fixed
`array<vec4<f32>, VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE>` where they are not --
WebGL2. `layout_entries` promised `Vec4::min_size()` for both. Sixteen bytes is
the minimum only for the runtime-sized one; the fixed one needs all 1024.

Only the layout changes. The buffer bound there is already 1024 bytes, because
`write_render_visibility_ranges` pads it to exactly that many elements on this
path.

The size now follows the binding type. `VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE`
was private to `bevy_render`, so export it rather than write the number out
again here.

The gap was found by Claude Opus 5.
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Welcome, new contributor!

Please make sure you've read our contributing guide, as well as our policy regarding AI usage, and we look forward to reviewing your pull request shortly ✨

@beicause beicause left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct. I didn't tested it.

false,
Some(Vec4::min_size()),
Some(match visibility_ranges_buffer_binding_type {
BufferBindingType::Uniform => Vec4::min_size().saturating_mul(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to use checked_mul instead of saturating_mul, though they have no different here.

@beicause beicause added C-Bug An unexpected or incorrect behavior A-Rendering Drawing game state to the screen O-WebGL2 Specific to the WebGL2 render API D-Straightforward Simple bug fixes and API improvements, docs, test and examples labels Sep 10, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Rendering Sep 10, 2026
@beicause beicause added S-Needs-Review Needs reviewer attention (from anyone!) to move forward and removed D-Straightforward Simple bug fixes and API improvements, docs, test and examples labels Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior O-WebGL2 Specific to the WebGL2 render API S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

Visibility Range example doesn't work in webgl2

2 participants