LWJGL Forum

Programming => Lightweight Java Gaming Library => Topic started by: Qudus on March 26, 2007, 18:20:09

Title: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 26, 2007, 18:20:09
hi

I was recently trying to improve shader performance. Therefore I wanted to reduce GC overhead, which I achieved, but I'm not very happy with my current solution.

I want to avoid to recreated the Float/IntBuffers needed for the glUniform1ARB() call. So I stored a global instance, which I only recreate, if the limit is lower than needed. But the glUniform1ARB() method didn't accept it. It demands a buffer of the exactly correct size. So I had to store one buffer for each variable. Is this really necessary? Could this be done with only one buffer?

Marvin
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Matzon on March 26, 2007, 19:56:20
You can slice the buffer into several ? - not sure how that would work out tho...
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 26, 2007, 21:09:07
Quote from: Matzon on March 26, 2007, 19:56:20
You can slice the buffer into several ? - not sure how that would work out tho...

What do you mean by "slice the buffer into several"?

Marvin
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Matzon on March 26, 2007, 22:29:47
http://java.sun.com/j2se/1.5.0/docs/api/java/nio/ByteBuffer.html#slice()
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 26, 2007, 23:02:04
Ah, ok. Thanks.

This is not bad, but there's still a GC overhead, if used. Is this an LWJGL implementation/API flaw? Or is there a way to handle this method GC fiendly an easy way? Maybe it is a good idea to improve the implementation for the next version to accept bigger buffers than needed. What do you think?

Marvin.
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Matzon on March 27, 2007, 05:16:32
I'm not sure that it is entirely possible since we do pass buffer.remaining() in some cases. We will investigate it though.
Title: Re: Shader, Uniform variables and buffer sizes
Post by: elias on March 27, 2007, 08:58:11
I don't get it - glUniform1ARB doesn't check the buffer size. This is the (generated) code for glUniform1ARB:


    public static void glUniform1ARB(int location, FloatBuffer values) {
        ContextCapabilities caps = GLContext.getCapabilities();
        long function_pointer = caps.ARB_shader_objects_glUniform1fvARB_pointer;
        BufferChecks.checkFunctionAddress(function_pointer);
        BufferChecks.checkDirect(values);
        nglUniform1fvARB(location, (values.remaining()), values, values.position(), function_pointer);
    }
    private static native void nglUniform1fvARB(int location, int count, FloatBuffer values, int values_position, long function_pointer);


as you can see, it uses values.position() for the start index and values.remaining() for the number of elements. You should be able to avoid garbage if you set the position and limit on your single buffer appropriately.

- elias
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 27, 2007, 10:43:31
Quote from: elias on March 27, 2007, 08:58:11
as you can see, it uses values.position() for the start index and values.remaining() for the number of elements. You should be able to avoid garbage if you set the position and limit on your single buffer appropriately.

I already tried to set the limit (and of course the position) by calling limit( int ) and revert() without success. But yould you advise me to use glUniform1ARB() instead of glUniveromARB()?

Marvin
Title: Re: Shader, Uniform variables and buffer sizes
Post by: elias on March 27, 2007, 10:51:24
I don't know, you mentioned glUniform1ARB and I assumed that's the function you need. What exactly goes wrong if you set the limit and position correctly?

- elias
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 27, 2007, 23:25:15
Quote from: elias on March 27, 2007, 10:51:24
I don't know, you mentioned glUniform1ARB and I assumed that's the function you need. What exactly goes wrong if you set the limit and position correctly?

This is the stack trace, if I simply use a FloatBuffer of 128 size:

org.lwjgl.opengl.OpenGLException: Invalid operation (1282)
at org.lwjgl.opengl.Util.checkGLError(Util.java:56)
at org.lwjgl.opengl.Display.swapBuffers(Display.java:555)
at org.lwjgl.opengl.Display.update(Display.java:571)
at org.xith3d.render.lwjgl.CanvasPeerImplNative.renderDone(CanvasPeerImplNative.java:278)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.display(CanvasPeerImplBase.java:1072)
at org.xith3d.render.lwjgl.CanvasPeerImplNative.doRender(CanvasPeerImplNative.java:298)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.render(CanvasPeerImplBase.java:1116)
at org.xith3d.render.DefaultRenderer.renderOnceInternal(DefaultRenderer.java:459)
at org.xith3d.render.DefaultRenderer.renderOnce(DefaultRenderer.java:480)
at org.xith3d.render.base.Xith3DEnvironment.render(Xith3DEnvironment.java:436)
at org.xith3d.render.loop.RenderLoop.renderNextFrame(RenderLoop.java:559)
at org.xith3d.render.loop.RenderLoop.loopIteration(RenderLoop.java:582)
at org.xith3d.render.loop.RenderLoop.update(RenderLoop.java:635)
at org.xith3d.render.loop.UpdatingThread.nextIteration(UpdatingThread.java:175)
at org.xith3d.render.loop.RenderLoop.loop(RenderLoop.java:691)
at org.xith3d.render.loop.UpdatingThread.run(UpdatingThread.java:223)
at org.xith3d.render.loop.RenderLoop.run(RenderLoop.java:712)
at java.lang.Thread.run(Thread.java:619)
Exception in thread "Thread-4" java.lang.Error: org.lwjgl.opengl.OpenGLException: Invalid operation (1282)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.display(CanvasPeerImplBase.java:1093)
at org.xith3d.render.lwjgl.CanvasPeerImplNative.doRender(CanvasPeerImplNative.java:298)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.render(CanvasPeerImplBase.java:1116)
at org.xith3d.render.DefaultRenderer.renderOnceInternal(DefaultRenderer.java:459)
at org.xith3d.render.DefaultRenderer.renderOnce(DefaultRenderer.java:480)
at org.xith3d.render.base.Xith3DEnvironment.render(Xith3DEnvironment.java:436)
at org.xith3d.render.loop.RenderLoop.renderNextFrame(RenderLoop.java:559)
at org.xith3d.render.loop.RenderLoop.loopIteration(RenderLoop.java:582)
at org.xith3d.render.loop.RenderLoop.update(RenderLoop.java:635)
at org.xith3d.render.loop.UpdatingThread.nextIteration(UpdatingThread.java:175)
at org.xith3d.render.loop.RenderLoop.loop(RenderLoop.java:691)
at org.xith3d.render.loop.UpdatingThread.run(UpdatingThread.java:223)
at org.xith3d.render.loop.RenderLoop.run(RenderLoop.java:712)
at java.lang.Thread.run(Thread.java:619)
Caused by: org.lwjgl.opengl.OpenGLException: Invalid operation (1282)
at org.lwjgl.opengl.Util.checkGLError(Util.java:56)
at org.lwjgl.opengl.Display.swapBuffers(Display.java:555)
at org.lwjgl.opengl.Display.update(Display.java:571)
at org.xith3d.render.lwjgl.CanvasPeerImplNative.renderDone(CanvasPeerImplNative.java:278)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.display(CanvasPeerImplBase.java:1072)
... 13 more


I get the same stack trace for a FloatBuffer of 128 size and position at 0 and limit at value.length (for a float array). I'm using glUniform1ARB( int, FloatBuffer ).

Marvin
Title: Re: Shader, Uniform variables and buffer sizes
Post by: elias on March 28, 2007, 04:49:42
What are you passing in as the int parameter in glUniform1ARB? Further, does it work if you pass a buffer with one remaining() element? Try to add a org.lwjgl.opengl.Util.checkGLError() just before and after the glUniform1ARB call, just to make sure the 1281 error comes from that.

- elias
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 28, 2007, 17:45:31
Quote from: elias on March 28, 2007, 04:49:42
What are you passing in as the int parameter in glUniform1ARB?

It's the result of glGetUniformLocationARB.

Here is the relevant code: http://xith3d.svn.sourceforge.net/viewvc/xith3d/trunk/src/org/xith3d/render/lwjgl/GLSLShaderProgramShaderPeer.java?view=markup
(lines #310 - #508)

In this version I use the workarund with the stored buffers per uniform.

Quote from: elias on March 28, 2007, 04:49:42
Further, does it work if you pass a buffer with one remaining() element? Try to add a org.lwjgl.opengl.Util.checkGLError() just before and after the glUniform1ARB call, just to make sure the 1281 error comes from that.

It does work with a correctly sized buffer.

btw. Do I understand the glUniform1ARB, glUniform2ARB, ... thing right? Does the (1) only take one uniform parameter and the (2) exactly two and so forth?

Marvin
Title: Re: Shader, Uniform variables and buffer sizes
Post by: elias on March 28, 2007, 20:10:40
Yes, glUniform1ARB takes one float if the target is a float uniform, but can take more than one variable if the target is an array of floats. Take a look at http://www.opengl.org/sdk/docs/man for a better explanation. Make sure you're setting the position and limit correctly and that the target is an array of the float array type if passing in more than one value.

- elias
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 29, 2007, 00:47:40
The position and limit HAVE the correct values and I'm storing the falues of a float array to the buffer as you can see here:

if (value instanceof float[])
{
    float[] v = (float[])value;
   
    setupTempFloatBuffer( v.length );
   
    tmpFloatBuffer.put( v );
    tmpFloatBuffer.rewind();
    ARBShaderObjects.glUniform1ARB( location, tmpFloatBuffer );
}


And this is the code, that sets up the temp FloatBuffer:

private void setupTempFloatBuffer(int minCap)
{
    if (tmpFloatBuffer.capacity() < minCap)
    {
        tmpFloatBuffer = BufferUtils.createFloatBuffer( (int)(minCap * 1.5) );
    }
   
    if (tmpFloatBuffer.limit() != minCap)
    {
        tmpFloatBuffer.limit( minCap );
    }
   
    tmpFloatBuffer.clear();
}


Is there anything incorrect?

The JavaDoc of FloatBuffer.limit( int ) says, that it only sets the limit to the given value, if it is larger than the current limit. Is there a way to set the limit in the case it is lower? My buffer has an initial capacity/limit of 128.

Marvin
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 29, 2007, 01:21:30
ok. I found a workaround:

private void setupTempFloatBuffer(int minCap)
{
    if (tmpFloatBuffer.capacity() < minCap)
    {
        tmpFloatBuffer = BufferUtils.createFloatBuffer( (int)(minCap * 1.5) );
    }
   
    tmpFloatBuffer.position( tmpFloatBuffer.limit() - minCap );
    tmpFloatBuffer.mark();
}


And I call it with:

if (value instanceof float[])
{
    float[] v = (float[])value;
   
    setupTempFloatBuffer( v.length );
   
    tmpFloatBuffer.put( v );
    tmpFloatBuffer.reset();
    ARBShaderObjects.glUniform1ARB( location, tmpFloatBuffer );
}


But it doesn't work for glGetUniformLocationARB. I use it this way:

private void setupTempByteBuffer(int minCap)
{
    if (tmpByteBuffer.capacity() < minCap)
    {
        tmpByteBuffer = BufferUtils.createByteBuffer( (int)(minCap * 1.5) );
    }
   
    tmpByteBuffer.position( tmpByteBuffer.limit() - minCap );
    tmpByteBuffer.mark();
}


called:

final String key = uniformVarNames.get( k );

// create a location for this key
setupTempByteBuffer( key.length() + 1 );
tmpByteBuffer.put( key.getBytes() );
tmpByteBuffer.reset();
int location = ARBShaderObjects.glGetUniformLocationARB( shaderProgram.getGlHandle(), tmpByteBuffer );


I get this stack trace when I use it like this:

java.lang.IllegalArgumentException: Missing null termination
at org.lwjgl.BufferChecks.checkNullTerminated(BufferChecks.java:76)
at org.lwjgl.opengl.ARBShaderObjects.glGetUniformLocationARB(ARBShaderObjects.java:373)
at org.xith3d.render.lwjgl.GLSLShaderProgramShaderPeer.applyUniformVariables(GLSLShaderProgramShaderPeer.java:283)
at org.xith3d.render.lwjgl.GLSLShaderProgramShaderPeer.shade(GLSLShaderProgramShaderPeer.java:513)
at org.xith3d.render.CanvasPeer.setState(CanvasPeer.java:629)
at org.xith3d.render.CanvasPeer.renderAtom(CanvasPeer.java:650)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.drawBin(CanvasPeerImplBase.java:451)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.renderMain(CanvasPeerImplBase.java:862)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.display(CanvasPeerImplBase.java:1049)
at org.xith3d.render.lwjgl.CanvasPeerImplNative.doRender(CanvasPeerImplNative.java:298)
at org.xith3d.render.lwjgl.CanvasPeerImplBase.render(CanvasPeerImplBase.java:1116)
at org.xith3d.render.DefaultRenderer.renderOnceInternal(DefaultRenderer.java:459)
at org.xith3d.render.DefaultRenderer.renderOnce(DefaultRenderer.java:480)
at org.xith3d.base.Xith3DEnvironment.render(Xith3DEnvironment.java:427)
at org.xith3d.loop.RenderLoop.renderNextFrame(RenderLoop.java:559)
at org.xith3d.loop.RenderLoop.loopIteration(RenderLoop.java:582)
at org.xith3d.loop.RenderLoop.update(RenderLoop.java:635)
at org.xith3d.loop.UpdatingThread.nextIteration(UpdatingThread.java:175)
at org.xith3d.loop.RenderLoop.loop(RenderLoop.java:691)
at org.xith3d.loop.UpdatingThread.run(UpdatingThread.java:223)
at org.xith3d.loop.RenderLoop.run(RenderLoop.java:712)
at java.lang.Thread.run(Thread.java:619)


Any ideas?

Marvin
Title: Re: Shader, Uniform variables and buffer sizes
Post by: elias on March 29, 2007, 04:45:34
Quote from: Qudus on March 29, 2007, 00:47:40
The position and limit HAVE the correct values and I'm storing the falues of a float array to the buffer as you can see here:

if (value instanceof float[])
{
    float[] v = (float[])value;
   
    setupTempFloatBuffer( v.length );
   
    tmpFloatBuffer.put( v );
    tmpFloatBuffer.rewind();
    ARBShaderObjects.glUniform1ARB( location, tmpFloatBuffer );
}


Rewind is not the best choice, but it should work in this case.


private void setupTempFloatBuffer(int minCap)
{
    if (tmpFloatBuffer.capacity() < minCap)
    {
        tmpFloatBuffer = BufferUtils.createFloatBuffer( (int)(minCap * 1.5) );
    }
   
    if (tmpFloatBuffer.limit() != minCap)
    {
        tmpFloatBuffer.limit( minCap );
    }
   
    tmpFloatBuffer.clear();
}


This is wrong, you're clear()ing the buffer right after setting the limit. Clearing means setting the position to 0, and the limit to the buffer capacity. I'd do something like this (untested):


private void setupTempFloatBuffer(int minCap)
{
    if (tmpFloatBuffer.capacity() < minCap)
    {
        tmpFloatBuffer = BufferUtils.createFloatBuffer( (int)(minCap * 1.5) );
    }
   
    tmpFloatBuffer.clear();
}

if (value instanceof float[])
{
    float[] v = (float[])value;
   
    setupTempFloatBuffer( v.length );
   
    tmpFloatBuffer.put( v );
    tmpFloatBuffer.flip();
    ARBShaderObjects.glUniform1ARB( location, tmpFloatBuffer );
}


Notice that the limit is not set anymore, and rewind is replaced with flip() which will set the position to 0 and the limit to the old position.

Regarding the "java.lang.IllegalArgumentException: Missing null termination" exception, you need to have the last byte in the uniform name be 0, to satisfy OpenGL requirements of a C string. When you didn't reuse buffers, the trailing bytes were always 0, so it didn't appear as a problem before.

- elias
Title: Re: Shader, Uniform variables and buffer sizes
Post by: Qudus on March 29, 2007, 16:05:50
Stupid me. pushing an additional (byte)0 to the tmpByteBuffer did the trick. I had that once in my coding, but it didn't help. Now it solved my problem.

Thanks a lot for all your help :).

Marvin