Search code examples
javaopenglglsllwjglglfw

JVM crash caused by glUniform1fv() on LWJGL


Me and a friend of mine are making a 3D engine with LWJGL, and after trying to pass a float array to my fragment shader as a uniform, the JVM started crashing. Here's the relevant part of the JVM crash log:

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  org.lwjgl.opengl.GL20C.nglUniform1fv(IIJ)V+0
j  org.lwjgl.opengl.GL20C.glUniform1fv(ILjava/nio/FloatBuffer;)V+9
j  org.lwjgl.opengl.GL20.glUniform1fv(ILjava/nio/FloatBuffer;)V+2
j  com.alyxferrari.neo3d.obj.Environment3D.rebuildLights()V+118
j  com.alyxferrari.neo3d.gfx.NEOEngine.startRender()V+69
j  com.alyxferrari.neo3d.example.NEOExample.main([Ljava/lang/String;)V+65
v  ~StubRoutines::call_stub

From this, I obviously thought that the issue stemmed from assigning the float[] uniforms in this method:

public void rebuildLights() {
        float[] xs = new float[DirectionalLight.MAX_IN_SHADER];
        float[] ys = new float[DirectionalLight.MAX_IN_SHADER];
        float[] zs = new float[DirectionalLight.MAX_IN_SHADER];
        float[] strengths = new float[DirectionalLight.MAX_IN_SHADER];
        for (int i = 0; i < lights.length; i++) {
            xs[i] = lights[i].getDirection().x;
            ys[i] = lights[i].getDirection().y;
            zs[i] = lights[i].getDirection().z;
            strengths[i] = lights[i].getStrength();
        }
        glUniform1fv(glGetUniformLocation(NEOEngine.getShader(), "lightXs"), FloatBuffer.wrap(xs));
        glUniform1fv(glGetUniformLocation(NEOEngine.getShader(), "lightYs"), FloatBuffer.wrap(ys));
        glUniform1fv(glGetUniformLocation(NEOEngine.getShader(), "lightZs"), FloatBuffer.wrap(zs));
        glUniform1fv(glGetUniformLocation(NEOEngine.getShader(), "strengths"), FloatBuffer.wrap(strengths));
        glUniform1f(glGetUniformLocation(NEOEngine.getShader(), "ambience"), ambience);
        glUniform1i(glGetUniformLocation(NEOEngine.getShader(), "count"), lights.length);
    }

As you can see, I'm populating some arrays based on data in my light objects, and I'm wrapping them in a FloatBuffer to pass to glUniform1fv(). The only thing I can think of is that this maybe isn't how I'm supposed to be making a FloatBuffer. When I saw that in LWJGL, glUniform1fv() takes a FloatBuffer and not a float[], I looked at Java's documentation for the FloatBuffer class and saw that FloatBuffer.wrap(float[]) seemed to be a good way to make a FloatBuffer from a float[]. Is there a specific way to make one that you need to do for LWJGL's OpenGL bindings? Or is there something else wrong with my code? Thanks in advance :)


Solution

  • LWJGL only works with direct NIO Buffers, that is, ByteBuffers that are not backed by on-heap Java arrays but backed by off-heap virtual memory allocated in the JVM's process but outside of the JVM-managed garbage-collected heap. This is to efficiently communicate native virtual memory to low-level libraries, such as OpenGL, without having to "pin" potentially garbage-collectable/moveable memory before handing a pointer to it to native libraries.

    See the section "Direct vs. non-direct buffers" in https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/nio/ByteBuffer.html

    The reason for the crash is that the non-direct ByteBuffer you supply (the one being created by .wrap(array)) has an internal address field value of 0. This is what LWJGL looks at when it actually calls the native OpenGL function. It reads that address field value and expects this to be the virtual memory address of the client memory to upload to the uniform, which OpenGL will then treat as such.

    So, in essence: When you use LWJGL, you must always only use direct NIO Buffers, not ones that are wrappers of arrays!

    You should first read this LWJGL 3 blog post about efficient memory management in LWJGL 3: https://blog.lwjgl.org/memory-management-in-lwjgl-3/