mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
OpenGL: Carry out all GL rendering on a single thread
This commit is contained in:
parent
ae3bfdb1d2
commit
2ae87f95f1
8 changed files with 415 additions and 337 deletions
|
|
@ -63,16 +63,16 @@
|
|||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <typeindex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <optional>
|
||||
|
||||
//==============================================================================
|
||||
#include "juce_CompilerSupport.h"
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ DECLARE_JNI_CLASS_WITH_BYTECODE (JuceOpenGLViewSurface, "com/rmsl/juce/JuceOpenG
|
|||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
class OpenGLContext::NativeContext : private SurfaceHolderCallback
|
||||
class OpenGLContext::NativeContext : private SurfaceHolderCallback
|
||||
{
|
||||
public:
|
||||
NativeContext (Component& comp,
|
||||
|
|
@ -132,8 +132,7 @@ public:
|
|||
void* /*contextToShareWith*/,
|
||||
bool useMultisamplingIn,
|
||||
OpenGLVersion)
|
||||
: component (comp),
|
||||
surface (EGL_NO_SURFACE), context (EGL_NO_CONTEXT)
|
||||
: component (comp)
|
||||
{
|
||||
auto env = getEnv();
|
||||
|
||||
|
|
@ -175,88 +174,45 @@ public:
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
bool initialiseOnRenderThread (OpenGLContext& aContext)
|
||||
InitResult initialiseOnRenderThread (OpenGLContext& ctx)
|
||||
{
|
||||
jassert (hasInitialised);
|
||||
// The "real" initialisation happens when the surface is created. Here, we'll
|
||||
// just return true if the initialisation happened successfully, or false if
|
||||
// it hasn't happened yet, or was unsuccessful.
|
||||
const std::lock_guard lock { nativeHandleMutex };
|
||||
|
||||
// has the context already attached?
|
||||
jassert (surface == EGL_NO_SURFACE && context == EGL_NO_CONTEXT);
|
||||
if (! hasInitialised)
|
||||
return InitResult::fatal;
|
||||
|
||||
auto env = getEnv();
|
||||
if (context.get() == EGL_NO_CONTEXT && surface.get() == EGL_NO_SURFACE)
|
||||
return InitResult::retry;
|
||||
|
||||
ANativeWindow* window = nullptr;
|
||||
|
||||
LocalRef<jobject> holder (env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getHolder));
|
||||
|
||||
if (holder != nullptr)
|
||||
{
|
||||
LocalRef<jobject> jSurface (env->CallObjectMethod (holder.get(), AndroidSurfaceHolder.getSurface));
|
||||
|
||||
if (jSurface != nullptr)
|
||||
{
|
||||
window = ANativeWindow_fromSurface(env, jSurface.get());
|
||||
|
||||
// if we didn't succeed the first time, wait 25ms and try again
|
||||
if (window == nullptr)
|
||||
{
|
||||
Thread::sleep (200);
|
||||
window = ANativeWindow_fromSurface (env, jSurface.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (window == nullptr)
|
||||
{
|
||||
// failed to get a pointer to the native window after second try so bail out
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
// create the surface
|
||||
surface = eglCreateWindowSurface (display, config, window, nullptr);
|
||||
jassert (surface != EGL_NO_SURFACE);
|
||||
|
||||
ANativeWindow_release (window);
|
||||
|
||||
// create the OpenGL context
|
||||
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
|
||||
context = eglCreateContext (display, config, EGL_NO_CONTEXT, contextAttribs);
|
||||
jassert (context != EGL_NO_CONTEXT);
|
||||
|
||||
juceContext = &aContext;
|
||||
return true;
|
||||
juceContext = &ctx;
|
||||
return InitResult::success;
|
||||
}
|
||||
|
||||
void shutdownOnRenderThread()
|
||||
{
|
||||
jassert (hasInitialised);
|
||||
|
||||
// is there a context available to detach?
|
||||
jassert (surface != EGL_NO_SURFACE && context != EGL_NO_CONTEXT);
|
||||
|
||||
eglDestroyContext (display, context);
|
||||
context = EGL_NO_CONTEXT;
|
||||
|
||||
eglDestroySurface (display, surface);
|
||||
surface = EGL_NO_SURFACE;
|
||||
const std::lock_guard lock { nativeHandleMutex };
|
||||
juceContext = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool makeActive() const noexcept
|
||||
{
|
||||
if (! hasInitialised)
|
||||
return false;
|
||||
const std::lock_guard lock { nativeHandleMutex };
|
||||
|
||||
if (surface == EGL_NO_SURFACE || context == EGL_NO_CONTEXT)
|
||||
return false;
|
||||
|
||||
if (! eglMakeCurrent (display, surface, surface, context))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return hasInitialised
|
||||
&& surface.get() != EGL_NO_SURFACE
|
||||
&& context.get() != EGL_NO_CONTEXT
|
||||
&& eglMakeCurrent (display, surface.get(), surface.get(), context.get());
|
||||
}
|
||||
|
||||
bool isActive() const noexcept { return eglGetCurrentContext() == context; }
|
||||
bool isActive() const noexcept
|
||||
{
|
||||
const std::lock_guard lock { nativeHandleMutex };
|
||||
return eglGetCurrentContext() == context.get();
|
||||
}
|
||||
|
||||
static void deactivateCurrentContext()
|
||||
{
|
||||
|
|
@ -264,7 +220,7 @@ public:
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void swapBuffers() const noexcept { eglSwapBuffers (display, surface); }
|
||||
void swapBuffers() const noexcept { eglSwapBuffers (display, surface.get()); }
|
||||
bool setSwapInterval (const int) { return false; }
|
||||
int getSwapInterval() const { return 0; }
|
||||
|
||||
|
|
@ -295,8 +251,8 @@ public:
|
|||
ignoreUnused (holder, format, width, height);
|
||||
}
|
||||
|
||||
void surfaceCreated (LocalRef<jobject> holder) override;
|
||||
void surfaceDestroyed (LocalRef<jobject> holder) override;
|
||||
void surfaceCreated (LocalRef<jobject>) override;
|
||||
void surfaceDestroyed (LocalRef<jobject>) override;
|
||||
|
||||
//==============================================================================
|
||||
struct Locker
|
||||
|
|
@ -338,6 +294,8 @@ private:
|
|||
|
||||
void dispatchDraw (jobject /*canvas*/)
|
||||
{
|
||||
const std::lock_guard lock { nativeHandleMutex };
|
||||
|
||||
if (juceContext != nullptr)
|
||||
juceContext->triggerRepaint();
|
||||
}
|
||||
|
|
@ -393,6 +351,34 @@ private:
|
|||
return false;
|
||||
}
|
||||
|
||||
struct NativeWindowReleaser
|
||||
{
|
||||
void operator() (ANativeWindow* ptr) const { if (ptr != nullptr) ANativeWindow_release (ptr); }
|
||||
};
|
||||
|
||||
std::unique_ptr<ANativeWindow, NativeWindowReleaser> getNativeWindow() const
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
const LocalRef<jobject> holder (env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getHolder));
|
||||
|
||||
if (holder == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const LocalRef<jobject> jSurface (env->CallObjectMethod (holder.get(), AndroidSurfaceHolder.getSurface));
|
||||
|
||||
if (jSurface == nullptr)
|
||||
return nullptr;
|
||||
|
||||
constexpr auto numAttempts = 2;
|
||||
|
||||
for (auto i = 0; i < numAttempts; Thread::sleep (200), ++i)
|
||||
if (auto* ptr = ANativeWindow_fromSurface (env, jSurface.get()))
|
||||
return std::unique_ptr<ANativeWindow, NativeWindowReleaser> { ptr };
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CriticalSection mutex;
|
||||
bool hasInitialised = false;
|
||||
|
|
@ -400,9 +386,20 @@ private:
|
|||
GlobalRef surfaceView;
|
||||
Rectangle<int> lastBounds;
|
||||
|
||||
struct SurfaceDestructor
|
||||
{
|
||||
void operator() (EGLSurface x) const { if (x != EGL_NO_SURFACE) eglDestroySurface (display, x); }
|
||||
};
|
||||
|
||||
struct ContextDestructor
|
||||
{
|
||||
void operator() (EGLContext x) const { if (x != EGL_NO_CONTEXT) eglDestroyContext (display, x); }
|
||||
};
|
||||
|
||||
mutable std::mutex nativeHandleMutex;
|
||||
OpenGLContext* juceContext = nullptr;
|
||||
EGLSurface surface;
|
||||
EGLContext context;
|
||||
std::unique_ptr<std::remove_pointer_t<EGLSurface>, SurfaceDestructor> surface { EGL_NO_SURFACE };
|
||||
std::unique_ptr<std::remove_pointer_t<EGLContext>, ContextDestructor> context { EGL_NO_CONTEXT };
|
||||
|
||||
GlobalRef surfaceHolderCallback;
|
||||
|
||||
|
|
@ -412,6 +409,9 @@ private:
|
|||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext)
|
||||
};
|
||||
|
||||
EGLDisplay OpenGLContext::NativeContext::display = EGL_NO_DISPLAY;
|
||||
EGLDisplay OpenGLContext::NativeContext::config;
|
||||
|
||||
//==============================================================================
|
||||
void AndroidGLCallbacks::attachedToWindow (JNIEnv*, jobject /*this*/, jlong host)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ public:
|
|||
[view release];
|
||||
}
|
||||
|
||||
bool initialiseOnRenderThread (OpenGLContext&) { return true; }
|
||||
InitResult initialiseOnRenderThread (OpenGLContext&) { return InitResult::success; }
|
||||
|
||||
void shutdownOnRenderThread()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
bool initialiseOnRenderThread (OpenGLContext& c)
|
||||
InitResult initialiseOnRenderThread (OpenGLContext& c)
|
||||
{
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
|
||||
|
|
@ -196,11 +196,11 @@ public:
|
|||
renderContext = glXCreateNewContext (display, *bestConfig, GLX_RGBA_TYPE, (GLXContext) contextToShareWith, GL_TRUE);
|
||||
|
||||
if (renderContext == nullptr)
|
||||
return false;
|
||||
return InitResult::fatal;
|
||||
|
||||
c.makeActive();
|
||||
context = &c;
|
||||
return true;
|
||||
return InitResult::success;
|
||||
}
|
||||
|
||||
void shutdownOnRenderThread()
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
bool initialiseOnRenderThread (OpenGLContext&) { return true; }
|
||||
void shutdownOnRenderThread() { deactivateCurrentContext(); }
|
||||
InitResult initialiseOnRenderThread (OpenGLContext&) { return InitResult::success; }
|
||||
void shutdownOnRenderThread() { deactivateCurrentContext(); }
|
||||
|
||||
bool createdOk() const noexcept { return getRawContext() != nullptr; }
|
||||
NSOpenGLContext* getRawContext() const noexcept { return renderContext; }
|
||||
|
|
|
|||
|
|
@ -92,11 +92,11 @@ public:
|
|||
peer->removeScaleFactorListener (this);
|
||||
}
|
||||
|
||||
bool initialiseOnRenderThread (OpenGLContext& c)
|
||||
InitResult initialiseOnRenderThread (OpenGLContext& c)
|
||||
{
|
||||
threadAwarenessSetter = std::make_unique<ScopedThreadDPIAwarenessSetter> (nativeWindow->getNativeHandle());
|
||||
context = &c;
|
||||
return true;
|
||||
return InitResult::success;
|
||||
}
|
||||
|
||||
void shutdownOnRenderThread()
|
||||
|
|
|
|||
|
|
@ -91,10 +91,19 @@ static bool contextHasTextureNpotFeature()
|
|||
return stringTokens.contains ("GL_ARB_texture_non_power_of_two");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class OpenGLContext::CachedImage : public CachedComponentImage,
|
||||
private ThreadPoolJob
|
||||
template <typename Fn> struct ScopeGuard : Fn
|
||||
{
|
||||
~ScopeGuard() { Fn::operator()(); }
|
||||
};
|
||||
|
||||
template <typename Fn> ScopeGuard (Fn) -> ScopeGuard<Fn>;
|
||||
|
||||
//==============================================================================
|
||||
class OpenGLContext::CachedImage : public CachedComponentImage
|
||||
{
|
||||
template <typename T, typename U>
|
||||
static constexpr bool isFlagSet (const T& t, const U& u) { return (t & u) != 0; }
|
||||
|
||||
struct AreaAndScale
|
||||
{
|
||||
Rectangle<int> area;
|
||||
|
|
@ -136,8 +145,7 @@ class OpenGLContext::CachedImage : public CachedComponentImage,
|
|||
public:
|
||||
CachedImage (OpenGLContext& c, Component& comp,
|
||||
const OpenGLPixelFormat& pixFormat, void* contextToShare)
|
||||
: ThreadPoolJob ("OpenGL Rendering"),
|
||||
context (c),
|
||||
: context (c),
|
||||
component (comp)
|
||||
{
|
||||
nativeContext.reset (new NativeContext (component, pixFormat, contextToShare,
|
||||
|
|
@ -158,59 +166,61 @@ public:
|
|||
void start()
|
||||
{
|
||||
if (nativeContext != nullptr)
|
||||
{
|
||||
renderThread = std::make_unique<ThreadPool> (1);
|
||||
resume();
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (renderThread != nullptr)
|
||||
// make sure everything has finished executing
|
||||
state |= StateFlags::pendingDestruction;
|
||||
|
||||
if (workQueue.size() > 0)
|
||||
{
|
||||
// make sure everything has finished executing
|
||||
destroying = true;
|
||||
if (! renderThread->contains (this))
|
||||
resume();
|
||||
|
||||
if (workQueue.size() > 0)
|
||||
{
|
||||
if (! renderThread->contains (this))
|
||||
resume();
|
||||
|
||||
while (workQueue.size() != 0)
|
||||
Thread::sleep (20);
|
||||
}
|
||||
|
||||
pause();
|
||||
renderThread.reset();
|
||||
while (workQueue.size() != 0)
|
||||
Thread::sleep (20);
|
||||
}
|
||||
|
||||
hasInitialised = false;
|
||||
pause();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void pause()
|
||||
{
|
||||
signalJobShouldExit();
|
||||
contextsWaitingForFlush->deregisterContext (this);
|
||||
messageManagerLock.abort();
|
||||
renderThread->remove (this);
|
||||
|
||||
if (renderThread != nullptr)
|
||||
if ((state.fetch_and (~StateFlags::initialised) & StateFlags::initialised) != 0)
|
||||
{
|
||||
repaintEvent.signal();
|
||||
renderThread->removeJob (this, true, -1);
|
||||
context.makeActive();
|
||||
shutdownOnThread();
|
||||
OpenGLContext::deactivateCurrentContext();
|
||||
}
|
||||
}
|
||||
|
||||
void resume()
|
||||
{
|
||||
if (renderThread != nullptr)
|
||||
renderThread->addJob (this, false);
|
||||
renderThread->add (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paint (Graphics&) override
|
||||
{
|
||||
updateViewportSize();
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
updateViewportSize();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If you hit this assertion, it's because paint has been called from a thread other
|
||||
// than the message thread. This commonly happens when nesting OpenGL contexts, because
|
||||
// the 'outer' OpenGL renderer will attempt to call paint on the 'inner' context's
|
||||
// component from the OpenGL thread.
|
||||
// Nesting OpenGL contexts is not directly supported, however there is a workaround:
|
||||
// https://forum.juce.com/t/opengl-how-do-3d-with-custom-shaders-and-2d-with-juce-paint-methods-work-together/28026/7
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
|
||||
bool invalidateAll() override
|
||||
|
|
@ -234,8 +244,8 @@ public:
|
|||
|
||||
void triggerRepaint()
|
||||
{
|
||||
needsUpdate = 1;
|
||||
repaintEvent.signal();
|
||||
state |= (StateFlags::pendingRender | StateFlags::paintComponents);
|
||||
renderThread->triggerRepaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -278,39 +288,99 @@ public:
|
|||
JUCE_CHECK_OPENGL_ERROR
|
||||
}
|
||||
|
||||
bool renderFrame()
|
||||
struct ScopedContextActivator
|
||||
{
|
||||
MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
|
||||
bool activate (OpenGLContext& ctx)
|
||||
{
|
||||
if (! active)
|
||||
active = ctx.makeActive();
|
||||
|
||||
const auto isUpdating = needsUpdate.exchange (false);
|
||||
return active;
|
||||
}
|
||||
|
||||
~ScopedContextActivator()
|
||||
{
|
||||
if (active)
|
||||
OpenGLContext::deactivateCurrentContext();
|
||||
}
|
||||
|
||||
private:
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
enum class RenderStatus
|
||||
{
|
||||
nominal,
|
||||
messageThreadAborted,
|
||||
noWork,
|
||||
};
|
||||
|
||||
RenderStatus renderFrame (MessageManager::Lock& mmLock)
|
||||
{
|
||||
if (! isFlagSet (state, StateFlags::initialised))
|
||||
{
|
||||
switch (initialiseOnThread())
|
||||
{
|
||||
case InitResult::fatal:
|
||||
case InitResult::retry: return RenderStatus::noWork;
|
||||
case InitResult::success: break;
|
||||
}
|
||||
}
|
||||
|
||||
state |= StateFlags::initialised;
|
||||
|
||||
#if JUCE_IOS
|
||||
if (backgroundProcessCheck.isBackgroundProcess())
|
||||
return RenderStatus::noWork;
|
||||
#endif
|
||||
|
||||
std::optional<MessageManager::Lock::ScopedTryLockType> scopedLock;
|
||||
ScopedContextActivator contextActivator;
|
||||
|
||||
const auto stateToUse = state.fetch_and (StateFlags::persistent);
|
||||
|
||||
if (! isFlagSet (stateToUse, StateFlags::pendingRender) && ! context.continuousRepaint)
|
||||
return RenderStatus::noWork;
|
||||
|
||||
const auto isUpdating = isFlagSet (stateToUse, StateFlags::paintComponents);
|
||||
|
||||
if (context.renderComponents && isUpdating)
|
||||
{
|
||||
bool abortScope = false;
|
||||
// If we early-exit here, we need to restore these flags so that the render is
|
||||
// attempted again in the next time slice.
|
||||
const ScopeGuard scope { [&] { if (! abortScope) state |= stateToUse; } };
|
||||
|
||||
// This avoids hogging the message thread when doing intensive rendering.
|
||||
if (lastMMLockReleaseTime + 1 >= Time::getMillisecondCounter())
|
||||
Thread::sleep (2);
|
||||
std::this_thread::sleep_until (lastMMLockReleaseTime + std::chrono::milliseconds { 2 });
|
||||
|
||||
while (! shouldExit())
|
||||
{
|
||||
doWorkWhileWaitingForLock (false);
|
||||
if (renderThread->isListChanging())
|
||||
return RenderStatus::messageThreadAborted;
|
||||
|
||||
if (mmLock.retryLock())
|
||||
break;
|
||||
}
|
||||
doWorkWhileWaitingForLock (contextActivator);
|
||||
|
||||
if (shouldExit())
|
||||
return false;
|
||||
scopedLock.emplace (mmLock);
|
||||
|
||||
// If we can't get the lock here, it's probably because a context has been removed
|
||||
// on the main thread.
|
||||
// We return, just in case this renderer needs to be removed from the rendering thread.
|
||||
// If another renderer is being removed instead, then we should be able to get the lock
|
||||
// next time round.
|
||||
if (! scopedLock->isLocked())
|
||||
return RenderStatus::messageThreadAborted;
|
||||
|
||||
abortScope = true;
|
||||
}
|
||||
|
||||
if (! context.makeActive())
|
||||
return false;
|
||||
if (! contextActivator.activate (context))
|
||||
return RenderStatus::noWork;
|
||||
|
||||
{
|
||||
NativeContext::Locker locker (*nativeContext);
|
||||
|
||||
JUCE_CHECK_OPENGL_ERROR
|
||||
|
||||
doWorkWhileWaitingForLock (true);
|
||||
doWorkWhileWaitingForLock (contextActivator);
|
||||
|
||||
const auto currentAreaAndScale = areaAndScale.get();
|
||||
const auto viewportArea = currentAreaAndScale.area;
|
||||
|
|
@ -331,23 +401,20 @@ public:
|
|||
{
|
||||
paintComponent (currentAreaAndScale);
|
||||
|
||||
if (! hasInitialised)
|
||||
return false;
|
||||
if (! isFlagSet (state, StateFlags::initialised))
|
||||
return RenderStatus::noWork;
|
||||
|
||||
messageManagerLock.exit();
|
||||
lastMMLockReleaseTime = Time::getMillisecondCounter();
|
||||
scopedLock.reset();
|
||||
lastMMLockReleaseTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
|
||||
drawComponentBuffer();
|
||||
}
|
||||
|
||||
OpenGLContext::deactivateCurrentContext();
|
||||
}
|
||||
|
||||
contextsWaitingForFlush->flush (this);
|
||||
|
||||
return true;
|
||||
nativeContext->swapBuffers();
|
||||
return RenderStatus::nominal;
|
||||
}
|
||||
|
||||
void updateViewportSize()
|
||||
|
|
@ -520,79 +587,19 @@ public:
|
|||
updateViewportSize();
|
||||
|
||||
#if JUCE_MAC
|
||||
if (hasInitialised)
|
||||
if (isFlagSet (state, StateFlags::initialised))
|
||||
{
|
||||
[nativeContext->view update];
|
||||
renderFrame();
|
||||
|
||||
// We're already on the message thread, no need to lock it again.
|
||||
MessageManager::Lock mml;
|
||||
renderFrame (mml);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JobStatus runJob() override
|
||||
{
|
||||
{
|
||||
// Allow the message thread to finish setting-up the context before using it.
|
||||
MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
|
||||
|
||||
do
|
||||
{
|
||||
if (shouldExit())
|
||||
return ThreadPoolJob::jobHasFinished;
|
||||
|
||||
} while (! mmLock.retryLock());
|
||||
}
|
||||
|
||||
if (! initialiseOnThread())
|
||||
{
|
||||
hasInitialised = false;
|
||||
|
||||
return ThreadPoolJob::jobHasFinished;
|
||||
}
|
||||
|
||||
contextsWaitingForFlush->registerContext (this);
|
||||
|
||||
hasInitialised = true;
|
||||
|
||||
while (! shouldExit())
|
||||
{
|
||||
#if JUCE_IOS
|
||||
if (backgroundProcessCheck.isBackgroundProcess())
|
||||
{
|
||||
repaintEvent.wait (300);
|
||||
repaintEvent.reset();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (shouldExit())
|
||||
break;
|
||||
|
||||
#if JUCE_MAC
|
||||
if (context.continuousRepaint)
|
||||
{
|
||||
repaintEvent.wait (-1);
|
||||
renderFrame();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (! renderFrame())
|
||||
repaintEvent.wait (5); // failed to render, so avoid a tight fail-loop.
|
||||
else if (! context.continuousRepaint && ! shouldExit())
|
||||
repaintEvent.wait (-1);
|
||||
|
||||
repaintEvent.reset();
|
||||
}
|
||||
|
||||
hasInitialised = false;
|
||||
context.makeActive();
|
||||
shutdownOnThread();
|
||||
OpenGLContext::deactivateCurrentContext();
|
||||
|
||||
return ThreadPoolJob::jobHasFinished;
|
||||
}
|
||||
|
||||
bool initialiseOnThread()
|
||||
InitResult initialiseOnThread()
|
||||
{
|
||||
// On android, this can get called twice, so drop any previous state.
|
||||
associatedObjectNames.clear();
|
||||
|
|
@ -601,8 +608,8 @@ public:
|
|||
|
||||
context.makeActive();
|
||||
|
||||
if (! nativeContext->initialiseOnRenderThread (context))
|
||||
return false;
|
||||
if (const auto nativeResult = nativeContext->initialiseOnRenderThread (context); nativeResult != InitResult::success)
|
||||
return nativeResult;
|
||||
|
||||
#if JUCE_ANDROID
|
||||
// On android the context may be created in initialiseOnRenderThread
|
||||
|
|
@ -649,7 +656,7 @@ public:
|
|||
if (context.renderer != nullptr)
|
||||
context.renderer->newOpenGLContextCreated();
|
||||
|
||||
return true;
|
||||
return InitResult::success;
|
||||
}
|
||||
|
||||
void shutdownOnThread()
|
||||
|
|
@ -712,36 +719,23 @@ public:
|
|||
WaitableEvent finishedSignal;
|
||||
};
|
||||
|
||||
bool doWorkWhileWaitingForLock (bool contextIsAlreadyActive)
|
||||
void doWorkWhileWaitingForLock (ScopedContextActivator& contextActivator)
|
||||
{
|
||||
bool contextActivated = false;
|
||||
|
||||
for (OpenGLContext::AsyncWorker::Ptr work = workQueue.removeAndReturn (0);
|
||||
work != nullptr && (! shouldExit()); work = workQueue.removeAndReturn (0))
|
||||
while (const auto work = workQueue.removeAndReturn (0))
|
||||
{
|
||||
if ((! contextActivated) && (! contextIsAlreadyActive))
|
||||
{
|
||||
if (! context.makeActive())
|
||||
break;
|
||||
|
||||
contextActivated = true;
|
||||
}
|
||||
if (renderThread->isListChanging() || ! contextActivator.activate (context))
|
||||
break;
|
||||
|
||||
NativeContext::Locker locker (*nativeContext);
|
||||
|
||||
(*work) (context);
|
||||
clearGLError();
|
||||
}
|
||||
|
||||
if (contextActivated)
|
||||
OpenGLContext::deactivateCurrentContext();
|
||||
|
||||
return shouldExit();
|
||||
}
|
||||
|
||||
void execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock, bool calledFromDestructor = false)
|
||||
void execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock)
|
||||
{
|
||||
if (calledFromDestructor || ! destroying)
|
||||
if (! isFlagSet (state, StateFlags::pendingDestruction))
|
||||
{
|
||||
if (shouldBlock)
|
||||
{
|
||||
|
|
@ -749,7 +743,7 @@ public:
|
|||
OpenGLContext::AsyncWorker::Ptr worker (*blocker);
|
||||
workQueue.add (worker);
|
||||
|
||||
messageManagerLock.abort();
|
||||
renderThread->abortLock();
|
||||
context.triggerRepaint();
|
||||
|
||||
blocker->block();
|
||||
|
|
@ -758,7 +752,7 @@ public:
|
|||
{
|
||||
workQueue.add (std::move (workerToUse));
|
||||
|
||||
messageManagerLock.abort();
|
||||
renderThread->abortLock();
|
||||
context.triggerRepaint();
|
||||
}
|
||||
}
|
||||
|
|
@ -774,96 +768,155 @@ public:
|
|||
return dynamic_cast<CachedImage*> (c.getCachedComponentImage());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ContextsWaitingForFlush : private AsyncUpdater
|
||||
class RenderThread
|
||||
{
|
||||
public:
|
||||
void registerContext (CachedImage* ctx)
|
||||
RenderThread() = default;
|
||||
|
||||
~RenderThread()
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock (mutex);
|
||||
needsRender.insert_or_assign (ctx, false);
|
||||
flags.setDestructing();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void deregisterContext (CachedImage* ctx)
|
||||
void add (CachedImage* x)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock (mutex);
|
||||
needsRender.erase (ctx);
|
||||
condvar.notify_all();
|
||||
const std::scoped_lock lock { listMutex };
|
||||
images.push_back (x);
|
||||
}
|
||||
|
||||
/* Ask to swap the CachedImage's buffers on the main thread.
|
||||
|
||||
Will block until the buffers have been swapped, or until the swap has been cancelled.
|
||||
*/
|
||||
void flush (CachedImage* ctx)
|
||||
void remove (CachedImage* x)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD;
|
||||
|
||||
flags.setSafe (false);
|
||||
abortLock();
|
||||
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock (mutex);
|
||||
|
||||
if (const auto iter = needsRender.find (ctx); iter != needsRender.cend())
|
||||
iter->second = true;
|
||||
const std::scoped_lock lock { callbackMutex, listMutex };
|
||||
images.remove (x);
|
||||
}
|
||||
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
handleAsyncUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
|
||||
std::unique_lock<std::mutex> lock { mutex };
|
||||
condvar.wait (lock, [this, ctx]
|
||||
{
|
||||
// If the context is still registered, continue once it no longer needs rendering.
|
||||
if (const auto iter = needsRender.find (ctx); iter != needsRender.cend())
|
||||
return ! iter->second;
|
||||
|
||||
// If the context is no longer registered, continue so that the thread can be destroyed.
|
||||
return true;
|
||||
});
|
||||
}
|
||||
flags.setSafe (true);
|
||||
}
|
||||
|
||||
~ContextsWaitingForFlush() override
|
||||
bool contains (CachedImage* x)
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
|
||||
// There definitely shouldn't still be active CachedImages if this object is being destroyed!
|
||||
jassert (needsRender.empty());
|
||||
const std::scoped_lock lock { listMutex };
|
||||
return std::find (images.cbegin(), images.cend(), x) != images.cend();
|
||||
}
|
||||
|
||||
void triggerRepaint() { flags.setRenderRequested(); }
|
||||
|
||||
void abortLock() { messageManagerLock.abort(); }
|
||||
|
||||
bool isListChanging() { return ! flags.isSafe(); }
|
||||
|
||||
private:
|
||||
std::mutex mutex;
|
||||
std::condition_variable condvar;
|
||||
std::map<CachedImage*, bool> needsRender;
|
||||
|
||||
/* Swaps the buffers for each of the pending contexts, then notifies all rendering threads
|
||||
that they may continue.
|
||||
*/
|
||||
void handleAsyncUpdate() override
|
||||
RenderStatus renderAll()
|
||||
{
|
||||
const std::unique_lock<std::mutex> lock (mutex);
|
||||
auto result = RenderStatus::noWork;
|
||||
|
||||
for (auto& [ctx, shouldSwap] : needsRender)
|
||||
const std::scoped_lock lock { callbackMutex, listMutex };
|
||||
|
||||
for (auto* x : images)
|
||||
{
|
||||
if (! shouldSwap)
|
||||
continue;
|
||||
listMutex.unlock();
|
||||
const ScopeGuard scope { [&] { listMutex.lock(); } };
|
||||
|
||||
auto& native = *ctx->nativeContext;
|
||||
const auto status = x->renderFrame (messageManagerLock);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case RenderStatus::noWork: break;
|
||||
case RenderStatus::nominal: result = RenderStatus::nominal; break;
|
||||
case RenderStatus::messageThreadAborted: return RenderStatus::messageThreadAborted;
|
||||
}
|
||||
|
||||
const OpenGLContext::NativeContext::Locker nativeContextLocker { native };
|
||||
native.makeActive();
|
||||
native.swapBuffers();
|
||||
native.deactivateCurrentContext();
|
||||
shouldSwap = false;
|
||||
}
|
||||
|
||||
condvar.notify_all();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
SharedResourcePointer<ContextsWaitingForFlush> contextsWaitingForFlush;
|
||||
/* Allows the main thread to communicate changes to the render thread.
|
||||
|
||||
When the render thread needs to change in some way (asked to resume rendering,
|
||||
a renderer is added/removed, or the thread needs to stop prior to destruction),
|
||||
the main thread can set the appropriate flag on this structure. The render thread
|
||||
will call waitForWork() repeatedly, pausing when the render thread has no work to do,
|
||||
and resuming when requested by the main thread.
|
||||
*/
|
||||
class Flags
|
||||
{
|
||||
public:
|
||||
void setDestructing() { update ([] (auto& f) { f |= destructorCalled; }); }
|
||||
void setRenderRequested() { update ([] (auto& f) { f |= renderRequested; }); }
|
||||
|
||||
void setSafe (const bool safe)
|
||||
{
|
||||
update ([safe] (auto& f)
|
||||
{
|
||||
if (safe)
|
||||
f |= listSafe;
|
||||
else
|
||||
f &= ~listSafe;
|
||||
});
|
||||
}
|
||||
|
||||
bool isSafe()
|
||||
{
|
||||
const std::scoped_lock lock { mutex };
|
||||
return (flags & listSafe) != 0;
|
||||
}
|
||||
|
||||
/* Blocks until the 'safe' flag is set, and at least one other flag is set.
|
||||
After returning, the renderRequested flag will be unset.
|
||||
Returns true if rendering should continue.
|
||||
*/
|
||||
bool waitForWork (bool requestRender)
|
||||
{
|
||||
std::unique_lock lock { mutex };
|
||||
flags |= (requestRender ? renderRequested : 0);
|
||||
condvar.wait (lock, [this] { return flags > listSafe; });
|
||||
flags &= ~renderRequested;
|
||||
return ((flags & destructorCalled) == 0);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Fn>
|
||||
void update (Fn fn)
|
||||
{
|
||||
{
|
||||
const std::scoped_lock lock { mutex };
|
||||
fn (flags);
|
||||
}
|
||||
|
||||
condvar.notify_one();
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
renderRequested = 1 << 0,
|
||||
destructorCalled = 1 << 1,
|
||||
listSafe = 1 << 2
|
||||
};
|
||||
|
||||
std::mutex mutex;
|
||||
std::condition_variable condvar;
|
||||
int flags = listSafe;
|
||||
};
|
||||
|
||||
MessageManager::Lock messageManagerLock;
|
||||
std::mutex listMutex, callbackMutex;
|
||||
std::list<CachedImage*> images;
|
||||
Flags flags;
|
||||
|
||||
std::thread thread { [this]
|
||||
{
|
||||
Thread::setCurrentThreadName ("OpenGL Renderer");
|
||||
while (flags.waitForWork (renderAll() != RenderStatus::noWork)) {}
|
||||
} };
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
friend class NativeContext;
|
||||
|
|
@ -872,6 +925,8 @@ public:
|
|||
OpenGLContext& context;
|
||||
Component& component;
|
||||
|
||||
SharedResourcePointer<RenderThread> renderThread;
|
||||
|
||||
OpenGLFrameBuffer cachedImageFrameBuffer;
|
||||
RectangleList<int> validArea;
|
||||
Rectangle<int> lastScreenBounds;
|
||||
|
|
@ -882,15 +937,14 @@ public:
|
|||
StringArray associatedObjectNames;
|
||||
ReferenceCountedArray<ReferenceCountedObject> associatedObjects;
|
||||
|
||||
WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent { true };
|
||||
WaitableEvent canPaintNowFlag, finishedPaintingFlag;
|
||||
#if JUCE_OPENGL_ES
|
||||
bool shadersAvailable = true;
|
||||
#else
|
||||
bool shadersAvailable = false;
|
||||
#endif
|
||||
bool textureNpotSupported = false;
|
||||
std::atomic<bool> hasInitialised { false }, needsUpdate { true }, destroying { false };
|
||||
uint32 lastMMLockReleaseTime = 0;
|
||||
std::chrono::steady_clock::time_point lastMMLockReleaseTime{};
|
||||
|
||||
#if JUCE_MAC
|
||||
NSView* getCurrentView() const
|
||||
|
|
@ -944,23 +998,24 @@ public:
|
|||
// Note: the NSViewComponentPeer also has a SharedResourcePointer<PerScreenDisplayLinks> to
|
||||
// avoid unnecessarily duplicating display-link threads.
|
||||
SharedResourcePointer<PerScreenDisplayLinks> sharedDisplayLinks;
|
||||
|
||||
PerScreenDisplayLinks::Connection connection { sharedDisplayLinks->registerFactory ([this] (auto* screen)
|
||||
{
|
||||
return [this, screen]
|
||||
{
|
||||
// Note: check against lastScreen rather than trying to access the component's peer here,
|
||||
// because this function is not called on the main thread.
|
||||
if (context.continuousRepaint)
|
||||
if (screen == lastScreen)
|
||||
repaintEvent.signal();
|
||||
};
|
||||
}) };
|
||||
#endif
|
||||
|
||||
std::unique_ptr<ThreadPool> renderThread;
|
||||
enum StateFlags
|
||||
{
|
||||
pendingRender = 1 << 0,
|
||||
paintComponents = 1 << 1,
|
||||
pendingDestruction = 1 << 2,
|
||||
initialised = 1 << 3,
|
||||
|
||||
// Flags that may change state after each frame
|
||||
transient = pendingRender | paintComponents,
|
||||
|
||||
// Flags that should retain their state after each frame
|
||||
persistent = initialised | pendingDestruction
|
||||
};
|
||||
|
||||
std::atomic<int> state { 0 };
|
||||
ReferenceCountedArray<OpenGLContext::AsyncWorker, CriticalSection> workQueue;
|
||||
MessageManager::Lock messageManagerLock;
|
||||
|
||||
#if JUCE_IOS
|
||||
iOSBackgroundProcessCheck backgroundProcessCheck;
|
||||
|
|
@ -1541,40 +1596,56 @@ void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
|
|||
}
|
||||
|
||||
#if JUCE_ANDROID
|
||||
EGLDisplay OpenGLContext::NativeContext::display = EGL_NO_DISPLAY;
|
||||
EGLDisplay OpenGLContext::NativeContext::config;
|
||||
|
||||
void OpenGLContext::NativeContext::surfaceCreated (LocalRef<jobject> holder)
|
||||
void OpenGLContext::NativeContext::surfaceCreated (LocalRef<jobject>)
|
||||
{
|
||||
ignoreUnused (holder);
|
||||
|
||||
if (auto* cachedImage = CachedImage::get (component))
|
||||
{
|
||||
if (auto* pool = cachedImage->renderThread.get())
|
||||
const std::lock_guard lock { nativeHandleMutex };
|
||||
|
||||
jassert (hasInitialised);
|
||||
|
||||
// has the context already attached?
|
||||
jassert (surface.get() == EGL_NO_SURFACE && context.get() == EGL_NO_CONTEXT);
|
||||
|
||||
const auto window = getNativeWindow();
|
||||
|
||||
if (window == nullptr)
|
||||
{
|
||||
if (! pool->contains (cachedImage))
|
||||
{
|
||||
cachedImage->resume();
|
||||
cachedImage->context.triggerRepaint();
|
||||
}
|
||||
// failed to get a pointer to the native window so bail out
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
// create the surface
|
||||
surface.reset (eglCreateWindowSurface (display, config, window.get(), nullptr));
|
||||
jassert (surface.get() != EGL_NO_SURFACE);
|
||||
|
||||
// create the OpenGL context
|
||||
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
|
||||
context.reset (eglCreateContext (display, config, EGL_NO_CONTEXT, contextAttribs));
|
||||
jassert (context.get() != EGL_NO_CONTEXT);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::NativeContext::surfaceDestroyed (LocalRef<jobject> holder)
|
||||
{
|
||||
ignoreUnused (holder);
|
||||
|
||||
// unlike the name suggests this will be called just before the
|
||||
// surface is destroyed. We need to pause the render thread.
|
||||
if (auto* cachedImage = CachedImage::get (component))
|
||||
if (auto* cached = CachedImage::get (component))
|
||||
{
|
||||
cachedImage->pause();
|
||||
|
||||
if (auto* threadPool = cachedImage->renderThread.get())
|
||||
threadPool->waitForJobToFinish (cachedImage, -1);
|
||||
cached->resume();
|
||||
cached->triggerRepaint();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::NativeContext::surfaceDestroyed (LocalRef<jobject>)
|
||||
{
|
||||
if (auto* cached = CachedImage::get (component))
|
||||
cached->pause();
|
||||
|
||||
{
|
||||
const std::lock_guard lock { nativeHandleMutex };
|
||||
|
||||
context.reset (EGL_NO_CONTEXT);
|
||||
surface.reset (EGL_NO_SURFACE);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -320,6 +320,13 @@ public:
|
|||
#endif
|
||||
|
||||
private:
|
||||
enum class InitResult
|
||||
{
|
||||
fatal,
|
||||
retry,
|
||||
success
|
||||
};
|
||||
|
||||
friend class OpenGLTexture;
|
||||
|
||||
class CachedImage;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue