My app uses a subclass of NSOpenGLView to render sometimes very large polygon meshes (3D plots of fractals.) Realtime drawing is not a priority, although having a responsive system so the user can manipulate the image and see the results IS a priority.
When the meshes get really large, (4000x4000 vertexes or larger, rendered into a mesh of triangle strips) rendering gets quite slow. It gets REALLY slow on older G4 machines. It works, but it can take a LONNNNNGGGGG time to render my image (no doubt because it’s thrashing between VRAM, main memory, and disk.)
I spent time re-factoring my code to use VBOs, but they were actually slower than the display lists I was using before. I now use vertex arrays to create a series of display lists, and once the display lists are built, drawing the mesh is reasonably responsive.
At the limits of machines with lower resources, it can take 5 minutes to build one static image into a group of display lists. Once I’ve built my display lists, it’s usable again.
The user might be willing to wait that long for a final render, so I want to support it as an option.
Locking up the app with a beach-ball cursor for 5 minutes is clearly not acceptable however.
I’m thinking of moving my rendering code into a separate thread. Here’s what I’m thinking of doing:
in my drawrect, if my display lists need to be built/rebuilt, and they are over a “too slow” threshold, I will:
-display a “rendering, please wait” sheet over the window, with a progress bar and a cancel button on it.
-spawn a thread for rendering.
-Inside the thread, set the context to the CGLContextObj for my NSOpenGLView.
-Lock the context with a CGLockContext call.
-Begin building my data structures and display list, periodically checking for a “terminate” boolean to change to true.
-Pass messages to the main thread to update the progress indicator as I make progress on the rendering.
-Once rendering is complete, unlock the OpenGL context, call a “rendering complete” function in the main thread, and terminate my rendering thread (or put it to sleep if I think I’ll need it again.)
-If the rendering thread finds the terminate flag set to true, it would clean up the data structures it created, call a “rendering terminated” function in the main thread, and terminate/go to sleep.
In the main thread, the sheet will keep the user from doing anything with the window that contains the NSOpenGLView that would cause a lockup because of the CGLockContext from the rendering thread.
If the user clicks the cancel button on the sheet, the main thread would set the “terminate thread” boolean to true, and wait for a “rendering terminated” call from the rendering thread.
The downside of using a sheet is that it would cover part of my NSOpenGLView, and that would force a redraw of my view when I take it away. I’m wondering if I should request a backing store when I set up my pixel format so the system can update the screen from the back buffer rather than having to re-display my mesh.
Does my approach sound reasonable? My rendering code has NOT been written with multi-threading in mind. It uses lots of instance variables from my mesh object. I’m thinking, though, that by putting up a sheet to keep the user locked out, and by locking the context with CGLockContext, I can safely operate in my rendering thread almost as if I was single threaded. My mesh object should be able to operate in isolation, without tripping over other parts of my app.
My app already is multi-threaded for the fractal rendering part. That part WAS designed with multi-threading in mind, and builds a job queue that it uses to keep the work threads busy. I’m not an expert on multi-threading, but I have successfully written multi-threaded code before.