WIP: fix: correct SSBO/UBO binding point resolution in material system#2647
WIP: fix: correct SSBO/UBO binding point resolution in material system#2647zzuegg wants to merge 6 commits intojMonkeyEngine:masterfrom
Conversation
The material system was using wrong binding points for shader storage and uniform buffer objects, causing data to be bound to incorrect slots. - GLRenderer.updateShaderBufferBlock() now queries the actual binding from the compiled shader (glGetActiveUniformBlocki for UBOs, glGetProgramResourceiv for SSBOs) and respects layout(binding=N). Falls back to blockIndex when no explicit binding is declared. - Remove premature renderer.setShaderStorageBufferObject/ setUniformBufferObject calls from Material.updateShaderMaterialParameter that used a sequential counter as binding point. - Cache binding on ShaderBufferBlock (per-shader) instead of BufferObject (per-buffer), since the same buffer can be bound at different points in different shaders. - Add glGetActiveUniformBlocki to GL3 and glGetProgramResourceiv + GL_BUFFER_BINDING to GL4 with implementations in both LWJGL backends. - BufferObject.initializeEmpty() now calls setUpdateNeeded() so the GPU buffer gets allocated on first use. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request resolves critical issues with Shader Storage Buffer Object (SSBO) and Uniform Buffer Object (UBO) binding within the material system. It introduces a more robust mechanism for resolving GL binding points by dynamically querying compiled shaders, which ensures that buffer data is correctly associated with its intended shader slots. A significant benefit of this change is the enhanced flexibility for developers, who can now utilize SSBOs and UBOs through the material system without needing to explicitly declare Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request correctly refactors the SSBO/UBO binding logic to query binding points from compiled shaders and provides a fallback for shaders without explicit bindings, which is a significant improvement. I've identified one potential bug regarding the handling of layout(binding=0) and a minor performance optimization opportunity. Overall, the changes are well-structured and address the intended problem effectively.
| if (bindingPoint == 0) { | ||
| bindingPoint = blockIndex; | ||
| } |
There was a problem hiding this comment.
This logic incorrectly handles layout(binding=0). When a uniform block has an explicit layout(binding=0), the GL query will return 0. This code then overwrites bindingPoint with blockIndex, which might be different from 0, thus ignoring the explicit binding from the shader. This is a bug for shaders that use layout(binding=0). The ambiguity of the return value 0 from the GL functions makes this tricky, but the current implementation doesn't fully respect layout(binding=N) as intended.
There was a problem hiding this comment.
i don't think there is a correct solution, if binding trough the material system is allowed, and the direct binding version too. then unfortuantely due to opengl returning 0 for "not defined" creates always a a point where collision can happen.
| IntBuffer params = BufferUtils.createIntBuffer(1); | ||
| gl4.glGetProgramResourceiv(program, GL4.GL_SHADER_STORAGE_BLOCK, blockIndex, props, null, params); | ||
| return params.get(0); |
There was a problem hiding this comment.
This method allocates a new IntBuffer for params on every call. To improve performance and reduce garbage, you can reuse the class member intBuf1 which is already available for this purpose.
| IntBuffer params = BufferUtils.createIntBuffer(1); | |
| gl4.glGetProgramResourceiv(program, GL4.GL_SHADER_STORAGE_BLOCK, blockIndex, props, null, params); | |
| return params.get(0); | |
| intBuf1.clear(); | |
| gl4.glGetProgramResourceiv(program, GL4.GL_SHADER_STORAGE_BLOCK, blockIndex, props, null, intBuf1); | |
| return intBuf1.get(0); |
Adds 5 parameterized screenshot tests that verify SSBO binding point resolution through the material system. Each test creates 3 SSBOs (red, green, blue) and checks that the shader output is white. - NoBindings: no explicit layout(binding=N) on any block - ExplicitBindings: all blocks have explicit non-zero bindings - Binding0OnSecond: second block has layout(binding=0) - MixedBindings: mix of explicit non-zero and implicit bindings - Collision (KNOWN_TO_FAIL): unbound block at blockIndex=1 collides with another block's explicit layout(binding=1), producing magenta instead of white — demonstrates the binding point ambiguity bug Also fixes BufferObject.setData() which was destroying the input parameter instead of this.data and was not calling setUpdateNeeded(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion Replaces the per-block binding resolution with a two-pass approach in resolveBufferBlockBindings() that runs once per shader program: Pass 1: query all block bindings from the compiled shader. Pass 2: detect duplicate binding points and reassign colliding blocks to unique free binding points. This fixes the case where an unbound block's default binding (0) collides with another block's explicit layout(binding=N). The collision test now passes as MUST_PASS instead of KNOWN_TO_FAIL. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UBOs and SSBOs use separate GL binding namespaces (GL_UNIFORM_BUFFER vs GL_SHADER_STORAGE_BUFFER), so a UBO at binding 0 does not collide with an SSBO at binding 0. Track them independently in the collision detection pass to avoid unnecessary reassignments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds compile-time validation that rejects shaders using layout(binding=0) on a buffer block that is not the first declared block. The GL query cannot distinguish explicit binding=0 from the default, so this case would silently produce incorrect bindings. The error message tells the user to use a non-zero binding or declare the block first in the shader. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Material system cleanup: Removed premature renderer.setShaderStorageBufferObject() / renderer.setUniformBufferObject() calls from Material.updateShaderMaterialParameter() that Two-pass binding resolution: A new resolveBufferBlockBindings() method runs once per shader program after linking:
Results are cached on ShaderBufferBlock (per-shader, not per-buffer), so the resolution only runs once. Shader source validation: A compile-time check scans the GLSL source for layout(binding=0) on non-first buffer blocks and throws a RendererException with a clear message. This is BufferObject fix: setData() and initializeEmpty() now call setUpdateNeeded() so GPU buffers are actually allocated. setData() also fixed a bug where it destroyed the input |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a robust mechanism for resolving SSBO and UBO binding points, which is a significant improvement. The two-pass approach to detect and resolve binding collisions is well-designed, and the validation for ambiguous layout(binding=0) usage is a clever solution to a known GL API limitation. The changes correctly move binding logic from the Material class to the GLRenderer and add necessary new GL functions to the interfaces and their implementations.
I've found a potential NullPointerException in the updated BufferObject.setData method and a performance improvement opportunity in GLRenderer.queryShaderStorageBlockBinding. Please see my detailed comments.
| public void setData(ByteBuffer data) { | ||
| if (data != null) { | ||
| BufferUtils.destroyDirectBuffer(data); | ||
| if (this.data != null) { | ||
| BufferUtils.destroyDirectBuffer(this.data); | ||
| } | ||
| this.data = BufferUtils.createByteBuffer(data.limit() - data.position()); | ||
| this.data.put(data); | ||
| setUpdateNeeded(); | ||
| } |
There was a problem hiding this comment.
This method will throw a NullPointerException if the input data is null. You should add a null check for the data parameter to handle this case gracefully, for example by setting this.data to null.
Also, using data.remaining() is slightly cleaner than data.limit() - data.position().
public void setData(ByteBuffer data) {
if (this.data != null) {
BufferUtils.destroyDirectBuffer(this.data);
}
if (data != null) {
this.data = BufferUtils.createByteBuffer(data.remaining());
this.data.put(data);
} else {
this.data = null;
}
setUpdateNeeded();
}| private int queryShaderStorageBlockBinding(int program, int blockIndex) { | ||
| IntBuffer props = BufferUtils.createIntBuffer(1); | ||
| props.put(GL4.GL_BUFFER_BINDING).flip(); | ||
| IntBuffer params = BufferUtils.createIntBuffer(1); | ||
| gl4.glGetProgramResourceiv(program, GL4.GL_SHADER_STORAGE_BLOCK, blockIndex, props, null, params); | ||
| return params.get(0); | ||
| } |
There was a problem hiding this comment.
To improve performance and reduce garbage collection, you can avoid re-allocating IntBuffers on every call to this method. The props buffer is constant and can be a static final field in GLRenderer. The params buffer can be a reusable member field.
For example, you could add these fields to GLRenderer:
private static final IntBuffer PROPS_BUFFER_BINDING = (IntBuffer) BufferUtils.createIntBuffer(1).put(GL4.GL_BUFFER_BINDING).flip();
private final IntBuffer resourceParams = BufferUtils.createIntBuffer(1);Then this method would become:
private int queryShaderStorageBlockBinding(int program, int blockIndex) {
resourceParams.clear();
gl4.glGetProgramResourceiv(program, GL4.GL_SHADER_STORAGE_BLOCK, blockIndex, PROPS_BUFFER_BINDING, null, resourceParams);
return resourceParams.get(0);
}…rageBlockBinding Use the renderer's intBuf1 and intBuf16 instance fields instead of allocating new IntBuffers on each call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSBO and UBO buffers set via the Material system were bound to wrong GL binding points, causing shader data to end up in the wrong slots. This fix queries the actual binding
point from the compiled shader and assigns unique fallback bindings when none is declared, so buffers reliably match their GLSL layout(binding=N) declarations.
This also enables using SSBOs and UBOs through the material system — including via MatParamOverride — without requiring layout(binding=N) in the shader. The renderer
automatically assigns a unique binding point per block, so multiple buffers won't collide. Just set the buffer on the material and it works.