Search code examples
javaimplementationjoglgame-loop

JOGL efficient render loop


I am new to JOGL and I am trying to figure out how to make a render loop with it... I know that there is the animator and FPS animator classes that I can use, but they seem fairly restrictive. I have written a few render loops in the past that I like a lot better that I would rather use but I can't seem to properly implement it with JOGL's GLEventListener Class.

Here is my render loop (and the class it comes in)

package com.richardkase.game;

public class Game implements Runnable {

public final String TITLE = "Test Game";

private boolean running = false;
private Thread thread;

private FPSCounter fps;


////// Constructor //////

public Game() {

    fps = new FPSCounter(150, 60);
}


////// Game Loop //////

@Override
public void run() {
    while (running) {
        fps.findDeltas();

        // this executes at "the second argument" of fps times a second
        if (fps.checkTickDelta())
            tick();

        // this executes at "the first argument" of fps times a second
        if (fps.checkFrameDelta())
            render();

        // this code executes once a second
        fps.checkPassingSecond();
    }
}


////// Tick Methods //////

private void tick() {
    long before = System.nanoTime();
    // code goes here

    fps.tick(before);
}


////// Render Methods //////

private void render() {
    long before = System.nanoTime();
    // code goes here

    fps.render(before);
}



////// Thread Methods //////

private synchronized void start() {
    if (running)
        return;
    running = true;
    thread = new Thread(this);
    thread.start();
}

private synchronized void stop() {
    if (!running)
        return;
    running = false;
    try {
        thread.join();
    } catch (InterruptedException e) {
        System.exit(1);
        e.printStackTrace();
    }
}


///////// Main Method //////////

public static void main(String[] args) {
    Game game = new Game();

    JFrame frame = new JFrame(game.TITLE);
    frame.setSize(500, 500);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setResizable(true);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);

    game.start();
}
}

here is FPSCounter class just for reference, all it does is keep track of time to make sure everything executes when it is suppose to

public class FPSCounter {

private long lastTime = System.nanoTime();

private double tickRate;
private double tickCheck;
private double tickDelta;

private double frameRate;
private double frameCheck;
private double frameDelta;

private int updates;
private int frames;
private long timer;

private long nanosPerFrame;
private long nanosPerUpdate;


////// Constructor ///////

public FPSCounter(double frameRate, double tickRate) {

    this.frameRate = frameRate;
    frameCheck = 1_000_000_000 / this.frameRate;
    frameDelta = 0;

    this.tickRate = tickRate;
    tickCheck = 1_000_000_000 / this.tickRate;
    tickDelta = 0;

    updates = 0;
    frames = 0;
    timer = System.currentTimeMillis();

}


////// find delta //////

public void findDeltas() {
    long now = System.nanoTime();
    tickDelta += now - lastTime;
    frameDelta += now - lastTime;
    lastTime = now;
}


////// Delta Check //////

public boolean checkTickDelta() {
    if (tickDelta >= tickCheck)  {
        tickDelta = 0;
        return true;
    }
    return false;
}

public boolean checkFrameDelta() {
    if (frameDelta >= frameCheck)  {
        frameDelta = 0;
        return true;
    }
    return false;
}


////// Second Check //////

public void checkPassingSecond() {
    if (System.currentTimeMillis() - timer > 1000) {
        System.out.println(updates + " updates, fps is " + frames);
        timer += 1000;
        frames = 0;
        updates = 0;
    }
}


////// Game Loop Methods ///////

public void render(long before) {
    long after = System.nanoTime();
    nanosPerFrame = after - before;
    frames++;
}

public void tick(long before) {
    long after = System.nanoTime();
    nanosPerUpdate = after - before;
    updates++;
}
}

how should I add the actual content to this game loop? should I have this class also extend GLEventListener or have a reference to a class that extends it? Or is there a more efficient way of doing it with the animator class that gives me the this kind of control that I am overlooking?

any help would be greatly appreciated!! Thanks

EDIT:

I should also add that I am very new to JOGL, found out about it a few days ago because I was trying to render things in 3d, so I barely know more than the profile system in JOGL... explainations are GREATLY appreciated!

I think I should layout what I'm looking for here. I need a rendering loop that renders in a similar way above, where the game updates its state at one rate, and redraws all the graphics at another rate.


Solution

  • This is how I started with JOGL... It is pretty straightforward and self-explanatory, but if requested, I can explain the code in detail ;)

    import java.awt.GraphicsEnvironment;
    import java.awt.Rectangle;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    
    import javax.media.opengl.GL2;
    import javax.media.opengl.GLAutoDrawable;
    import javax.media.opengl.GLCapabilities;
    import javax.media.opengl.GLEventListener;
    import javax.media.opengl.GLProfile;
    import javax.media.opengl.awt.GLCanvas;
    import javax.swing.JFrame;
    
    import com.jogamp.opengl.util.FPSAnimator;
    
    public class OpenGLMain implements GLEventListener { 
    
        private static FPSAnimator animator;
        private static int width;
        private static int height;
        private static GL2 gl;
        public static Rectangle screenSize;
        public static JFrame frame;
    
        public static void main(String[] args) {
            GLProfile glprofile = GLProfile.getMaximum(true);
            GLCapabilities capabilities = new GLCapabilities(glprofile);
            GLCanvas canvas = new GLCanvas(capabilities);
    
            screenSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
            width = (int) screenSize.getWidth();
            height = (int) screenSize.getHeight();
    
            frame = new JFrame("Frame name");
            frame.setAlwaysOnTop(false);
            frame.setSize(width, height);
            frame.add(canvas);
            frame.setUndecorated(true);
            frame.setVisible(true);
    
            animator = new FPSAnimator(25);
            animator.add(canvas);
            animator.start();
    
            canvas.addGLEventListener(new OpenGLMain());
            canvas.requestFocus();
    
            Listeners.keyClicks(canvas);
            Listeners.mouseMovement(canvas);
            Listeners.mouseClicks(canvas);
            Listeners.mouseScrolled(canvas);
    
            frame.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
        }
    
        public void display(GLAutoDrawable drawable) {
            update();
            render(drawable);
        }
    
        public void dispose(GLAutoDrawable drawable) {
        }
    
        public void init(GLAutoDrawable drawable) {
            gl = drawable.getGL().getGL2();
            Scenes.init(drawable, gl);
        }
    
        public void reshape(GLAutoDrawable drawable, int arg1, int arg2, int arg3, int arg4) {
        }
    
        private void update() {
            Scenes.update();
        }
    
        private void render(GLAutoDrawable drawable) {
            Scenes.render(drawable);
        }
    }
    

    public class OpenGLMain implements GLEventListener {
    

    This line allows you to implement GLEvents that come with JOGL


    GLProfile glprofile = GLProfile.getMaximum(true);
    GLCapabilities capabilities = new GLCapabilities(glprofile);
    GLCanvas canvas = new GLCanvas(capabilities);
    

    This will get the maximum possible openGL context to use in the canvas


    screenSize = GraphicsEnvironment
        .getLocalGraphicsEnvironment()
        .getMaximumWindowBounds();
    width = (int) screenSize.getWidth();
    height = (int) screenSize.getHeight();
    

    Maximum window bounds will give you usable desktop space, but you could just set width and height to any other size you want...


    frame = new JFrame("Frame name");
    frame.setAlwaysOnTop(false);
    frame.setSize(width, height);
    frame.add(canvas);
    frame.setUndecorated(true);
    frame.setVisible(true);
    

    This is the JFrame that will hold the OpenGL canvas. You can set it up any way you like :)


    animator = new FPSAnimator(25);
    animator.add(canvas);
    animator.start();
    

    This creates animator with framerate of 25fps, connects it to the canvas and starts the animator thread


    canvas.addGLEventListener(new OpenGLMain());
    canvas.requestFocus();
    

    This will add a GLEvent listener to the newly created instance of your class, but I guess this is the point at which you would insert a separate GLEventListener class


    Listeners.keyClicks(canvas);
    Listeners.mouseMovement(canvas);
    Listeners.mouseClicks(canvas);
    Listeners.mouseScrolled(canvas);
    

    This is my way of starting listeners for keys, mouse movement, clicks and scrolls... it is located in a separate static class


    frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
    

    This is just a listener for window closing event...


    public void display(GLAutoDrawable drawable) {
        update();
        render(drawable);
    }
    

    Here is where the main loop happens... I've split it up into update(); and render(); methods, so that everything gets updated before it gets rendered.


    public void init(GLAutoDrawable drawable) {
        gl = drawable.getGL().getGL2();
        Scenes.init(drawable, gl);
    }
    

    The init method that happens before rendering... I have a separate static class Scenes, where I create layers and logical structure of the scenes to draw on canvas.