mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
OpenGLFrameBuffer: Automatically save/restore content when the context is paused/resumed
This addresses an issue where OpenGL-backed image content could get lost when putting and Android app into an inactive state. This happens because the GL context gets destroyed, freeing all associated resources. The workaround introduced here will listen for OpenGL state-change events, and attempt to save and restore framebuffer content into main memory, so that the content can be restored once the app is reactivated.
This commit is contained in:
parent
fc2caf0a4d
commit
c2d52a5499
8 changed files with 91 additions and 3 deletions
|
|
@ -252,6 +252,26 @@ public:
|
|||
const ScopedLock lock;
|
||||
};
|
||||
|
||||
void addListener (NativeContextListener& l)
|
||||
{
|
||||
listeners.add (&l);
|
||||
}
|
||||
|
||||
void removeListener (NativeContextListener& l)
|
||||
{
|
||||
listeners.remove (&l);
|
||||
}
|
||||
|
||||
void notifyWillPause()
|
||||
{
|
||||
listeners.call ([&] (auto& l) { l.contextWillPause(); });
|
||||
}
|
||||
|
||||
void notifyDidResume()
|
||||
{
|
||||
listeners.call ([&] (auto& l) { l.contextDidResume(); });
|
||||
}
|
||||
|
||||
Component& component;
|
||||
|
||||
private:
|
||||
|
|
@ -395,6 +415,7 @@ private:
|
|||
|
||||
mutable std::mutex nativeHandleMutex;
|
||||
OpenGLContext* juceContext = nullptr;
|
||||
ListenerList<NativeContextListener> listeners;
|
||||
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 };
|
||||
|
||||
|
|
|
|||
|
|
@ -213,6 +213,9 @@ public:
|
|||
const ScopedLock lock;
|
||||
};
|
||||
|
||||
void addListener (NativeContextListener&) {}
|
||||
void removeListener (NativeContextListener&) {}
|
||||
|
||||
private:
|
||||
CriticalSection mutex;
|
||||
Component& component;
|
||||
|
|
|
|||
|
|
@ -404,6 +404,9 @@ public:
|
|||
const ScopedLock lock;
|
||||
};
|
||||
|
||||
void addListener (NativeContextListener&) {}
|
||||
void removeListener (NativeContextListener&) {}
|
||||
|
||||
private:
|
||||
bool tryChooseVisual (const OpenGLPixelFormat& format, const std::vector<GLint>& optionalAttribs)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -295,6 +295,9 @@ public:
|
|||
double videoRefreshPeriodS = 1.0 / 60.0;
|
||||
};
|
||||
|
||||
void addListener (NativeContextListener&) {}
|
||||
void removeListener (NativeContextListener&) {}
|
||||
|
||||
Component& owner;
|
||||
NSOpenGLContext* renderContext = nil;
|
||||
NSOpenGLView* view = nil;
|
||||
|
|
|
|||
|
|
@ -196,6 +196,9 @@ public:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void addListener (NativeContextListener&) {}
|
||||
void removeListener (NativeContextListener&) {}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void handleAsyncUpdate() override
|
||||
|
|
|
|||
|
|
@ -193,6 +193,10 @@ public:
|
|||
//==============================================================================
|
||||
void pause()
|
||||
{
|
||||
#if JUCE_ANDROID
|
||||
context.executeOnGLThread ([this] (auto&) { nativeContext->notifyWillPause(); }, true);
|
||||
#endif
|
||||
|
||||
renderThread->remove (this);
|
||||
|
||||
if ((state.fetch_and (~StateFlags::initialised) & StateFlags::initialised) == 0)
|
||||
|
|
@ -670,6 +674,10 @@ public:
|
|||
if (context.renderer != nullptr)
|
||||
context.renderer->newOpenGLContextCreated();
|
||||
|
||||
#if JUCE_ANDROID
|
||||
nativeContext->notifyDidResume();
|
||||
#endif
|
||||
|
||||
return InitResult::success;
|
||||
}
|
||||
|
||||
|
|
@ -1669,6 +1677,16 @@ void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
|
|||
JUCE_CHECK_OPENGL_ERROR
|
||||
}
|
||||
|
||||
void OpenGLContext::NativeContextListener::addListener (OpenGLContext& ctx, NativeContextListener& l)
|
||||
{
|
||||
ctx.nativeContext->addListener (l);
|
||||
}
|
||||
|
||||
void OpenGLContext::NativeContextListener::removeListener (OpenGLContext& ctx, NativeContextListener& l)
|
||||
{
|
||||
ctx.nativeContext->removeListener (l);
|
||||
}
|
||||
|
||||
#if JUCE_ANDROID
|
||||
|
||||
void OpenGLContext::NativeContext::surfaceCreated (LocalRef<jobject>)
|
||||
|
|
|
|||
|
|
@ -348,6 +348,7 @@ public:
|
|||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
class NativeContext;
|
||||
class NativeContextListener;
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -35,7 +35,23 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
class OpenGLFrameBuffer::Pimpl
|
||||
/*
|
||||
Used on Android to detect when the GL context and associated resources (textures, framebuffers,
|
||||
etc.) need to be destroyed/created due to the Surface changing state.
|
||||
*/
|
||||
class OpenGLContext::NativeContextListener
|
||||
{
|
||||
public:
|
||||
virtual ~NativeContextListener() = default;
|
||||
|
||||
virtual void contextWillPause() = 0;
|
||||
virtual void contextDidResume() = 0;
|
||||
|
||||
static void addListener (OpenGLContext& ctx, NativeContextListener& l);
|
||||
static void removeListener (OpenGLContext& ctx, NativeContextListener& l);
|
||||
};
|
||||
|
||||
class OpenGLFrameBuffer::Pimpl : private OpenGLContext::NativeContextListener
|
||||
{
|
||||
public:
|
||||
struct SavedState
|
||||
|
|
@ -44,6 +60,11 @@ public:
|
|||
std::vector<PixelARGB> data;
|
||||
};
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return std::holds_alternative<TransientState> (state);
|
||||
|
|
@ -63,6 +84,8 @@ public:
|
|||
return false;
|
||||
|
||||
associatedContext = &context;
|
||||
NativeContextListener::addListener (*associatedContext, *this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +138,9 @@ public:
|
|||
|
||||
void release()
|
||||
{
|
||||
associatedContext = nullptr;
|
||||
if (auto* prev = std::exchange (associatedContext, nullptr))
|
||||
NativeContextListener::removeListener (*prev, *this);
|
||||
|
||||
state.emplace<std::monostate>();
|
||||
}
|
||||
|
||||
|
|
@ -353,7 +378,7 @@ private:
|
|||
if (OpenGLHelpers::isContextActive())
|
||||
{
|
||||
if (textureID != 0)
|
||||
glDeleteTextures (1, &textureID);
|
||||
gl::glDeleteTextures (1, &textureID);
|
||||
|
||||
if (depthOrStencilBuffer != 0)
|
||||
gl::glDeleteRenderbuffers (1, &depthOrStencilBuffer);
|
||||
|
|
@ -428,6 +453,17 @@ private:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void contextWillPause() override
|
||||
{
|
||||
saveAndRelease();
|
||||
}
|
||||
|
||||
void contextDidResume() override
|
||||
{
|
||||
if (associatedContext != nullptr)
|
||||
reloadSavedCopy (*associatedContext);
|
||||
}
|
||||
|
||||
OpenGLContext* associatedContext = nullptr;
|
||||
std::variant<std::monostate, TransientState, SavedState> state;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue