LWJGL Forum

Programming => Lightweight Java Gaming Library => Topic started by: blobjim on September 01, 2016, 20:11:34

Title: Nothing rendering when separating OpenGL thread from GLFW/Event Processing
Post by: blobjim on September 01, 2016, 20:11:34
I recently attempted separating the OpenGL context thread from the GLFW thread but when I render, the only OpenGL drawing that seems to be actually working is the glClear(GL_COLOR_BUFFER_BIT).

All GLFW window processing is working smoothly.

I have an event thread (Main thread, GLFW), game thread (game stuff, no API calls), and rendering thread (OpenGL context thread).

GLFW Window Setup:


public Display(String title) {
glfwInit();

screenWidth = glfwGetVideoMode(glfwGetPrimaryMonitor()).width();
screenHeight = glfwGetVideoMode(glfwGetPrimaryMonitor()).height();

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);

displayID = glfwCreateWindow(screenWidth, screenHeight, title, 0, 0);
glfwSetWindowSizeLimits(displayID, screenWidth/2, screenHeight/2, screenWidth, screenHeight);
glfwSetWindowAspectRatio(displayID, screenWidth, screenHeight);

input = new InputManager();

glfwSetKeyCallback(displayID, input.getGlfwKeyCallback());
glfwSetScrollCallback(displayID, input.getGlfwScrollCallback());
glfwSetMouseButtonCallback(displayID, input.getGlfwMouseButtonCallback());
glfwSetCursorPosCallback(displayID, input.getGlfwCursorPosCallback());
glfwSetFramebufferSizeCallback(displayID, input.getGlfwFramebufferSizeCallback());
glfwSetWindowRefreshCallback(displayID, input.getGlfwWindowRefreshCallback());

input.getKeyHandlers().add(this);
}


GLFW Event Thread:



package main;

import graphics.Display;
import org.lwjgl.glfw.GLFWErrorCallback;

public class EventThread {

public static void main(String[] args) {

Thread.currentThread().setName("Event Thread");

//Set the path of native libraries (i.e. .dll files)
System.setProperty("java.library.path", "libraries/natives/");

//Create the game display (just calls GLFW functions with a stored window ID)
Display display = new Display("2D Game Engine Demo");
GameState.setDisplay(display);

//Create the GLFW error output for the even thread
GLFWErrorCallback.createPrint(System.err).set();

//Create an instance of the game logic thread
GameThread game = new GameThread();

//Create an instance of the rendering/graphics thread (OpenGL context)
GraphicsThread graphics = new GraphicsThread();

//Start the game thread
game.start();

//Start the graphics thread
graphics.start();

//set the glfw window to the primary monitor
GameState.getDisplay().setFullscreen(true);

while(!GameState.getShouldExit()) {
GameState.getDisplay().waitEvents(); //glfwWaitEvents()
}

//glfwDestroyWindow
display.destroy();
}
}



Context Setup (called in OpenGL context thread below):


public void setupContext() { //this method is inside the Display class, but is never called from the main thread
glfwMakeContextCurrent(displayID);
GL.createCapabilities();
glfwSwapInterval(0);
glClearColor(1.0f,1.0f,1.0f,1.0f);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}



OpenGL Context Thread (Starts by setting up the context, buffering data, then rendering):



package main;

import graphics.Renderer;
import graphics.Shader;
import graphics.ShaderProgram;
import input.controller.CameraController;
import util.ResourceManager;
import world.Camera;

public final class GraphicsThread extends Thread {
public static Object renderLock = new Object();

@Override
public void run() {
this.setName("Graphics Thread");
GameState.getDisplay().setupContext(); //method shown above, creates OpenGL capabilties and set context to this thread for the display


ResourceManager.loadResources(); //create "Models" and buffer data to GPU

ShaderProgram shaderProgram = new ShaderProgram( //create the shader program
new Shader("resources/shaders/vertexShader", org.lwjgl.opengl.GL20.GL_VERTEX_SHADER),
new Shader("resources/shaders/fragmentShader", org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER)
);

//Create the camera
Camera camera = new Camera( 0, 0, 0.07f, 1.5f);
new CameraController(camera);

//Create the renderer and add the camera and shader program to it
Renderer renderer = new Renderer(shaderProgram, camera);
GameState.setRenderer(renderer);

synchronized(GameThread.modelLoadLock) { //Allow game thread to look up models from the ResourceManager now that they're loaded
GameThread.modelLoadLock.notifyAll();
}

synchronized(renderLock) { //Wait for game world to be created before beginning rendering
try {
renderLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

while(!GameState.getShouldExit()) { //while glfwWindowShouldClose is false
render();
}
}

private void render() {
Renderer renderer = GameState.getRenderer();
renderer.getCamera().setX(GameState.getPlayer().position().getX());
renderer.getCamera().setY(GameState.getPlayer().position().getY());
renderer.getCamera().update();
renderer.updateViewport(); //glViewport(0,0,window width, window height)
glClear(GL_COLOR_BUFFER_BIT);
renderer.render(GameState.getWorld()); //just loads data to GPU such as the transformation matrix, and does glDrawElements
GameState.getDisplay().refresh(); //glfwSwapBuffers(windowID)
}
}



Before I attempted to separate the GLFW and OpenGL threads, my program was displaying graphics on the screen perfectly.
Thanks for taking the time to look through my post  :)
Title: Re: Nothing rendering when separating OpenGL thread from GLFW/Event Processing
Post by: spasi on September 01, 2016, 21:24:59
The code you posted is too opaque, can't see any GLFW calls in there and in which thread(s) they're happening. Best I can do is point you to the code sample (https://github.com/LWJGL/lwjgl3/blob/master/modules/core/src/test/java/org/lwjgl/demo/glfw/Threads.java) that demonstrates one working approach.
Title: Re: Nothing rendering when separating OpenGL thread from GLFW/Event Processing
Post by: blobjim on September 02, 2016, 01:04:59
Quote from: spasi on September 01, 2016, 21:24:59
The code you posted is too opaque, can't see any GLFW calls in there and in which thread(s) they're happening. Best I can do is point you to the code sample (https://github.com/LWJGL/lwjgl3/blob/master/modules/core/src/test/java/org/lwjgl/demo/glfw/Threads.java) that demonstrates one working approach.

I've updated my code to explain what my program does more and show more of the GLFW calls, would you mind taking another look? <3
Title: Re: Nothing rendering when separating OpenGL thread from GLFW/Event Processing
Post by: spasi on September 02, 2016, 07:44:16
The GLFW calls looks fine, so it must be something specific to your rendering or synchronization code. Are you checking for OpenGL errors? The easiest way is creating a debug context (glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE)) and calling GLUtil.setupDebugMessageCallback() after GL.createCapabilities().
Title: Re: Nothing rendering when separating OpenGL thread from GLFW/Event Processing
Post by: CoDi on September 02, 2016, 08:07:34
What's your motivation to separate rendering and event processing?

After spawning the render thread, your main thread calls setFullscreen() and starts looping waitEvents() without any form of synchronisation. Meanwhile, the render thread does all it's initialisation and loading. http://www.glfw.org/docs/latest/intro.html#thread_safety doesn't mention anything explicitly, but I wouldn't be surprised about nasty side effects.

As GLFW event processing may trigger callbacks (on the event thread), which then result in more GLFW and/or GL calls (not safe anymore on the event thread), you'll have to do some kind of event queuing or synchronisation yourself. I honestly don't see an advantage to that.
Title: Re: Nothing rendering when separating OpenGL thread from GLFW/Event Processing
Post by: blobjim on September 02, 2016, 21:41:23
Quote from: spasi on September 02, 2016, 07:44:16
The GLFW calls looks fine, so it must be something specific to your rendering or synchronization code. Are you checking for OpenGL errors? The easiest way is creating a debug context (glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE)) and calling GLUtil.setupDebugMessageCallback() after GL.createCapabilities().

This is my current console output with the opengl debug output (thanks for the tip, this may come in handy more in the future :D):


Beginning event processing on thread Event Thread
[LWJGL] OpenGL debug message
ID: 0x20071
Source: API
Type: OTHER
Severity: NOTIFICATION
Message: Buffer detailed info: Buffer object 1 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations.
[LWJGL] OpenGL debug message
ID: 0x20071
Source: API
Type: OTHER
Severity: NOTIFICATION
Message: Buffer detailed info: Buffer object 2 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations.
[LWJGL] OpenGL debug message
ID: 0x20071
Source: API
Type: OTHER
Severity: NOTIFICATION
Message: Buffer detailed info: Buffer object 3 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations.
Beginning  game loop on thread Game Thread
Beginning rendering on thread Graphics Thread
(I close the window here)
Exiting rendering loop
Exiting game loop
Exiting event loop
Memory usage: 17 MB



It looks like there aren't any errors occurring (interesting that the buffers types still mention ARB, when they are included in standard OpenGL)

Quote from: CoDi on September 02, 2016, 08:07:34
What's your motivation to separate rendering and event processing?

After spawning the render thread, your main thread calls setFullscreen() and starts looping waitEvents() without any form of synchronisation. Meanwhile, the render thread does all it's initialisation and loading. http://www.glfw.org/docs/latest/intro.html#thread_safety doesn't mention anything explicitly, but I wouldn't be surprised about nasty side effects.

As GLFW event processing may trigger callbacks (on the event thread), which then result in more GLFW and/or GL calls (not safe anymore on the event thread), you'll have to do some kind of event queuing or synchronisation yourself. I honestly don't see an advantage to that.

I'll probably revert back to combined GLFW/OpenGL processing on one thread if I can't figure this stuff out. I just thought it would be cleaner/more efficient to have event processing only occur when an event actually happens using glfwWaitEvents() because input shouldn't be tied to the speed of my rendering thread.