Two windows with separate framerates

hi!

is it possible to run two windows at separate framerates?
i don’t need to share the gl context or any other resources.

having two applications in the same run loop locks them to the same framerate, and i had no luck running even a single application on a separate thread.

the simplest example i can come up with is this:

#include "ofMain.h"
#include "ofApp.h"
#include "ofAppGLFWWindow.h"
//========================================================================


shared_ptr<ofAppBaseWindow> mainWindow;

class MyThread : public ofThread{
	void threadedFunction(){
		shared_ptr<ofApp> mainApp = make_shared<ofApp>();
		ofRunApp(mainWindow, mainApp);
		ofRunMainLoop(); // crashes when not run on main loop :( 
	}
};
int main( ){
	ofInit(); // seems to be needed on osx to set up the "cs" struct correctly for the ofGetMonotonicTime function. not really a problem here, because it crashes before i can display anything anyways. 

	MyThread thread;
	ofWindowSettings mainSettings;
	mainSettings.width = 500;
	mainSettings.height = 100;
	mainWindow = ofGetMainLoop()->createWindow(mainSettings); // needs to happen on the main thread
	thread.startThread();
	thread.waitForThread();
}

here i’m testing to start the primary run loop on a separate thread.

it always crashes in ofMainLoop::pollEvents() when forwarding to windowPollEvents (that’s the glfw poll function being called) which sort of makes sense to me.

i’m not dead set on the OF0.9.x approach to multi window, my goal is to get a second gl context in a second window with a separate framerate. (i’ll just have one mesh and one shader there).

any ideas?

This is not possible yet. There’s already some work done to make this possible by having most of the code in OF not depend on any global variable except for all the global calls (things like ofGetWidth, ofGetHeight, ofDrawCircle) which you won’t be able to use if working with opengl contexts in multiple threads and every bind/draw call which will instead be done as shown in this example:

The only missing part is the initialization of the window. although there’s also some work done by moving the loop logic to it’s own class, ofMainLoop, there’s still a couple of things inside ofGLFWAppWindow that won’t work in multithreaded environments yet since some glfw calls need to be done always in the main thread.

Once this is ready your ofApp will look something like the example above and main something similar to:

	ofInit();

	ofGLFWWindowSettings settings;
	settings.width = 600;
	settings.height = 600;
	settings.setPosition(ofVec2f(300,0));
	settings.resizable = true;
	settings.numSamples = 8;
	ofMainLoop mainLoop;
	auto mainWindow = mainLoop.createWindow(settings);


	settings.width = 300;
	settings.height = 300;
	settings.setPosition(ofVec2f(0,0));
	settings.resizable = false;
	settings.numSamples = 4;
	ofMainLoop guiLoop;
	auto guiWindow = guiLoop.createWindow(settings);

	bool t1Running = true;
	auto t1 = std::thread([&]{
		shared_ptr<ofApp> mainApp(new ofApp);
		mainApp->window = mainWindow;
		mainApp->gl = static_pointer_cast<ofGLRenderer>(mainWindow->renderer());
		mainLoop.run(mainWindow, mainApp);
		while(!mainWindow->getWindowShouldClose()){
			mainLoop.loopOnce();
		}
		t1Running = false;
	});

	bool t2Running = true;
	auto t2 = std::thread([&]{
		shared_ptr<GuiApp> guiApp(new GuiApp);
		guiApp->window = guiWindow;
		guiApp->gl = static_pointer_cast<ofGLRenderer>(guiWindow->renderer());
		guiLoop.run(guiWindow, guiApp);
		while(!guiWindow->getWindowShouldClose()){
			guiLoop.loopOnce();
		}
		t2Running = false;
	});

	while(t1Running || t2Running){
		ofAppGLFWWindow::pollEvents();
		ofSleepMillis(1000/60);
	}

	t1.join();
	t2.join();

which kind of works already but has still some problems when exiting the application and it’s really not well tested so some things might fail.

3 Likes

thanks for your hints, this is very helpful.

with this i was able to achieve what i want:
a secondary gl context in a separate window at a separate framerate with a variable to show/hide it, and an unmodified main window that uses the standard ofDrawXXX() calls.

here’s my main.cpp based on your code:
(only tested on osx)

#include "ofMain.h"
#include "ofAppGLFWWindow.h"
//========================================================================


static bool secondaryWindowVisible = false;

// main app runs on the main thread
// and uses the main run loop (ie. can use ofBackground() instead of gl->background())
class MainApp : public ofBaseApp{
public:
	void draw(){
		ofBackground(0);
		ofDrawBitmapString("Main @ " + ofToString(ofGetFrameRate()) + " fps", 10, 10);
	}
	
	//--------------------------------------------------------------
	void mousePressed(int x, int y, int button){
		secondaryWindowVisible ^= true;
	}

};

// secondary output window. this runs at high fps and does very little
class SecondApp :  public ofBaseApp{
public:
	shared_ptr<ofAppBaseWindow> window;
	shared_ptr<ofGLRenderer> gl;
	void draw(){
		gl->background({0});
		gl->setColor({255});
		gl->drawString("Seconary @ " + ofToString(window->events().getFrameRate()) + " fps", 10, 10, 0);
	}
};

// user presses "close" on secondary window
void hideSecondaryWindow(GLFWwindow* windowP_){
	secondaryWindowVisible = false;
}


int main( ){
	ofInit();
	
	// CREATE MAIN WINDOW + ADD TO MAIN RUN LOOP
	
	ofGLFWWindowSettings settings;
	settings.width = 600;
	settings.height = 600;
	settings.setPosition(ofVec2f(300,0));
	settings.resizable = true;
	settings.numSamples = 8;
	auto mainWindow = ofGetMainLoop()->createWindow(settings);
	ofGetMainLoop()->events().setFrameRate(25);

	// CREATE SECONDARY WINDOW + ADD TO CUSTOM RUN LOOP
	settings.width = 300;
	settings.height = 300;
	settings.setPosition(ofVec2f(0,0));
	settings.resizable = false;
	settings.numSamples = 4;
	ofMainLoop guiLoop;
	auto guiWindow = guiLoop.createWindow(settings);
	guiLoop.events().setFrameRate(60);
	
	
	bool t1Running = true;
	auto t1 = std::thread([&]{
		shared_ptr<ofBaseApp> mainApp(new MainApp);
		ofGetMainLoop()->run(mainWindow, mainApp);
		while(!mainWindow->getWindowShouldClose()){
			ofGetMainLoop()->loopOnce();
		}
		t1Running = false;
	});
	
	bool t2Running = true;
	bool t2MustDie = false;
	
	auto t2 = std::thread([&]{
		shared_ptr<SecondApp> guiApp(new SecondApp);
		guiApp->window = guiWindow;
		guiApp->gl = static_pointer_cast<ofGLRenderer>(guiWindow->renderer());
		guiLoop.run(guiWindow, guiApp);
		
		// first hide the window. all the glfw* calls should (?) be on main, but that caused crashes. 
		ofAppGLFWWindow * ofWindow = (ofAppGLFWWindow*)guiWindow.get();
		GLFWwindow * glfwWindow = ofWindow->getGLFWWindow();
		glfwSetWindowCloseCallback(glfwWindow, hideSecondaryWindow);
		
		while(!t2MustDie){
			if(secondaryWindowVisible){
				// we want to be visible. but... are we?
				if(ofWindow->getWindowShouldClose()){
					glfwSetWindowShouldClose(glfwWindow, false);
					glfwShowWindow(glfwWindow);
				}
				guiLoop.loopOnce();
			}
			else{
				// for some reason the window sometimes comes back to life after cmd+tabbing
				// if i don't do these two lines regularly:
				glfwSetWindowShouldClose(glfwWindow, true);
				glfwHideWindow(glfwWindow);
				ofSleepMillis(10); // no work, let's sleep a long time
			}
		}
		
		guiLoop.events().notifyExit();
		t2Running = false;
	});
	
	// wait until the main window closes (I don't care if the secondary window is still going)
	while(t1Running){
		// poll events for all apps. this is done on the main thread.
		ofAppGLFWWindow::pollEvents();
		ofSleepMillis(1000/60);
	}

	t2MustDie = true;
	while(t2Running){
		ofSleepMillis(1);
	}

	t1.join();
	t2.join();
}
1 Like