This isn't a pure CG question but it's more of a programming one related to CG.
The main problem that occurs is both the GUI and the rendered scene need to be drawn regularly by swapping buffers. If the rendering of the scene takes much longer say 5 or 6 FPS, then if the GUI is drawn together with this scene it wouldn't be responsive and lag a lot.
In order to overcome this problem we could make the path tracer render an image in a separate thread and the GUI in the main one. However this still doesn't solve the problem since we only have 1 window, we need to swapbuffers
in order to display both the GUI as well as the rendered scene. Let's say we swapbuffers in the main thread using this logic...
render_thread.start(); // render thread sets a flag when it finishes generating an image. This flag is accessible to the main gui thread.
while(window_open)
{
glClear(GL_COLOR_BUFFER_BIT);
if(image_generated) // flag set by the render thread.
draw_image();
draw_GUI();
swapbuffers();
}
However here the glClear()
call will clear our rendered scene if the render_thread doesn't finish in time. So basically we need a form of a layered framebuffer where we can clear and draw the top layer separately. Afaik this maybe done through overlay planes but i think those are platform specific?
So I have 2 or 3 solutions in mind but I don't know what's the standard people or most softwares follow. If anybody can guide me I'd appreciate it.
You use multiple windows, possibly the render window being embedded into the GUI window as a subset. The GUI window would be transparent everywhere except the GUI widgets (menu bars etc) leaving the space for the render window to display whatever.
You use the same window but use Scissor tests to clear only the part of the window covered by the menu and redraw the menu again. This sounds nice but provides problem when for example a dropbox covers the rendered scene or when using transparent Widgets.
You save your rendered scene in a texture. Re-draw this texture and then the GUI over the top until the render thread gives us a new image. The snipped above then would look like,
render_thread.start(); // render thread sets a flag when it finishes generating an image. This flag is accessible to the main gui thread. while(window_open) { glClear(GL_COLOR_BUFFER_BIT); if(image_generated) // flag set by the render thread. { draw_image(); save image in a texture; } else Re-draw the saved texture; draw_GUI(); swapbuffers(); sleep(33.33ms) or until render_thread finishes // sleep either for 33ms which equals 30FPS or till render thread finishes. If the render thread finishes in less than 33ms (> 30FPS) than we don't need to re-draw the saved texture. }
Option 3 could be done through blitting as well. Since I'm using an FBO and two RBOs (will explain later why). The snippet would look like this.
render_thread.start(); // render thread sets a flag when it finishes generating an image. This flag is accessible to the main gui thread. while(window_open) { glClear(GL_COLOR_BUFFER_BIT); if(image_generated) // flag set by the render thread. { draw_image(); save handle to RBO that has the image. } else Blit from the saved RBO to default framebuffer. draw_GUI(); swapbuffers(); sleep(33.33ms) or until render_thread finishes // sleep either for 33ms which equals 30FPS or till render thread finishes. If the render thread finishes in less than 33ms (> 30FPS) than we don't need to blit again. }
Options 3 and 4 seem to do the job. Is there any other method it's done?
As for why am I using 2 RBOs, I'm actually using OpenCL-OpenGL interop. So in OpenCL 1.1, need two Image objects (since we can't read/write to 1) for taking average with the previous frame (that's how it will converge). These two Image objects are wrapped around OpenGL's RBOs. Hence in option 4. when the render thread has finished I will save the handle to the RBO which currently has the image. For details on what libraries I'm using, (GLFW for windowing and nanogui for widgets).
For more details on how the path tracer is setup you can read this problem which I was having earlier.