Root::renderOneFrame() on background thread

Discussion area about developing with Ogre-Next (2.1, 2.2 and beyond)


Post Reply
bayoubengal
Halfling
Posts: 48
Joined: Wed Sep 05, 2018 3:18 pm

Root::renderOneFrame() on background thread

Post by bayoubengal »

On OSX, one drives rendering from a displaylink thread which calls your code in rhythm with the refresh cycle of the display.

Can I invoke Root::renderOneFrame() on a background thread? How would that interplay with UI events on the main thread like resizing the window hosting the drawing?

-James
bayoubengal
Halfling
Posts: 48
Joined: Wed Sep 05, 2018 3:18 pm

Re: Root::renderOneFrame() on background thread

Post by bayoubengal »

I have the concept coded. I can say that the metal backend seems to run pretty well. the openglbackend crashes in

void GL3PlusRenderSystem::_setRenderTarget(RenderTarget *target, uint8 viewportRenderTargetFlags)

at

//Make sure colour writes are enabled for RenderWindows.
OCGE( glBindFramebuffer( GL_FRAMEBUFFER, 0 ) );


I immediately notice that there is no effort to manage the current opengl context (CGLContextRef) or lock it as would be needed for threaded use. Is the opengl rendersystem coded to render on a background thread?
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: Root::renderOneFrame() on background thread

Post by dark_sylinc »

renderOneFrame is meant to be called from the main thread.

However for some RenderSystems, the background thread can act as the "main thread" as far as Ogre is concern (i.e. all Ogre related activities happen in that background thread, or are carefully synchronized as shown in Tutorial05_MultithreadingBasics and Tutorial06_Multithreading samples) as long as all UI messages are forwarded correctly.

Such is the case of Metal which is probably safe to move to a background thread.
Note that renderOneFrame will call present (see MetalRenderWindow::swapBuffers) and also may call [mMetalLayer nextDrawable], I don't know if that counts as an UI operation, and if it does, then Apple strictly says it should be done in the main UI thread.
I can't find any info in that regard in Apple docs.
bayoubengal wrote: Wed Mar 06, 2019 11:28 pm I have the concept coded. I can say that the metal backend seems to run pretty well. the openglbackend crashes in (...)
I immediately notice that there is no effort to manage the current opengl context (CGLContextRef) or lock it as would be needed for threaded use. Is the opengl rendersystem coded to render on a background thread?
OpenGL and threading do not mix well. Each platform has its own quirks and each driver has too many bugs to do it reliably. It gets worse on macOS, where the OpenGL driver(s) haven't been particularly good, haven't been updated in years, and now Apple is deprecating it (while at the same time, breaking GL stuff that was actually working, like VSync and window presentation mechanisms).

Cheers
bayoubengal
Halfling
Posts: 48
Joined: Wed Sep 05, 2018 3:18 pm

Re: Root::renderOneFrame() on background thread

Post by bayoubengal »

We have production systems with legacy hardware that won't be updated anytime soon. Those systems are stuck on 10.11 with no metal support. one of the primary reasons I am looking at ogre for this chore is that I can dynamically chose metal on hardware that supports it or fall back to opengl on older hardware.

I have code that correctly renders opengl on a display link thread. its doable as long as you keep the work synchronous. I have a mechanism that switches the rendering from the background thread to the main thread for things like window resizing.

I suspect that the crash I am seeing is simply because the context has not been established on the background thread. I initialized the system on the main thread when the window is created. Do you have a recommendation on how that chore should be handled in GL3PlusRenderSystem::_setRenderTarget() when it gets called on a background thread the first time?
bayoubengal
Halfling
Posts: 48
Joined: Wed Sep 05, 2018 3:18 pm

Re: Root::renderOneFrame() on background thread

Post by bayoubengal »

backtrace to demonstrate the execution path in question...


* thread #16, name = 'CVDisplayLink', stop reason = EXC_BAD_ACCESS (code=1, address=0x1508)
frame #0: 0x00007fff56365f9a libGL.dylib`glBindFramebuffer + 18
* frame #1: 0x000000010735d4f8 Vx76-[Debug]`Ogre::GL3PlusRenderSystem::_setRenderTarget(this=0x00006230002e0d00, target=0x000061200064b1c0, viewportRenderTargetFlags='\x01') at OgreGL3PlusRenderSystem.cpp:3517
frame #2: 0x0000000105501e64 Vx76-[Debug]`Ogre::CompositorWorkspace::_validateFinalTarget(this=0x0000613000022cc0) at OgreCompositorWorkspace.cpp:790
frame #3: 0x00000001053ef05d Vx76-[Debug]`Ogre::CompositorManager2::_updateImplementation(this=0x0000611002da0180, sceneManagers=0x000060e0000a22a0, hlmsManager=0x0000624000b46110) at OgreCompositorManager2.cpp:656
frame #4: 0x00000001064ddb99 Vx76-[Debug]`Ogre::RenderSystem::updateCompositorManager(this=0x00006230002e0d00, compositorManager=0x0000611002da0180, sceneManagers=0x000060e0000a22a0, hlmsManager=0x0000624000b46110) at OgreRenderSystem.cpp:1049
frame #5: 0x00000001053ee406 Vx76-[Debug]`Ogre::CompositorManager2::_update(this=0x0000611002da0180, sceneManagers=0x000060e0000a22a0, hlmsManager=0x0000624000b46110) at OgreCompositorManager2.cpp:641
frame #6: 0x000000010672ccbc Vx76-[Debug]`Ogre::Root::_updateAllRenderTargets(this=0x0000618000015c80) at OgreRoot.cpp:1567
frame #7: 0x000000010672c8d2 Vx76-[Debug]`Ogre::Root::renderOneFrame(this=0x0000618000015c80) at OgreRoot.cpp:1091
frame #8: 0x00000001043dcc62 Vx76-[Debug]`::-[C3DWindowController getFrameForTime:](self=0x0000616000880580, _cmd="getFrameForTime:", outputTime=0x0000000000000000) at C3DWindowController.mm:1082
frame #9: 0x00000001043ee133 Vx76-[Debug]`-[C3DWindowController initSPtr]::$_3::operator(this=0x0000700001f4b980)(com::prg::system::cDisplayLink&) const::'lambda'()::operator()() const at C3DWindowController.mm:1007
frame #10: 0x00000001043ed0d1 Vx76-[Debug]`auto com::prg::system::catchEOBJC<C3DWindowController, 993ul, -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(), -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>(theFunction=0x0000700001f4b980, theExceptionHandler=0x0000700001f4b9a0)(com::prg::system::cDisplayLink&) const::'lambda'()&, -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)&) at exception_handling.h:181
frame #11: 0x00000001043ec5af Vx76-[Debug]`auto com::prg::system::catchE<C3DWindowController, 993ul, -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(), -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>(theFunction=0x0000700001f4b980, theExceptionHandler=0x0000700001f4b9a0)(com::prg::system::cDisplayLink&) const::'lambda'()&, -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)&) at exception_handling.h:263
frame #12: 0x00000001043ec40d Vx76-[Debug]`auto com::prg::system::catchE<C3DWindowController, 993ul, -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(), -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>(theFunction=0x0000700001f4b980, theExceptionHandler=0x0000700001f4b9a0)(com::prg::system::cDisplayLink&) const::'lambda'()&&, -[C3DWindowController initSPtr]::$_3::operator()(com::prg::system::cDisplayLink&) const::'lambda'(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)&&) at exception_handling.h:286
frame #13: 0x00000001043ec364 Vx76-[Debug]`-[C3DWindowController initSPtr]::$_3::operator(this=0x000060c0002be2b8, theLink=0x00006130000227a0)(com::prg::system::cDisplayLink&) const at C3DWindowController.mm:993
frame #14: 0x00000001043ebf59 Vx76-[Debug]`std::__1::__packaged_task_func<-[C3DWindowController initSPtr]::$_3, std::__1::allocator<-[C3DWindowController initSPtr]::$_3>, void (com::prg::system::cDisplayLink&)>::operator()(com::prg::system::cDisplayLink&) [inlined] decltype(__f=0x000060c0002be2b8, __args=0x00006130000227a0)(std::__1::forward<com::prg::system::cDisplayLink&>(fp0))) std::__1::__invoke<-[C3DWindowController initSPtr]::$_3&, com::prg::system::cDisplayLink&>(-[C3DWindowController initSPtr]::$_3&&&, com::prg::system::cDisplayLink&&&) at type_traits:4428
frame #15: 0x00000001043ebf3c Vx76-[Debug]`std::__1::__packaged_task_func<-[C3DWindowController initSPtr]::$_3, std::__1::allocator<-[C3DWindowController initSPtr]::$_3>, void (com::prg::system::cDisplayLink&)>::operator(this=0x000060c0002be2b0, __arg=0x00006130000227a0)(com::prg::system::cDisplayLink&) at future:1826
frame #16: 0x00000001074f33c4 Vx76-[Debug]`std::__1::packaged_task<void (com::prg::system::cDisplayLink&)>::operator()(com::prg::system::cDisplayLink&) [inlined] std::__1::__packaged_task_function<void (com::prg::system::cDisplayLink&)>::operator(this=0x000060c0002be2b0, __arg=0x00006130000227a0)(com::prg::system::cDisplayLink&) const at future:2003
frame #17: 0x00000001074f330b Vx76-[Debug]`std::__1::packaged_task<void (com::prg::system::cDisplayLink&)>::operator(this=0x000060c0002be2b0, __args=0x00006130000227a0)(com::prg::system::cDisplayLink&) at future:2223
frame #18: 0x00000001074ec71c Vx76-[Debug]`auto com::prg::system::inline_exec<std::__1::packaged_task<void (com::prg::system::cDisplayLink&)>, com::prg::system::cDisplayLink&>(thePkg=0x000060c0002be2b0, theArgs=0x00006130000227a0)>&, com::prg::system::cDisplayLink&&&) at system.h:1468
frame #19: 0x00000001074e58d9 Vx76-[Debug]`com::prg::system::cDisplayLink::createSPtr(this=0x0000700001f4c6b0, theLink=0x00006130000227a0)::$_2::operator()(__CVDisplayLink*, CVTimeStamp const*, CVTimeStamp const*, unsigned long long, unsigned long long*)::'lambda'(com::prg::system::cDisplayLink&)::operator()(com::prg::system::cDisplayLink&) const at graphics.mm:398
frame #20: 0x00000001074e2ed1 Vx76-[Debug]`com::prg::system::cDisplayLink::createSPtr(this=0x000060400131cff0, displayLink=0x000061b000112580, now=0x0000700001f4cdf8, outputTime=0x0000700001f4cd08, flagsIn=0, flagsOut=0x0000700001f4cd58)::$_2::operator()(__CVDisplayLink*, CVTimeStamp const*, CVTimeStamp const*, unsigned long long, unsigned long long*) at graphics.mm:441
frame #21: 0x00000001074e1521 Vx76-[Debug]`::___ZZN3com3prg6system12cDisplayLink10createSPtrEvENK3$_2cvU13block_pointerFiP15__CVDisplayLinkPK11CVTimeStampS8_yPyEEv_block_invoke(.block_descriptor=0x000060400131cfd0, displayLink=0x000061b000112580, now=0x0000700001f4cdf8, outputTime=0x0000700001f4cd08, flagsIn=0, flagsOut=0x0000700001f4cd58) at graphics.mm:295
frame #22: 0x00007fff4ebfc96e CoreVideo`CVDisplayLink::performIO(CVTimeStamp*) + 182
frame #23: 0x00007fff4ebfbe48 CoreVideo`CVDisplayLink::runIOThread() + 650
frame #24: 0x00007fff7a277305 libsystem_pthread.dylib`_pthread_body + 126
frame #25: 0x00007fff7a27a26f libsystem_pthread.dylib`_pthread_start + 70
frame #26: 0x00007fff7a276415 libsystem_pthread.dylib`thread_start + 13
bayoubengal
Halfling
Posts: 48
Joined: Wed Sep 05, 2018 3:18 pm

Re: Root::renderOneFrame() on background thread

Post by bayoubengal »

looking back at my opengl work, for the purposes of the platform, I need to work in

[somecglcontextref makeCurrentContext];

CGLLockContext(somecglcontextref);

into the operation of GL3PlusRenderSystem::_setRenderTarget()


What is the appropriate way to do that in GL3PlusRenderSystem?
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: Root::renderOneFrame() on background thread

Post by dark_sylinc »

CocoaContext (in RenderSystems/GL3Plus/src/windowing/OSX/OgreOSXCocoaContext.mm) supposedly encapsulate all context-related operations.

_setRenderTarget can switch context:

Code: Select all

// Switch context if different from current one
GL3PlusContext *newContext = 0;
target->getCustomAttribute(GL3PlusRenderTexture::CustomAttributeString_GLCONTEXT, &newContext);
if (newContext && mCurrentContext != newContext)
{
    _switchContext(newContext);
}
bayoubengal
Halfling
Posts: 48
Joined: Wed Sep 05, 2018 3:18 pm

Re: Root::renderOneFrame() on background thread

Post by bayoubengal »

right.
that code block does not execute because the ogre context has not changed. what is different is that the current thread is not configured for the NSOpenglContext. I was able to make it not crash and draw by puting the following in front of renderOneFrame(). This code sets the current context and locks it for the duration of the rendering. I modified OgreGL3PlusView to offer 'glContext' a method.

However, I'm getting red flashes when I resize which probably means drawRect: is being called on the view by the main thread directly.


any_type atExit;
if(!tmpUseMetal)
{
auto tmpGlViewPtr = self.prop3dView;
auto tmpGlContextPtr =(NSOpenGLContext*)[(id)tmpGlViewPtr glContext];
[unwrap(tmpGlContextPtr) makeCurrentContext];
CGLLockContext(tmpGlContextPtr.CGLContextObj);
atExit = make_scope_guard([=]
{
CGLUnlockContext(tmpGlContextPtr.CGLContextObj);
});
}
Post Reply