2
$\begingroup$

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.

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

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

  3. 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.
    }
    
  4. 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.

Path tracer isn't converging

$\endgroup$

1 Answer 1

3
$\begingroup$

Turns out I was an effin idiot. I didn't need to use multithreading or whatsoever. Since the main path tracer work is executing in the kernel, I don't need to wait for the kernel to finish right after call to EnqueueKernel by using clFinish() ,instead I can tell opencl to set the flag when kernel finishes.

In other words, the main time extensive work is done on the GPU while on the CPU side we draw the GUI regularly. Once the kernel finishes we can check through clGetEventInfo() and draw the rendered scene then. No need to wait for the kernel to finish.

EDIT:- Although I don't need to use multi threading, I still need to use 1 of the above options, that is if the kernel takes much longer to finish I will have to redraw the previous scene, hence I'm doing this through option 4 i.e. by blitting the previously drawn image.

If anybody thinks this can be done more efficiently I'm all ears. The only problem with option 3/4 is that, since both my RBO's are being used by OpenCL, so if OpenCL takes much longer to render a frame, in order to draw the previously drawn image I need to blit from the previously written RBO to the default framebuffer. However this is being used by the OpenCL currently for reading purposes. Hence I need to keep a copy of this costing an additional blit each time the kernel completes.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.