Nothing rendering when separating OpenGL thread from GLFW/Event Processing

Started by blobjim, September 01, 2016, 20:11:34

Previous topic - Next topic

blobjim

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  :)

spasi

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 that demonstrates one working approach.

blobjim

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 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

spasi

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().

CoDi

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.

blobjim

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.