How to switch the thread of an OpenGL context?

Started by pdid, February 01, 2017, 02:51:50

Previous topic - Next topic

pdid

I'm trying to set up a multi-threaded game and I'm trying to initialize an OpenGL context in one thread (the same one where I create the window) and then switch the context to a different thread using glfwMakeContextCurrent() once initialized. The issue is that when I call glfwMakeContextCurrent() on the new thread I get a JVM crash with an EXCEPTION_ACCESS_VIOLATION. Included is the error message and error file, in case they will be helpful.

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x70a10f29, pid=8244, tid=0x000027e0
#
# JRE version: Java(TM) SE Runtime Environment (8.0_111-b14) (build 1.8.0_111-b14)
# Java VM: Java HotSpot(TM) Client VM (25.111-b14 mixed mode windows-x86 )
# Problematic frame:
# C  [lwjgl_opengl32.dll+0x10f29]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Users\Acer Computer\Desktop\Ian\Programming\Eclipse\Workspaces\Hones Game Engine\Test Game (Key Chase)\hs_err_pid8244.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

spasi

Hard to tell what might be wrong without seeing some code, but it sounds like you are not calling GL.setCapabilities() after making the context current in the other thread.

pdid

Thanks for the help, I tried that but that was not the error. Instead I was forgetting to remove the context from the thread it was on before. However this leads me to another issue. If I want to swap a GL context from one thread to another, I would have to do some semi-complicated multi-threading to make sure the context is removed from the first thread before it is added to the second. I could do this, but an easier solution, if it exists, is to delete the context of a thread, from a separate thread. I'm not even sure if that is possible, but if it is, and anyone could tell me how, that would be greatly appreciated. Thanks in advance.

spasi

No, you cannot do that. You need to clear the context on the first thread, before making it current on the second thread. Doing this cleanly in Java should be easy, there are several options you can use (e.g. CountDownLatch).

Fataho

How to clear the context on the thread? What command should be used? I try to load info into VBO on separate thread and I am getting similar error. What I am doing wrong?

[LWJGL] GLFW_PLATFORM_ERROR error
	Description : WGL: Failed to make context current: The requested resource is in use. 
	Stacktrace  :
		org.lwjgl.glfw.GLFW.glfwMakeContextCurrent(GLFW.java:3397)
		fataho.engine.Loader.lambda$loadTexturedModelWithSeparateThread$0(Loader.java:68)
		java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-0" java.lang.IllegalStateException: There is no OpenGL context current in the current thread.
	at org.lwjgl.opengl.GL.createCapabilities(GL.java:351)
	at org.lwjgl.opengl.GL.createCapabilities(GL.java:302)
	at fataho.engine.Loader.lambda$loadTexturedModelWithSeparateThread$0(Loader.java:69)
	at java.lang.Thread.run(Thread.java:745)

My code:

        private int verticesVBOId;
	private int texturesVBOId;
	private int normalsVBOId;
	private int textureId;
	public TexturedModel loadTexturedModelWithSeparateThread(float[] verticesArray
		, float[] textureArray
		, float[] normalsArray
		, int[] indicesArray
		, String textureFilePathAndName){

		int vaoId = createVAO();
		bindVAO(vaoId);
		new Thread(() -> {
			glfwMakeContextCurrent(Window.windowID);
			GL.createCapabilities();
			verticesVBOId = createVBO(verticesArray);
			texturesVBOId = createVBO(textureArray);
			normalsVBOId = createVBO(normalsArray);
			bindIndices(indicesArray);
			textureId = loadTexture(textureFilePathAndName);
		}).start();
		storeDataInAttributeList(verticesVBOId, 0, 3);
		storeDataInAttributeList(texturesVBOId, 1, 2);
		storeDataInAttributeList(normalsVBOId, 2, 3);
		unBindVAO();

		return new TexturedModel(vaoId, indicesArray.length, textureId);
	}

        private int createVAO(){
		int vaoId = GL30.glGenVertexArrays();
		vaoIds.add(vaoId);
		return vaoId;
	}

	private void bindVAO(int vaoId){
		GL30.glBindVertexArray(vaoId);
	}

	private void unBindVAO(){
		GL30.glBindVertexArray(0);
	}

        private synchronized int createVBO(float[] data){
		int vboId = GL15.glGenBuffers();
		vaoIds.add(vboId);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
		FloatBuffer verticesBuffer = storeDataInFloatBuffer(data);
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		return vboId;
	}
	private synchronized void storeDataInAttributeList(int vboId, int attributeNumber, int coordinateSize){
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
		GL20.glVertexAttribPointer(attributeNumber, coordinateSize, GL11.GL_FLOAT, false, 0, 0);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
	}

	private int loadTexture(String modelFileLocationAndName){
		int bytesPerPixel = 4; // 3 for RGB, 4 for RGBA
		BufferedImage image = null;
		try {
			image = ImageIO.read(new File(modelFileLocationAndName));
		} catch (IOException e) {
			//Error Handling Here
			e.printStackTrace();
		}int[] pixels = new int[image.getWidth() * image.getHeight()];
		image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
		ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * bytesPerPixel); // 4 for RGBA, 3 for RGB
		for(int y = 0; y < image.getHeight(); y++){
			for(int x = 0; x < image.getWidth(); x++){
				int pixel = pixels[y * image.getWidth() + x];
				buffer.put((byte) ((pixel >> 16) & 0xFF));     // Red component
				buffer.put((byte) ((pixel >> 8) & 0xFF));      // Green component
				buffer.put((byte) (pixel & 0xFF));               // Blue component
				buffer.put((byte) ((pixel >> 24) & 0xFF));    // Alpha component. Only for RGBA
			}
		}
		buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS
		// You now have a ByteBuffer filled with the color data of each pixel.
		// Now just create a texture ID and bind it. Then you can load it using
		// whatever OpenGL method you want, for example:
		int textureID = glGenTextures(); //Generate texture ID
		glBindTexture(GL_TEXTURE_2D, textureID); //Bind texture ID
		//Setup wrap mode
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
		//Setup textureBuffer scaling filtering
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		//Send texel data to OpenGL
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

		// Mip mapping
//		GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
//		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
//		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, -1); // enter -1 if using Anisotropic filtering, ese -0,4f

		// Anisotropic filtering, not sure for 100 %, I can't check compatibility context

		float amount = Math.min(4f, GL11.glGetFloat(EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT));
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, amount);
		return textureID;
	}



Kai

See GLFW's glfwMakeContextCurrent() documentation. Especially the "Parameters" section.

Fataho

Quote from: Kai on February 10, 2017, 11:07:56
See GLFW's glfwMakeContextCurrent() documentation. Especially the "chronized blogParameters" section.

Thank you for the hint. It works now. I also had to put everything into synchronized block and use wait and notify. Here is my code:

public TexturedModel loadTexturedModelWithSeparateThread(float[] verticesArray
		, float[] textureArray
		, float[] normalsArray
		, int[] indicesArray
		, String textureFilePathAndName){

		int vaoId = createVAO();
		bindVAO(vaoId);
		glfwMakeContextCurrent(MemoryUtil.NULL);
		new Thread(() -> {
			synchronized (this){
				glfwMakeContextCurrent(Window.windowID);
				GL.createCapabilities();
				verticesVBOId = createVBO(verticesArray);
				texturesVBOId = createVBO(textureArray);
				normalsVBOId = createVBO(normalsArray);
				bindIndices(indicesArray);
				textureId = loadTexture(textureFilePathAndName);
				glfwMakeContextCurrent(MemoryUtil.NULL);
				System.out.println("TEST T2");
				notify();
			}

		}).start();
		synchronized(this){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			glfwMakeContextCurrent(Window.windowID);
			GL.createCapabilities();
			storeDataInAttributeList(verticesVBOId, 0, 3);
			storeDataInAttributeList(texturesVBOId, 1, 2);
			storeDataInAttributeList(normalsVBOId, 2, 3);
			unBindVAO();
			System.out.println("TEST T1");
		}
		return new TexturedModel(vaoId, indicesArray.length, textureId);
	}


Output:
TEST THREAD 2
TEST THREAD 1

Gojira

I was thinking about doing something similar, but Mac OS has a restriction that the original main thread must be used when calling OpenGL, is that correct?

So to be portable one shouldn't use multiple threads inside OpenGL like this.  Or that's what I assumed.

Kai

Quote from: Gojira on March 06, 2017, 17:42:40
...but Mac OS has a restriction that the original main thread must be used when calling OpenGL, is that correct?
No, this is not correct. What you mean are window manager API calls which are done inside glfwPollEvents() and glfwCreateWindow(), for example. Consult the GLFW api documentation to see which functions are safe to call in which threads.

With any OS you can use any threads to issue OpenGL commands in, including GLFW's glfwSwapBuffers() and glfwWindowShouldClose(). The only restriction regarding OpenGL calls is that any given OpenGL context must only be current in one and only one thread at any given time. You can change the current thread, though.

Gojira

Quote from: Kai on March 06, 2017, 17:48:56
Quote from: Gojira on March 06, 2017, 17:42:40
...but Mac OS has a restriction that the original main thread must be used when calling OpenGL, is that correct?
No, this is not correct. What you mean are window manager API calls which are done inside glfwPollEvents() and glfwCreateWindow(), for example. Consult the GLFW api documentation to see which functions are safe to call in which threads.


Here's what the GLFW website says about threading.

QuoteThread safety

Most GLFW functions must only be called from the main thread, but some may be called from any thread. However, no GLFW function may be called from any thread but the main thread until GLFW has been successfully initialized, including functions that may called before initialization.

The reference documentation for every GLFW function states whether it is limited to the main thread.

Initialization and termination, event processing and the creation and destruction of windows, contexts and cursors are all limited to the main thread due to limitations of one or several platforms.

Because event processing must be performed on the main thread, all callbacks except for the error callback will only be called on that thread. The error callback may be called on any thread, as any GLFW function may generate errors.

Source: http://www.glfw.org/docs/latest/intro_guide.html#thread_safety

Maybe I've got the wrong page but "most GLFW functions" sounds like a blanket statement to me.  I'm not trying to play games either I'm just learning and I want to make sure I am looking at the correct documentation.

Kai

You are again mistaking GL functions for GLFW functions. You must separate between them. It is _always_ okay to call OpenGL/GL functions in any thread you like (given that that context is only current in one thread at a time).
The GLFW documentation is of course correct, but what is said there applies to _GLFW_ functions. NOT OpenGL/GL functions.
You find GLFW functions generally in the org.lwjgl.glfw.GLFW class and OpenGL functions in the org.lwjgl.opengl.* classes.

Gojira

Ah, OK I think I get it.

So most GLFW functions need to be called on the main thread.

But the rendering context I get from `glfwCreateWindow` and `glfwMakeContextCurrent` can be used with OpenGL calls on any thread (assuming proper synchronization and mutex).