Drawing Graphics Using Background Threads


#1

Hi, I apologise if I am missing something simple here, please feel free to point it out and make me feel stupid.

I am making some scrolling waveforms using a variation of the AudioThumbnail class. To avoid drawing thousands of lines every second I am caching the waveform in an image and repositioning that (actually I am caching 3 waveforms and switching between them when necessary).

Now all this is quite CPU intensive and as there could be any number fo these waveforms on screen at any one time I thought it best to render the waveforms on a background thread in a temporary image then copy them to the waveforms to be drawn. I know the message thread could be using the images whilst I am copying them so have locked the Message thread during the copy portion of the code but I still get startup “EXC_BAD_ACCESS” crashes from the TypefaceCache class. The line the debugger stops on is 83: if (face.flags == flags presumably because the faces object is 0x0.

My code looks like this (called from my own thread):

[code]void ColouredDraggableWaveDisplay::refreshWaveform(int waveNum)
{
if(waveImgs[waveNum]->img->isValid() && waveImgs[waveNum]->needToRepaint)
{
Graphics g(*tempImage);

	const double startTime = waveImgs[waveNum]->startTime;
	const double endTime = startTime + pixelsToTime(waveImgs[waveNum]->img->getWidth());
	
	if (waveformIsFullyLoaded || (numSamplesFinished > (startTime * currentSampleRate)))
	{
		g.fillAll(Colours::black);
		
		thumbnailView->drawColouredChannel(g, Rectangle<int> (0, 0, tempImage->getWidth(), tempImage->getHeight()),
										   startTime, endTime,
										   0, 1.0f);
	}
		
	MessageManagerLock mml (Thread::getCurrentThread());
	
	if (! mml.lockWasGained())
		return; // another thread is trying to kill us!
	
	Graphics g2(*waveImgs[waveNum]->img);

	g2.drawImage(*tempImage,
				 0, 0, waveImgs[waveNum]->img->getWidth(), waveImgs[waveNum]->img->getHeight(),
				 0, 0, tempImage->getWidth(), tempImage->getHeight(),
				 false); 

	waveImgs[waveNum]->needToRepaint = false;
	
	repaint();
}	

}
[/code]

If I put the lock right at the top it all works fine but defeats the point of using a separate thread.

Could this be because my thread is starting before the message manager has started and so some things have not been initialised yet? Any pointers would be much appreciated.


#2

Just my opinion but if you want to render an Image in another thread, when it is done rendering you should atomically pass it to the message thread using an AsyncUpdater and a ReferenceCountedObjectPtr, and do the repaint from the message thread in handleAsyncUpdate().


#3

Or depending on whether the implementation is thread-safe you could use Image::duplicateIfShared() from the message thread before you start drawing the new Image to the screen in your paint() method.


#4

To avoid having to copy altogether, you could just have two images for each thing you’re going to show, and just swap between them; render one, draw into the other, when drawn, swap them over.


#5

Thanks for the tips guys, my crash was stemming from starting the thread too early, doing it on a timer callback (which is called from the message thread) stops the crash.

@TheVinn: I might look into using an AsyncUpdater to trigger my repaints, would stop the need to lock the MM at all.

@Haydxn: I do something similar using past, current and next waveform images but in this case I actually rescale the temporary image onto the one I am going to display to get a higher resolution. This saves me from hacking the AudioThumbnail further to generate a larger cached window then drawing sub-pixel positioned lines. I know I loose clarity if I squash it too much but it seems to work well for a 2:1 scale.

Thanks guys, I will keep on improving it.