LWJGL 3 - Thread for OpenGL does not close after window is closed

Started by ShadowDragon, October 19, 2016, 00:17:56

Previous topic - Next topic

ShadowDragon

Hi,

I'm quite new to LWJGL 3 and also still learning OpenGL (v4.5).

Now my problem is, when I start my Game and close it then again, the thread the Game was running in does not close and still runs.
I've tested it also without OpenGL (so with JFrame) and there it direclty closes, but when using it with OpenGL it doesn't close. (or maybe takes very long?)

This is my code:
package shadowdragon.test.test;

import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryUtil.*;

public abstract class Game implements Runnable {

	private long window;
	
	private int width, height;
	private String title;
	private boolean vsync;
	
	private Thread thread;
	private boolean running = false;
	
	private final double ups;
	
	public Game(int width, int height, String title, boolean vsync, double ups) {
		this.width = width;
		this.height = height;
		this.title = title;
		this.vsync = vsync;
		this.ups = ups;
		this.start();
	}
	
	private synchronized void start() {
		this.running = true;
		this.thread = new Thread(this, "Test");
		this.thread.start();
		
		System.out.println("Initializing Test...");
		System.out.println("LWJGL " + Version.getVersion());
	}
	
	public synchronized void stop() {
		if(this.running) this.running = false;
		
		this.dispose();
		
		glfwFreeCallbacks(this.window);
		glfwDestroyWindow(this.window);
		
		glfwTerminate();
		glfwSetErrorCallback(null).free();
		
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	private boolean init() {
		GLFWErrorCallback.createPrint(System.err).set();
		
		if(!glfwInit())
			throw new IllegalStateException("Unable to initialize GLFW");
		
		glfwDefaultWindowHints();
		glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
		glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
		glfwWindowHint(GLFW_FOCUSED, GL_TRUE);
		glfwWindowHint(GLFW_SAMPLES , 4);
		
		this.window = glfwCreateWindow(this.width, this.height, this.title, NULL, NULL);
		if(window == NULL)
			throw new RuntimeException("Failed to create the GLFW window");
		
		GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
		glfwSetWindowPos(this.window, 
				(vidmode.width() - this.width) / 2, 
				(vidmode.height() - this.height) / 2);
		
		glfwMakeContextCurrent(this.window);
		if(vsync) enableVSync();
		else disableVSync();
		
		glfwShowWindow(this.window);
		
		GL.createCapabilities();
		
		glClearColor(0.f, 0.f, 0.f, 0.f);
		
		return true;
	}
	
	public void run() {
		this.init();
		
		long lastTime = System.nanoTime();
		long timer = System.currentTimeMillis();
		final double ns = 1000000000.0 / this.ups;
		double delta = 0.d;
		
		int updates = 0;
		int frames = 0;
		
		while(running) {
			long now = System.nanoTime();
			delta += (now - lastTime) / ns;
			lastTime = now;
			
			while(delta >= 1.d) {
				update();
				updates++;
				delta--;
			}
			this.render();
			frames++;
			
			if(System.currentTimeMillis() - timer > 1000) {
				timer += 1000;
				System.out.println(updates + " ups, " + frames + " fps");
				updates = 0;
				frames = 0;
			}
			
			if(glfwWindowShouldClose(this.window)) this.running = false;
		}
		
		this.stop();
	}
	
	private void update() {
		
	}

	private void render() {
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		this.render(2);
		glfwSwapBuffers(this.window);
		glfwPollEvents();
	}

	private void render(int i) {
		// TODO Auto-generated method stub
		
	}
}


Well, actually there is also a second class, but I don't post it here, because it does nothing yet, except for being there and having a main method and extending the Game class from above.

OS: Windows 10 64bit Build: 1607
LWJGL 3.0.1-SNAPSHOT (with gradle and it's wrapper version 3.1)
Java 8 (x64) Update 102
GTX 960 with driver version: 373.06

Also, the Game should automatically be closed and all the threads be killed/closed when the window is closed, so that I don't need to extra call the stop method somewhere in the actual Game.

I hope someone knows how to fix this. I'm also open for any improvements of my code or the performance. ;)

Thx in advance.
ShadowDragon

spasi

The reason the program does not end has nothing to do with LWJGL. The problem is the thread.join() call. You're asking the thread to block and wait for itself to stop, which never happens because the thread is blocked in that very join() call.

The design of your application is also against the guidelines set by GLFW. The event loop should be running on the main thread, not on a new thread spawned from main. In general, why do you even need a new thread here? You should avoid multithreading until you have a very good reason to incorporate it in your design (and when you do, make its impact the minimal possible).

Note also that LWJGL never creates threads behind your back. You have full control of what happens and in which thread.

ShadowDragon

Quote from: spasi on October 19, 2016, 08:15:18
The reason the program does not end has nothing to do with LWJGL. The problem is the thread.join() call. You're asking the thread to block and wait for itself to stop, which never happens because the thread is blocked in that very join() call.

The design of your application is also against the guidelines set by GLFW. The event loop should be running on the main thread, not on a new thread spawned from main. In general, why do you even need a new thread here? You should avoid multithreading until you have a very good reason to incorporate it in your design (and when you do, make its impact the minimal possible).

Note also that LWJGL never creates threads behind your back. You have full control of what happens and in which thread.
Well, the main reason why I created a new thread lays a few years back. xD Back then I watched a tutorial series on how to create a Java Game with Java2D and he used the thread system there and later in his LWJGL 3 Game tutorial he also used it to control the number of updates the Game gets, so that on all PCs the Game runs with the same speed and to have stuff like FPS and UPS/tps counters.

The basic idea with having the stop call on that position was actually to close the thread when the running boolean is false. And yeah, I've got no idea of the GLFW guidelines, didn't even know such a thing exists.

What I am now just wondering is, why is then this working:
public class Game extends Canvas implements Runnable {
	
	private static final long serialVersionUID = 8033888269093687890L;
	
	public static int width = 300;
	public static int height = width / 16 * 9;
	public static int scale = 3;
	
	private Thread thread;
	private JFrame frame;
	private boolean running = false;
	
	private Screen screen;
	
	private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
	private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
	
	public Game() {
		Dimension size = new Dimension(width * scale, height * scale);
		setPreferredSize(size);
		
		this.screen = new Screen(width, height);
		this.frame = new JFrame();
	}
	
	public synchronized void start() {
		this.running = true;
		this.thread = new Thread(this, "Test Game");
		this.thread.start();
	}
	
	public synchronized void stop() {
		this.running = false;
		try {
			this.thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void run() {
		long lastTime = System.nanoTime();
		long timer = System.currentTimeMillis();
		final double ns = 1000000000 / 60;
		double delta = 0;
		
		int frames = 0;
		int updates = 0;
		
		while(this.running) {
			long now = System.nanoTime();
			delta += (now - lastTime) / ns;
			lastTime = now;
			
			while(delta >= 1) {
				update();
				updates++;
				delta--;
			}
			render();
			frames++;
			
			if(System.currentTimeMillis() - timer > 1000) {
				timer += 1000;
				System.out.println(updates + " ups | " + frames + " fps");
				updates = 0;
				frames = 0;
			}
		}
		stop();
	}
	
	private void update() {
		
	}
	
	private void render() {
		BufferStrategy bs = getBufferStrategy();
		if(bs == null) {
			createBufferStrategy(3);
			return;
		}
		
		for(int i = 0; i < pixels.length; i++) {
			this.pixels[i] = this.screen.pixels[i];
		}
		
		this.screen.clear();
		this.screen.render();
		
		Graphics g = bs.getDrawGraphics();
		g.setColor(Color.BLACK);
		g.fillRect(0, 0, getWidth(), getHeight());
		
		g.drawImage(this.image, 0, 0, getWidth(), getHeight(), null);
		
		g.dispose();
		bs.show();
	}
	
	public static void main(String[] args) {
		Game game = new Game();
		game.frame.setResizable(false);
		game.frame.setTitle("Test Game");
		game.frame.add(game);
		game.frame.pack();
		game.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		game.frame.setLocationRelativeTo(null);
		game.frame.setVisible(true);
		game.start();
	}
}


Here the thread will be closed.

spasi

Because you use JFrame.EXIT_ON_CLOSE and stop() is never called.

ShadowDragon

Quote from: spasi on October 19, 2016, 11:17:00
Because you use JFrame.EXIT_ON_CLOSE and stop() is never called.
Ah, ok. So, how can I get the same control I have with the thread stuff when using it the way GLFW wants it to be used?

Cornix

The EXIT_ON_CLOSE flag in Swing will call System.exit(0) which will force the JVM to exit once the EventDispatchThread has no more Timers and Windows to update. You could do the same thing and call System.exit(0) which will kill all threads and release all system resources, but its generally a bad habit and indicates poor code quality.

If you seriously want to use multithreading (which I would not recommend unless your performance tests show you that you are CPU locked) then I suggest you first read up on how to use threads in java and multithreading in general. In some areas you can learn-by-doing and just experiment, but multithreading is different. Its too easy to make bad mistakes which go unnoticed because of the nondeterministic nature of threads.