diff --git a/Source/video/InvisibleOpenGLContextComponent.h b/Source/video/InvisibleOpenGLContextComponent.h index 3c19e32..afd2252 100644 --- a/Source/video/InvisibleOpenGLContextComponent.h +++ b/Source/video/InvisibleOpenGLContextComponent.h @@ -4,12 +4,14 @@ // An invisible component that owns a persistent OpenGL context, always attached to the desktop. class InvisibleOpenGLContextComponent : public juce::Component { public: - InvisibleOpenGLContextComponent() { + InvisibleOpenGLContextComponent(juce::OpenGLRenderer* renderer) { setSize(1, 1); // Minimal size setBounds(0, 0, 1, 1); // Minimal bounds setVisible(true); setOpaque(false); context.setComponentPaintingEnabled(false); + context.setRenderer(renderer); + context.setContinuousRepainting(true); context.attachTo(*this); addToDesktop( juce::ComponentPeer::windowIsTemporary | diff --git a/Source/video/SyphonFrameGrabber.h b/Source/video/SyphonFrameGrabber.h index 796a4eb..970d0e9 100644 --- a/Source/video/SyphonFrameGrabber.h +++ b/Source/video/SyphonFrameGrabber.h @@ -3,64 +3,56 @@ #include "InvisibleOpenGLContextComponent.h" -class SyphonFrameGrabber : private juce::Thread, public juce::Component { +class SyphonFrameGrabber : public juce::Component, public juce::OpenGLRenderer { public: - SyphonFrameGrabber(SharedTextureManager& manager, juce::String server, juce::String app, ImageParser& parser, int pollMs = 8) - : juce::Thread("SyphonFrameGrabber"), pollIntervalMs(pollMs), manager(manager), parser(parser) { - // Create the invisible OpenGL context component - glContextComponent = std::make_unique(); - - // Make sure the context is properly initialized before creating the receiver - glContextComponent->getContext().makeActive(); - - // Create the receiver after the context is active - receiver = manager.addReceiver(server, app); - if (receiver) { - receiver->setUseCPUImage(true); // for pixel access - - // Initialize the receiver with the active GL context - receiver->initGL(); - } - - // Release the context - glContextComponent->getContext().deactivateCurrentContext(); - - // Start the thread after everything is set up - startThread(); + SyphonFrameGrabber(SharedTextureManager& manager, juce::String server, juce::String app, ImageParser& parser) + : manager(manager), parser(parser) { + // Create the invisible component that hosts our OpenGL context + openGLComponent = std::make_unique(this); + + // Store the server/app info for initializing the receiver + serverName = server; + appName = app; } ~SyphonFrameGrabber() override { - juce::CriticalSection::ScopedLockType lock(openGLLock); + // The InvisibleOpenGLContextComponent will detach the context in its destructor + openGLComponent = nullptr; - stopThread(500); + // Clean up the receiver if needed if (receiver) { manager.removeReceiver(receiver); receiver = nullptr; } - glContextComponent.reset(); } - void run() override { - while (!threadShouldExit()) { - { - juce::CriticalSection::ScopedLockType lock(openGLLock); + // OpenGLRenderer interface implementation + void newOpenGLContextCreated() override { + // Create the receiver in the GL context creation callback + receiver = manager.addReceiver(serverName, appName); + if (receiver) { + receiver->setUseCPUImage(true); // for pixel access + receiver->initGL(); + } + } - bool activated = false; - if (glContextComponent) { - activated = glContextComponent->getContext().makeActive(); - } - if (juce::OpenGLContext::getCurrentContext() != nullptr) { - receiver->renderGL(); - } - if (activated && glContextComponent) { - juce::OpenGLContext::deactivateCurrentContext(); - } - if (isActive() && receiver->isConnected) { - juce::Image image = receiver->getImage(); - parser.updateLiveFrame(image); - } + void renderOpenGL() override { + // This is called on the OpenGL thread at the frequency set by the context + if (receiver) { + receiver->renderGL(); + + if (isActive() && receiver->isConnected) { + juce::Image image = receiver->getImage(); + parser.updateLiveFrame(image); } - wait(pollIntervalMs); + } + } + + void openGLContextClosing() override { + // Clean up in the context closing callback + if (receiver) { + manager.removeReceiver(receiver); + receiver = nullptr; } } @@ -80,12 +72,12 @@ public: } private: - int pollIntervalMs; SharedTextureManager& manager; SharedTextureReceiver* receiver = nullptr; ImageParser& parser; - std::unique_ptr glContextComponent; - juce::CriticalSection openGLLock; // To protect OpenGL context operations + std::unique_ptr openGLComponent; + juce::String serverName; + juce::String appName; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SyphonFrameGrabber) };