Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions jme3-core/src/main/java/com/jme3/material/Material.java
Original file line number Diff line number Diff line change
Expand Up @@ -881,14 +881,10 @@ private void updateShaderMaterialParameter(Renderer renderer, VarType type, Shad
ShaderBufferBlock.BufferType btype;
if (type == VarType.ShaderStorageBufferObject) {
btype = ShaderBufferBlock.BufferType.ShaderStorageBufferObject;
bufferBlock.setBufferObject(btype, bufferObject);
renderer.setShaderStorageBufferObject(unit.bufferUnit, bufferObject); // TODO: probably not needed
} else {
btype = ShaderBufferBlock.BufferType.UniformBufferObject;
bufferBlock.setBufferObject(btype, bufferObject);
renderer.setUniformBufferObject(unit.bufferUnit, bufferObject); // TODO: probably not needed
}
unit.bufferUnit++;
bufferBlock.setBufferObject(btype, bufferObject);
} else {
Uniform uniform = shader.getUniform(param.getPrefixedName());
if (!override && uniform.isSetByCurrentMaterial())
Expand Down
16 changes: 16 additions & 0 deletions jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,20 @@ public interface GL3 extends GL2 {
* uniformBlockIndex within program.
*/
public void glUniformBlockBinding(int program, int uniformBlockIndex, int uniformBlockBinding);

/**
* <p><a target="_blank" href="http://docs.gl/gl4/glGetActiveUniformBlock">Reference Page</a></p>
*
* Queries information about an active uniform block.
*
* @param program the name of a program containing the uniform block.
* @param uniformBlockIndex the index of the uniform block within program.
* @param pname the name of the parameter to query. One of:
* {@link #GL_UNIFORM_BLOCK_BINDING}
* {@link #GL_UNIFORM_BLOCK_DATA_SIZE}
* {@link #GL_UNIFORM_BLOCK_NAME_LENGTH}
* {@link #GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS}
* @return the value of the queried parameter.
*/
public int glGetActiveUniformBlocki(int program, int uniformBlockIndex, int pname);
}
24 changes: 23 additions & 1 deletion jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
*/
package com.jme3.renderer.opengl;

import java.nio.IntBuffer;

/**
* GL functions only available on vanilla desktop OpenGL 4.0.
*
Expand Down Expand Up @@ -77,6 +79,11 @@ public interface GL4 extends GL3 {
public static final int GL_SHADER_STORAGE_BUFFER = 0x90D2;
public static final int GL_SHADER_STORAGE_BLOCK = 0x92E6;

/**
* Accepted by the {@code props} parameter of GetProgramResourceiv.
*/
public static final int GL_BUFFER_BINDING = 0x9302;

/**
* Accepted by the &lt;pname&gt; parameter of GetIntegerv, GetBooleanv,
* GetInteger64v, GetFloatv, and GetDoublev:
Expand Down Expand Up @@ -124,7 +131,22 @@ public interface GL4 extends GL3 {
* @param storageBlockBinding The index storage block binding to associate with the specified storage block.
*/
public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding);


/**
* <p><a target="_blank" href="http://docs.gl/gl4/glGetProgramResource">Reference Page</a></p>
*
* Retrieves values for multiple properties of a single active resource within a program object.
*
* @param program the name of a program object whose resources to query.
* @param programInterface a token identifying the interface within program containing the resource named name.
* @param index the active resource index.
* @param props an array of properties to query.
* @param length an array that will receive the number of values written to params.
* @param params an array that will receive the property values.
*/
public void glGetProgramResourceiv(int program, int programInterface, int index, IntBuffer props, IntBuffer length, IntBuffer params);


/**
* Binds a single level of a texture to an image unit for the purpose of reading
* and writing it from shaders.
Expand Down
191 changes: 162 additions & 29 deletions jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1456,7 +1456,6 @@ protected void updateShaderBufferBlock(final Shader shader, final ShaderBufferBl

final BufferObject bufferObject = bufferBlock.getBufferObject();
final BufferType bufferType = bufferBlock.getType();


if (bufferObject.isUpdateNeeded()) {
if (bufferType == BufferType.ShaderStorageBufferObject) {
Expand All @@ -1469,39 +1468,20 @@ protected void updateShaderBufferBlock(final Shader shader, final ShaderBufferBl
int usage = resolveUsageHint(bufferObject.getAccessHint(), bufferObject.getNatureHint());
if (usage == -1) return; // cpu only

bindProgram(shader);

final int shaderId = shader.getId();

int bindingPoint = bufferObject.getBinding();
int bindingPoint = bufferBlock.getBinding();
if (bindingPoint < 0) {
// Binding not yet resolved — skip until resolveBufferBlockBindings runs
bufferBlock.clearUpdateNeeded();
return;
}

switch (bufferType) {
case UniformBufferObject: {
setUniformBufferObject(bindingPoint, bufferObject); // rebind buffer if needed
if (bufferBlock.isUpdateNeeded()) {
int blockIndex = bufferBlock.getLocation();
if (blockIndex < 0) {
blockIndex = gl3.glGetUniformBlockIndex(shaderId, bufferBlock.getName());
bufferBlock.setLocation(blockIndex);
}
if (bufferBlock.getLocation() != NativeObject.INVALID_ID) {
gl3.glUniformBlockBinding(shaderId, bufferBlock.getLocation(), bindingPoint);
}
}
setUniformBufferObject(bindingPoint, bufferObject);
break;
}
case ShaderStorageBufferObject: {
setShaderStorageBufferObject(bindingPoint, bufferObject); // rebind buffer if needed
if (bufferBlock.isUpdateNeeded() ) {
int blockIndex = bufferBlock.getLocation();
if (blockIndex < 0) {
blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, bufferBlock.getName());
bufferBlock.setLocation(blockIndex);
}
if (bufferBlock.getLocation() != NativeObject.INVALID_ID) {
gl4.glShaderStorageBlockBinding(shaderId, bufferBlock.getLocation(), bindingPoint);
}
}
setShaderStorageBufferObject(bindingPoint, bufferObject);
break;
}
default: {
Expand All @@ -1512,6 +1492,111 @@ protected void updateShaderBufferBlock(final Shader shader, final ShaderBufferBl
bufferBlock.clearUpdateNeeded();
}

/**
* Resolves binding points for all buffer blocks in a shader. Runs once
* per shader program. Queries the binding from the compiled shader,
* detects collisions, and reassigns duplicates to unique binding points.
*
* @param shader the shader whose buffer blocks to resolve.
*/
private void resolveBufferBlockBindings(final Shader shader) {
final ListMap<String, ShaderBufferBlock> bufferBlocks = shader.getBufferBlockMap();
final int shaderId = shader.getId();

bindProgram(shader);

// Pass 1: resolve block indices and query bindings from the compiled shader
for (int i = 0; i < bufferBlocks.size(); i++) {
ShaderBufferBlock block = bufferBlocks.getValue(i);
if (block.getBinding() >= 0) continue; // already resolved

BufferType bufferType = block.getType();
if (bufferType == null) continue; // not yet configured

// Resolve block index (location)
int blockIndex = block.getLocation();
if (blockIndex < 0) {
if (bufferType == BufferType.ShaderStorageBufferObject) {
blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, block.getName());
} else {
blockIndex = gl3.glGetUniformBlockIndex(shaderId, block.getName());
}
block.setLocation(blockIndex);
}

if (blockIndex < 0 || blockIndex == NativeObject.INVALID_ID) {
continue;
}

// Query the binding declared in the shader
int binding;
if (bufferType == BufferType.ShaderStorageBufferObject) {
binding = queryShaderStorageBlockBinding(shaderId, blockIndex);
} else {
binding = gl3.glGetActiveUniformBlocki(shaderId, blockIndex, GL3.GL_UNIFORM_BLOCK_BINDING);
}
block.setBinding(binding);
}

// Pass 2: detect and resolve collisions.
// UBOs and SSBOs use separate GL binding namespaces, so track them independently.
Set<Integer> usedUboBindings = new HashSet<>();
Set<Integer> usedSsboBindings = new HashSet<>();
int nextFreeUbo = 0;
int nextFreeSsbo = 0;

for (int i = 0; i < bufferBlocks.size(); i++) {
ShaderBufferBlock block = bufferBlocks.getValue(i);
int binding = block.getBinding();
if (binding < 0) continue;

BufferType bufferType = block.getType();
Set<Integer> usedBindings;
if (bufferType == BufferType.ShaderStorageBufferObject) {
usedBindings = usedSsboBindings;
} else {
usedBindings = usedUboBindings;
}

if (!usedBindings.add(binding)) {
// Collision within the same namespace — find a free binding point
if (bufferType == BufferType.ShaderStorageBufferObject) {
while (usedBindings.contains(nextFreeSsbo)) nextFreeSsbo++;
binding = nextFreeSsbo;
} else {
while (usedBindings.contains(nextFreeUbo)) nextFreeUbo++;
binding = nextFreeUbo;
}
usedBindings.add(binding);
block.setBinding(binding);
}

// Set the binding on the shader program
int blockIndex = block.getLocation();
if (bufferType == BufferType.ShaderStorageBufferObject) {
gl4.glShaderStorageBlockBinding(shaderId, blockIndex, binding);
} else {
gl3.glUniformBlockBinding(shaderId, blockIndex, binding);
}
}
}

/**
* Queries the binding point of a shader storage block using
* glGetProgramResourceiv with GL_BUFFER_BINDING.
*
* @param program the shader program id.
* @param blockIndex the block index within the program.
* @return the binding point assigned to the block.
*/
private int queryShaderStorageBlockBinding(int program, int blockIndex) {
intBuf16.clear();
intBuf16.put(GL4.GL_BUFFER_BINDING).flip();
intBuf1.clear();
gl4.glGetProgramResourceiv(program, GL4.GL_SHADER_STORAGE_BLOCK, blockIndex, intBuf16, null, intBuf1);
return intBuf1.get(0);
}

protected void updateShaderUniforms(Shader shader) {
ListMap<String, Uniform> uniforms = shader.getUniformMap();
for (int i = 0; i < uniforms.size(); i++) {
Expand All @@ -1529,6 +1614,11 @@ protected void updateShaderUniforms(Shader shader) {
*/
protected void updateShaderBufferBlocks(final Shader shader) {
final ListMap<String, ShaderBufferBlock> bufferBlocks = shader.getBufferBlockMap();
// Resolve binding points once per shader, detecting and fixing collisions
if (bufferBlocks.size() > 0 && bufferBlocks.getValue(0).getBinding() < 0) {
resolveBufferBlockBindings(shader);
}

for (int i = 0; i < bufferBlocks.size(); i++) {
updateShaderBufferBlock(shader, bufferBlocks.getValue(i));
}
Expand Down Expand Up @@ -1559,6 +1649,45 @@ public int convertShaderType(ShaderType type) {
}
}

private static final Pattern BUFFER_BLOCK_PATTERN = Pattern.compile(
"layout\\s*\\([^)]*\\)\\s*(buffer|uniform)\\s+\\w+");

private static final Pattern BINDING_ZERO_PATTERN = Pattern.compile(
"layout\\s*\\([^)]*binding\\s*=\\s*0[^)]*\\)\\s*(buffer|uniform)\\s+\\w+");

/**
* Checks that layout(binding=0) is not used on a non-first buffer block,
* since the GL query cannot distinguish explicit binding=0 from the
* default, making collision detection unreliable for that case.
*
* @param source the GLSL source code.
* @param sourceName the name of the shader source for error messages.
*/
private void validateBufferBlockBindings(String source, String sourceName) {
Matcher allBlocks = BUFFER_BLOCK_PATTERN.matcher(source);
Matcher binding0Blocks = BINDING_ZERO_PATTERN.matcher(source);

// Find positions of all buffer/uniform block declarations
List<Integer> allPositions = new ArrayList<>();
while (allBlocks.find()) {
allPositions.add(allBlocks.start());
}

if (allPositions.size() < 2) return; // single block, no ambiguity possible

int firstBlockPos = allPositions.get(0);

while (binding0Blocks.find()) {
if (binding0Blocks.start() != firstBlockPos) {
throw new RendererException(
"Shader '" + sourceName + "' uses layout(binding=0) on a non-first "
+ "buffer block. This is ambiguous because the GL query cannot "
+ "distinguish explicit binding=0 from the default. Use a non-zero "
+ "binding or declare this block first in the shader.");
}
}
}

public void updateShaderSourceData(ShaderSource source) {
int id = source.getId();
if (id == -1) {
Expand Down Expand Up @@ -1623,7 +1752,11 @@ public void updateShaderSourceData(ShaderSource source) {
stringBuf.append("#define ").append(source.getType().name().toUpperCase()).append("_SHADER 1\n");

stringBuf.append(source.getDefines());
stringBuf.append(source.getSource());

String sourceCode = source.getSource();
validateBufferBlockBindings(sourceCode, source.getName());

stringBuf.append(sourceCode);


intBuf1.clear();
Expand Down
24 changes: 24 additions & 0 deletions jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public static enum BufferType {
protected WeakReference<BufferObject> bufferObjectRef;
protected BufferType type;

/**
* The binding point assigned to this block, or -1 if not yet assigned.
*/
protected int binding = -1;

/**
* Set the new buffer object.
*
Expand Down Expand Up @@ -90,11 +95,30 @@ public void clearUpdateNeeded(){
updateNeeded = false;
}

/**
* Get the binding point assigned to this block.
*
* @return the binding point, or -1 if not yet assigned.
*/
public int getBinding() {
return binding;
}

/**
* Set the binding point for this block.
*
* @param binding the binding point.
*/
public void setBinding(int binding) {
this.binding = binding;
}

/**
* Reset this storage block.
*/
public void reset() {
location = -1;
binding = -1;
updateNeeded = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ public void initializeEmpty(int length) {
BufferUtils.destroyDirectBuffer(data);
}
this.data = BufferUtils.createByteBuffer(length);
setUpdateNeeded();
}


Expand All @@ -167,11 +168,12 @@ public void initializeEmpty(int length) {
* @param data ByteBuffer containing the data to pass
*/
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();
}
Comment on lines 170 to 177
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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();
    }



Expand Down
Loading
Loading