Paint() called from OpenGL thread

I’m having this strange behaviour, with asserts from JUCE being fired, because my paint() method is being called from OpenGL thread.

The assert is being called in juce::Font::getStringWidthFloat(), which is called indirectly from a Component::paint() call, all on the OpenGL thread.

What am I doing wrong?
Any ideas?

|#0| juce::Font::getStringWidthFloat(juce::String const&)
|#1| juce::Font::getStringWidth(juce::String const&)
|#2| SpDrawText::neededRect(juce::String, bool, juce::String, float)
|#3| SpDrawText::neededRect(juce::String)
|#4| SpDrawText::neededRect()
|#5| SpDrawText::draw()
|#6| InspectorPanel::draw()
|#7| ParticleGroupAudioFilesView::draw()
|#8| BaseViewExt::paint(juce::Graphics&)
|#9| juce::Component::paintComponentAndChildren(juce::Graphics&)
|#10| juce::Component::paintEntireComponent(juce::Graphics&, bool)
|#11| juce::Component::paintWithinParentContext(juce::Graphics&)
|#12| juce::Component::paintComponentAndChildren(juce::Graphics&)
|#13| juce::Component::paintEntireComponent(juce::Graphics&, bool)
|#14| juce::Component::paintWithinParentContext(juce::Graphics&)
|#15| juce::Component::paintComponentAndChildren(juce::Graphics&)
|#16| juce::Component::paintEntireComponent(juce::Graphics&, bool)
|#17| juce::Component::paintWithinParentContext(juce::Graphics&)
|#18| juce::Component::paintComponentAndChildren(juce::Graphics&)
|#19| juce::Component::paintEntireComponent(juce::Graphics&, bool)
|#20| juce::Component::paintWithinParentContext(juce::Graphics&)
|#21| juce::Component::paintComponentAndChildren(juce::Graphics&)
|#22| juce::Component::paintEntireComponent(juce::Graphics&, bool)
|#23| juce::Component::paintWithinParentContext(juce::Graphics&)
|#24| juce::Component::paintComponentAndChildren(juce::Graphics&)
|#25| juce::Component::paintEntireComponent(juce::Graphics&, bool)
|#26| juce::Component::paintWithinParentContext(juce::Graphics&)
|#27| juce::Component::paintComponentAndChildren(juce::Graphics&)
|#28| juce::Component::paintEntireComponent(juce::Graphics&, bool)
|#29| juce::Component::paintWithinParentContext(juce::Graphics&)
|#30| juce::Component::paintComponentAndChildren(juce::Graphics&)
|#31| juce::Component::paintEntireComponent(juce::Graphics&, bool)
|#32| juce::ComponentPeer::handlePaint(juce::LowLevelGraphicsContext&)
|#33| juce::NSViewComponentPeer::invokePaint(juce::LowLevelGraphicsContext&)
|#34| juce::NSViewComponentPeer::drawRect(CGContext*, CGRect, float)
|#35| juce::NSViewComponentPeer::drawRect(CGRect)
|#36| juce::JuceNSViewClass::drawRect(objc_object*, objc_selector*, CGRect)
|#37| _NSViewDrawRect ()|
|#38| -[NSView(NSInternal) _recursive:displayRectIgnoringOpacity:inContext:shouldChangeFontReferenceColor:stopAtLayerBackedViews:] ()|
|#39| __46-[NSView(NSLayerKitGlue) drawLayer:inContext:]_block_invoke ()|
|#40| -[NSView(NSLayerKitGlue) drawViewBackingLayer:inContext:drawingHandler:] ()|
|#41| CABackingStoreUpdate
()|
|#42| ___ZN2CA5Layer8display_Ev_block_invoke ()|
|#43| -[CALayer _display] ()|
|#44| _NSBackingLayerDisplay ()|
|#45| -[_NSViewBackingLayer display] ()|
|#46| CA::Layer::display_if_needed(CA::Transaction*) ()|
|#47| CA::Context::commit_transaction(CA::Transaction*) ()|
|#48| CA::Transaction::commit() ()|
|#49| NSPerformVisuallyAtomicChange ()|
|#50| -[NSOpenGLContext setView:] ()|
|#51| juce::OpenGLContext::NativeContext::makeActive() |#52| juce::OpenGLContext::makeActive()
|#53| juce::OpenGLContext::CachedImage::initialiseOnThread()
|#54| juce::OpenGLContext::CachedImage::runJob()
|#55| juce::ThreadPool::runNextJob(juce::ThreadPool::ThreadPoolThread&)
|#56| juce::ThreadPool::ThreadPoolThread::run()
|#57| juce::thread::threadEntryPoint()
|#58| juce::juce_threadEntryPoint(void*)
|#59| juce::threadEntryProc(void*)
|#60| _pthread_body ()|
|#61| _pthread_start ()|
|#62| thread_start ()|

If you’re calling Component::paint() explicitly (rather than using OpenGLContext::setComopnentPaintingEnabled(true)) you’ll run into problems because there are quite a few things that should only be run on the main thread (in this case, Font related methods)

Note though that acquiring a message thread lock in renderOpenGL() might not be viable:

I’m not calling paint() explicitly - the entire call stack bellow paint() is either JUCE or system.

1 Like

Is the OpenGLContext just attached normally with attachTo() and component painting enabled? The context will handle locking the main thread when painting components via OpenGL, otherwise it just runs the OpenGLRenderer (if one is set)

I haven’t run into this issue on macOS 10.13.6

You’re supposed to use triggerRepaint, not repaint with the openGL context approach

1 Like

The behavior is somewhat similar since the OpenGLContext is implemented via a CachedComponentImage. Calling repaint on a component invalidates the cache (in this case just a saved rectangle) which then results in a call to OpenGLContext::triggerRepaint(). If you manually call triggerRepaint() it seems it will run the internal OpenGLContext::paintComponent() but may skip over updating the actual component texture if validArea hasn’t changed

1 Like