javamavenopenglshaderjogl

Black screen when using GL3 (OpenGL) with Maven and JOGL


I am trying to begin working with OpenGL by drawing a quad with the simplest of shaders, but when using GL3 capabilities, I just get a black screen. I wasn't able to really debug this because (a) I don't understand much yet, and (b) I have tried to ask various LLMs, but I think the problem is too specific.

I'm running Windows 10 (Java 17), and have an NVIDIA GeForce RTX 3060 running NVIDIA Studio Driver 576.

I'll post the code here, but for the ease of viewing, here is the repo: https://github.com/MikhailDolgopolov/OpenGL-VoronoiTerrain

(I'm using GLJPanel, because with GLCanvas it just can't detect my graphics configuration)

When using DebugGL, it seems to only show warnings and no errors, so I'm completely clueless about what sort of thing can even be the root cause. I was able to see a simple triangle using GL2, but no progress on shaders in GL3.

package com.galeon;

import com.jogamp.opengl.*;
import com.jogamp.opengl.awt.GLJPanel;
import com.jogamp.opengl.util.FPSAnimator;

import javax.swing.JFrame;
import java.awt.EventQueue;

public interface SceneRenderer extends GLEventListener  {
    void init(GLAutoDrawable drawable);
    void display(GLAutoDrawable drawable);
    void reshape(GLAutoDrawable drawable, int x, int y, int w, int h);
    void dispose(GLAutoDrawable drawable);
}

public class WindowDrawer implements GLEventListener {
    private final SceneRenderer renderer;

    public WindowDrawer(SceneRenderer renderer) {
        this.renderer = renderer;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            GLProfile.initSingleton();
            GLProfile profile = GLProfile.get(GLProfile.GL3);
            GLCapabilities caps = new GLCapabilities(profile);

            GLJPanel canvas = new GLJPanel(caps);
            WindowDrawer dw = new WindowDrawer(new SimpleQuadRenderer());
            canvas.addGLEventListener(dw);

            FPSAnimator animator = new FPSAnimator(canvas, 1, true);
            animator.start();

            JFrame frame = new JFrame("Red Quad Test");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(canvas);
            frame.setSize(800, 600);
            frame.setVisible(true);
        });
    }

    @Override public void init(GLAutoDrawable drawable) {
        renderer.init(drawable);
    }
    @Override public void display(GLAutoDrawable drawable) {
        renderer.display(drawable);
    }
    @Override public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
        renderer.reshape(drawable, x, y, w, h);
    }
    @Override public void dispose(GLAutoDrawable drawable) {
        renderer.dispose(drawable);
    }
}
package com.galeon;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL3;

public class ShaderLoader {

    private final int programId;
    private final int vertexShaderId;
    private final int fragmentShaderId;

    public ShaderLoader(GL3 gl, String vertPath, String fragPath) {
        programId = gl.glCreateProgram();
        vertexShaderId=compileAndAttach(gl, vertPath, GL3.GL_VERTEX_SHADER);
        fragmentShaderId=compileAndAttach(gl, fragPath, GL3.GL_FRAGMENT_SHADER);
        linkProgram(gl);
    }

    private int compileAndAttach(GL3 gl, String path, int type) {
        String src = readFile(path);
        int shaderId = gl.glCreateShader(type);
        gl.glShaderSource(shaderId, 1, new String[]{src}, null);
        gl.glCompileShader(shaderId);

        int[] status = new int[1];
        gl.glGetShaderiv(shaderId, GL3.GL_COMPILE_STATUS, status, 0);
        System.out.println("  compile status = " + status[0]);
        if (status[0] == GL.GL_FALSE) {
            int[] len = new int[1];
            gl.glGetShaderiv(shaderId, GL3.GL_INFO_LOG_LENGTH, len, 0);
            byte[] buf = new byte[len[0]];
            gl.glGetShaderInfoLog(shaderId, buf.length, null, 0, buf, 0);
            System.err.println(">>> SHADER INFO LOG:\n" + new String(buf));
        }
        gl.glAttachShader(programId, shaderId);
        return shaderId;
    }

    private void linkProgram(GL3 gl) {
        gl.glLinkProgram(programId);

        int[] status = new int[1];
        gl.glGetProgramiv(programId, GL3.GL_LINK_STATUS, status, 0);
        System.out.println("  link status = " + status[0]);
        if (status[0] == GL.GL_FALSE) {
            int[] len = new int[1];
            gl.glGetProgramiv(programId, GL3.GL_INFO_LOG_LENGTH, len, 0);
            byte[] buf = new byte[len[0]];
            gl.glGetProgramInfoLog(programId, buf.length, null, 0, buf, 0);
            System.err.println(">>> PROGRAM INFO LOG:\n" + new String(buf));
        }

        gl.glValidateProgram(programId);
    }

    private String readFile(String path) {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) sb.append(line).append("\n");
            return sb.toString();
        } catch (IOException e) {
            throw new RuntimeException("Unable to read shader file: " + path, e);
        }
    }

    public int getProgramId() {
        return programId;
    }

    public int getVertexShaderId() { return vertexShaderId; }
    public int getFragmentShaderId() { return fragmentShaderId; }
}
package com.galeon;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.*;
import java.nio.FloatBuffer;

public class SimpleQuadRenderer implements SceneRenderer {
    private int programId, vao, vbo;

    @Override
    public void init(GLAutoDrawable drawable) {
        GL3 gl = drawable.getGL().getGL3();

        // compile & link
        ShaderLoader loader = new ShaderLoader(
            gl,
            "src/main/shaders/vertex_simple.glsl",
            "src/main/shaders/fragment_simple.glsl"
        );
        programId = loader.getProgramId();

        // quad coords
        float s = 0.5f;
        float[] quad = {
            -s,-s,  s,-s,  -s, s,
            -s, s,  s,-s,   s, s
        };
        FloatBuffer fb = Buffers.newDirectFloatBuffer(quad);

        // VAO
        int[] tmp = new int[1];
        gl.glGenVertexArrays(1, tmp, 0);
        vao = tmp[0];
        gl.glBindVertexArray(vao);

        // VBO
        gl.glGenBuffers(1, tmp, 0);
        vbo = tmp[0];
        gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo);
        gl.glBufferData(GL.GL_ARRAY_BUFFER, quad.length * Float.BYTES, fb, GL.GL_STATIC_DRAW);

        // attribute 0 ⇒ vec2 position
        gl.glVertexAttribPointer(0, 2, GL.GL_FLOAT, false, 0, 0);
        gl.glEnableVertexAttribArray(0);

        // unbind
        gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
        gl.glBindVertexArray(0);
    }

    @Override
    public void display(GLAutoDrawable drawable) {
        GL3 gl = drawable.getGL().getGL3();

        gl.glClearColor(0f, 0.5f, 0f, 1f);
        gl.glClear(GL.GL_COLOR_BUFFER_BIT);

        gl.glUseProgram(programId);
        gl.glBindVertexArray(vao);
        gl.glDrawArrays(GL.GL_TRIANGLES, 0, 6);
        gl.glBindVertexArray(0);

        int e = gl.glGetError();
        if (e != GL.GL_NO_ERROR) {
            System.err.println("GL Error: 0x" + Integer.toHexString(e));
        }
    }

    @Override public void reshape(GLAutoDrawable d, int x, int y, int w, int h) {
        d.getGL().getGL3().glViewport(0, 0, w, h);
    }
    @Override public void dispose(GLAutoDrawable d) {
        GL3 gl = d.getGL().getGL3();
        gl.glDeleteProgram(programId);
        gl.glDeleteBuffers(1, new int[]{vbo}, 0);
        gl.glDeleteVertexArrays(1, new int[]{vao}, 0);
    }
}

fragment_simple.glsl:

#version 330 core
layout(location = 0) out vec4 FragColor;

void main() {
    FragColor = vec4(1.0, 0.0, 0.0, 1.0);  // solid red
}

vertex_simple.glsl:

#version 330 core
layout(location = 0) in vec2 position;

void main() {
    gl_Position = vec4(position, 0.0, 1.0);
}

The solution may be extremely obvious, but I'm just lost after staring at these 5 files for a day or two with no progress.


Solution

  • GLJPanel is provided for compatibility with Swing as per Javadoc. Try using GLWindow.

    //GLJPanel canvas = new GLJPanel(caps);
    GLWindow window = GLWindow.create(caps);
    
    // replace JFrame with the window
    window.setTitle("Red Quad Test");
    window.setSize(800, 600);
    window.setVisible(true);
    window.addWindowListener(new WindowAdapter() {
        @Override
        public void windowDestroyed(WindowEvent e) {
            animator.stop();
            System.exit(1);
        }
    });