Merge pull request #9446 from hrydgard/android-lifecycle

Rework Android lost/restore GL context handling again
This commit is contained in:
Henrik Rydgård 2017-03-18 16:10:31 +01:00 committed by GitHub
commit fc648f5f32
10 changed files with 152 additions and 88 deletions

View File

@ -145,7 +145,7 @@ DrawEngineGLES::DrawEngineGLES()
indexGen.Setup(decIndex);
InitDeviceObjects();
register_gl_resource_holder(this);
register_gl_resource_holder(this, "drawengine_gles", 1);
tessDataTransfer = new TessellationDataTransferGLES(gl_extensions.VersionGEThan(3, 0, 0));
}
@ -200,7 +200,6 @@ void DrawEngineGLES::DestroyDeviceObjects() {
bufferNameInfo_.clear();
freeSizedBuffers_.clear();
bufferNameCacheSize_ = 0;
if (sharedVao_ != 0) {
glDeleteVertexArrays(1, &sharedVao_);
}
@ -209,7 +208,7 @@ void DrawEngineGLES::DestroyDeviceObjects() {
void DrawEngineGLES::GLLost() {
ILOG("TransformDrawEngine::GLLost()");
// The objects have already been deleted.
// The objects have already been deleted by losing the context, so we don't call DestroyDeviceObjects.
bufferNameCache_.clear();
bufferNameInfo_.clear();
freeSizedBuffers_.clear();

View File

@ -534,6 +534,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
}
void NativeInitGraphics(GraphicsContext *graphicsContext) {
ILOG("NativeInitGraphics");
using namespace Draw;
Core_SetGraphicsContext(graphicsContext);
g_draw = graphicsContext->GetDrawContext();
@ -640,16 +642,17 @@ void NativeInitGraphics(GraphicsContext *graphicsContext) {
#endif
g_gameInfoCache = new GameInfoCache();
ILOG("NativeInitGraphics completed");
}
void NativeShutdownGraphics() {
ILOG("NativeShutdownGraphics");
#ifdef _WIN32
delete winAudioBackend;
winAudioBackend = NULL;
#endif
screenManager->deviceLost();
delete g_gameInfoCache;
g_gameInfoCache = nullptr;
@ -664,6 +667,8 @@ void NativeShutdownGraphics() {
colorPipeline->Release();
texColorPipeline->Release();
ILOG("NativeShutdownGraphics done");
}
void TakeScreenshot() {
@ -873,20 +878,21 @@ void NativeUpdate() {
}
void NativeDeviceLost() {
if (g_gameInfoCache)
g_gameInfoCache->Clear();
screenManager->deviceLost();
// We start by calling gl_lost - this lets objects zero their native GL objects
// so they then don't try to delete them as well.
if (GetGPUBackend() == GPUBackend::OPENGL) {
gl_lost();
}
if (g_gameInfoCache)
g_gameInfoCache->Clear();
screenManager->deviceLost();
}
void NativeDeviceRestore() {
NativeDeviceLost();
screenManager->deviceRestore();
if (GetGPUBackend() == GPUBackend::OPENGL) {
gl_restore();
}
screenManager->deviceRestore();
}
bool NativeIsAtTopLevel() {

View File

@ -16,7 +16,7 @@ class ManagedTexture : public GfxResourceHolder {
public:
ManagedTexture(Draw::DrawContext *draw) : draw_(draw), texture_(nullptr) {
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL)
register_gl_resource_holder(this);
register_gl_resource_holder(this, "managed_texture", 0);
}
~ManagedTexture() {
if (texture_)

View File

@ -1,5 +1,5 @@
// This is generic code that is included in all Android apps that use the
// Native framework by Henrik Rydgård (https://github.com/hrydgard/native).
// Native framework by Henrik Rydgard (https://github.com/hrydgard/native).
// It calls a set of methods defined in NativeApp.h. These should be implemented
// by your game or app.
@ -602,6 +602,14 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) {
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
ILOG("NativeApp.shutdown() -- begin");
if (renderer_inited) {
NativeShutdownGraphics();
graphicsContext->Shutdown();
delete graphicsContext;
graphicsContext = nullptr;
renderer_inited = false;
}
NativeShutdown();
VFSShutdown();
ILOG("NativeApp.shutdown() -- end");
@ -612,16 +620,23 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env,
if (javaGL && !graphicsContext) {
graphicsContext = new AndroidJavaEGLGraphicsContext();
}
ILOG("NativeApp.displayInit() (graphicsContext=%p)", graphicsContext);
if (!renderer_inited) {
if (renderer_inited) {
ILOG("NativeApp.displayInit() restoring");
NativeDeviceLost();
NativeShutdownGraphics();
NativeDeviceRestore();
NativeInitGraphics(graphicsContext);
ILOG("Restored.");
} else {
ILOG("NativeApp.displayInit() first time");
NativeInitGraphics(graphicsContext);
renderer_inited = true;
renderer_ever_inited = true;
} else {
NativeDeviceRestore();
ILOG("displayInit: NativeDeviceRestore completed.");
}
NativeMessageReceived("recreateviews", "");
}
// JavaEGL
@ -695,19 +710,6 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env,
ProcessFrameCommands(env);
}
extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayShutdown(JNIEnv *env, jobject obj) {
ILOG("NativeApp.displayShutdown()");
if (renderer_inited) {
NativeDeviceLost();
NativeShutdownGraphics();
graphicsContext->Shutdown();
delete graphicsContext;
graphicsContext = nullptr;
renderer_inited = false;
NativeMessageReceived("recreateviews", "");
}
}
void System_AskForPermission(SystemPermission permission) {
switch (permission) {
case SYSTEM_PERMISSION_STORAGE:

View File

@ -589,7 +589,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback {
if (javaGL) {
Log.i(TAG, "onDestroy");
mGLSurfaceView.onDestroy();
nativeRenderer.onDestroyed();
NativeApp.audioShutdown();
// Probably vain attempt to help the garbage collector...
mGLSurfaceView = null;

View File

@ -59,6 +59,7 @@ public class NativeRenderer implements GLSurfaceView.Renderer {
}
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
Log.i(TAG, "onSurfaceCreated");
// Log.i(TAG, "onSurfaceCreated - EGL context is new or was lost");
// Actually, it seems that it is here we should recreate lost GL objects.
displayInit();
@ -71,18 +72,13 @@ public class NativeRenderer implements GLSurfaceView.Renderer {
double actualH = sz.y;
dpi_scale_x = ((double)width / (double)actualW);
dpi_scale_y = ((double)height / (double)actualH);
Log.i(TAG, "onSurfaceChanged: " + dpi_scale_x + "x" + dpi_scale_y + " (width=" + width + ", actualW=" + actualW);
Log.i(TAG, "onSurfaceChanged: Scale: " + dpi_scale_x + "x" + dpi_scale_y + " (width=" + width + ", actualW=" + actualW);
int scaled_dpi = (int)((double)dpi * dpi_scale_x);
displayResize(width, height, scaled_dpi, refreshRate);
last_width = width;
last_height = height;
}
// Not override, it's custom.
public void onDestroyed() {
displayShutdown();
}
// NATIVE METHODS
// Note: This also means "device lost" and you should reload
@ -90,5 +86,4 @@ public class NativeRenderer implements GLSurfaceView.Renderer {
public native void displayInit();
public native void displayResize(int w, int h, int dpi, float refreshRate);
public native void displayRender();
public native void displayShutdown();
}

View File

@ -4,18 +4,27 @@
#include "base/logging.h"
#include "gfx/gl_lost_manager.h"
std::vector<GfxResourceHolder *> *holders;
struct Holder {
GfxResourceHolder *holder;
const char *desc;
int priority;
};
std::vector<Holder> *holders;
static bool inLost;
static bool inRestore;
static int g_max_priority = 0;
void register_gl_resource_holder(GfxResourceHolder *holder) {
void register_gl_resource_holder(GfxResourceHolder *holder, const char *desc, int priority) {
if (inLost || inRestore) {
FLOG("BAD: Should not call register_gl_resource_holder from lost/restore path");
return;
}
if (holders) {
holders->push_back(holder);
holders->push_back({ holder, desc, priority });
if (g_max_priority < priority)
g_max_priority = priority;
} else {
WLOG("GL resource holder not initialized, cannot register resource");
}
@ -28,7 +37,7 @@ void unregister_gl_resource_holder(GfxResourceHolder *holder) {
}
if (holders) {
for (size_t i = 0; i < holders->size(); i++) {
if ((*holders)[i] == holder) {
if ((*holders)[i].holder == holder) {
holders->erase(holders->begin() + i);
return;
}
@ -47,12 +56,17 @@ void gl_restore() {
return;
}
ILOG("gl_restore() restoring %i items:", (int)holders->size());
ILOG("gl_restore() restoring %d items:", (int)holders->size());
for (int p = 0; p <= g_max_priority; p++) {
for (size_t i = 0; i < holders->size(); i++) {
ILOG("gl_restore(%i / %i, %p, %08x)", (int)(i + 1), (int)holders->size(), (*holders)[i], *((uint32_t *)((*holders)[i])));
(*holders)[i]->GLRestore();
if ((*holders)[i].priority == p) {
ILOG("GLRestore(%d / %d, %s, prio %d)", (int)(i + 1), (int)holders->size(),
(*holders)[i].desc, (*holders)[i].priority);
(*holders)[i].holder->GLRestore();
}
ILOG("gl_restore() completed on %i items:", (int)holders->size());
}
}
ILOG("gl_restore() completed on %d items:", (int)holders->size());
inRestore = false;
}
@ -65,9 +79,14 @@ void gl_lost() {
}
ILOG("gl_lost() clearing %i items:", (int)holders->size());
for (int p = g_max_priority; p >= 0; p--) {
for (size_t i = 0; i < holders->size(); i++) {
ILOG("gl_lost(%i / %i, %p, %08x)", (int)(i + 1), (int)holders->size(), (*holders)[i], *((uint32_t *)((*holders)[i])));
(*holders)[i]->GLLost();
if ((*holders)[i].priority == p) {
ILOG("gl_lost(%d / %d, %s, prio %d)", (int) (i + 1), (int) holders->size(),
(*holders)[i].desc, (*holders)[i].priority);
(*holders)[i].holder->GLLost();
}
}
}
ILOG("gl_lost() completed on %i items:", (int)holders->size());
inLost = false;
@ -78,7 +97,8 @@ void gl_lost_manager_init() {
FLOG("Double GL lost manager init");
// Dead here (FLOG), no need to delete holders
}
holders = new std::vector<GfxResourceHolder *>();
g_max_priority = 0;
holders = new std::vector<Holder>();
}
void gl_lost_manager_shutdown() {

View File

@ -3,17 +3,30 @@
// On Android, even OpenGL can lose allocated resources. This is a utility to keep
// track of them.
// It's important to realize that with OpenGL, there's no Lost event that can be relied upon.
// The only solid indication we get is onSurfaceCreated. That is called every time the graphics
// surface that we render upon has been recreated. When that's called, we know that any
// gl resources we owned before it was called have been killed and need to be recreated.
// However, with D3D UWP, and potentially other platforms, there is a lost event.
// So we keep that infrastructure, but with GL we simply call both Lost and Restore when we detect a Restore.
// For code simplicity, it may be a good idea to manually tear down and recreate everything. Even in this case,
// it's important to use this to zero out resource handles in GLLost() - gl_lost should be called before you
// tear things down, so then you can check if handles are zero and avoid deleting resources that are already gone.
class GfxResourceHolder {
public:
virtual ~GfxResourceHolder() {}
virtual void GLRestore() = 0;
virtual void GLLost() = 0;
virtual void GLRestore() = 0;
};
void gl_lost_manager_init();
void gl_lost_manager_shutdown();
void register_gl_resource_holder(GfxResourceHolder *holder);
// The string pointed to by desc must be a constant or otherwise live for the entire registered lifetime of the object.
void register_gl_resource_holder(GfxResourceHolder *holder, const char *desc, int priority);
void unregister_gl_resource_holder(GfxResourceHolder *holder);
// Notifies all objects it's time to forget / delete things.

View File

@ -49,7 +49,7 @@ GLSLProgram *glsl_create(const char *vshader, const char *fshader, std::string *
delete program;
return 0;
}
register_gl_resource_holder(program);
register_gl_resource_holder(program, "glsl_program", 0);
return program;
}
@ -70,7 +70,7 @@ GLSLProgram *glsl_create_source(const char *vshader_src, const char *fshader_src
delete program;
return 0;
}
register_gl_resource_holder(program);
register_gl_resource_holder(program, "glsl_program_src", 0);
return program;
}

View File

@ -271,16 +271,18 @@ GLuint ShaderStageToOpenGL(ShaderStage stage) {
}
}
// Not registering this as a resource holder, instead Pipeline is registered. It will
// invoke Compile again to recreate the shader then link them together.
class OpenGLShaderModule : public ShaderModule {
class OpenGLShaderModule : public ShaderModule, public GfxResourceHolder {
public:
OpenGLShaderModule(ShaderStage stage) : stage_(stage), shader_(0) {
OpenGLShaderModule(ShaderStage stage) : stage_(stage) {
register_gl_resource_holder(this, "drawcontext_shader_module", 0);
glstage_ = ShaderStageToOpenGL(stage);
}
~OpenGLShaderModule() {
ILOG("Shader module destroyed");
if (shader_)
glDeleteShader(shader_);
unregister_gl_resource_holder(this);
}
bool Compile(ShaderLanguage language, const uint8_t *data, size_t dataSize);
@ -289,9 +291,6 @@ public:
}
const std::string &GetSource() const { return source_; }
void Unset() {
shader_ = 0;
}
ShaderLanguage GetLanguage() {
return language_;
}
@ -299,12 +298,25 @@ public:
return stage_;
}
void GLLost() override {
ILOG("Shader module lost");
// Shader has been destroyed since the old context is gone, so let's zero it.
shader_ = 0;
}
void GLRestore() override {
ILOG("Shader module being restored");
if (!Compile(language_, (const uint8_t *)source_.data(), source_.size())) {
ELOG("Shader restore compilation failed: %s", source_.c_str());
}
}
private:
ShaderStage stage_;
ShaderLanguage language_;
GLuint shader_;
GLuint glstage_;
bool ok_;
GLuint shader_ = 0;
GLuint glstage_ = 0;
bool ok_ = false;
std::string source_; // So we can recompile in case of context loss.
};
@ -369,11 +381,12 @@ class OpenGLPipeline : public Pipeline, GfxResourceHolder {
public:
OpenGLPipeline() {
program_ = 0;
register_gl_resource_holder(this);
// Priority 1 so this gets restored after the shaders.
register_gl_resource_holder(this, "drawcontext_pipeline", 1);
}
~OpenGLPipeline() {
unregister_gl_resource_holder(this);
for (auto iter : shaders) {
for (auto &iter : shaders) {
iter->Release();
}
glDeleteProgram(program_);
@ -389,15 +402,10 @@ public:
void GLLost() override {
program_ = 0;
for (auto iter : shaders) {
iter->Unset();
}
}
void GLRestore() override {
for (auto iter : shaders) {
iter->Compile(iter->GetLanguage(), (const uint8_t *)iter->GetSource().c_str(), iter->GetSource().size());
}
// Shaders will have been restored before the pipeline.
LinkShaders();
}
@ -890,7 +898,7 @@ public:
totalSize_ = size;
glBindBuffer(target_, buffer_);
glBufferData(target_, size, NULL, usage_);
register_gl_resource_holder(this);
register_gl_resource_holder(this, "drawcontext_buffer", 0);
}
~OpenGLBuffer() override {
unregister_gl_resource_holder(this);
@ -959,6 +967,7 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) {
pipeline->dynamicUniforms = *desc.uniformDesc;
return pipeline;
} else {
ELOG("Failed to create pipeline - shaders failed to link");
delete pipeline;
return NULL;
}
@ -1048,6 +1057,8 @@ bool OpenGLPipeline::LinkShaders() {
OutputDebugStringUTF8(buf);
#endif
delete[] buf;
} else {
ELOG("Could not link program with %d shaders for unknown reason:", (int)shaders.size());
}
return false;
}
@ -1228,9 +1239,25 @@ void OpenGLInputLayout::Unapply() {
}
}
class OpenGLFramebuffer : public Framebuffer {
class OpenGLFramebuffer : public Framebuffer, public GfxResourceHolder {
public:
OpenGLFramebuffer() {
register_gl_resource_holder(this, "framebuffer", 0);
}
~OpenGLFramebuffer();
void GLLost() override {
handle = 0;
color_texture = 0;
z_stencil_buffer = 0;
z_buffer = 0;
stencil_buffer = 0;
}
void GLRestore() override {
ELOG("Restoring framebuffers not yet implemented");
}
GLuint handle = 0;
GLuint color_texture = 0;
GLuint z_stencil_buffer = 0; // Either this is set, or the two below.
@ -1624,14 +1651,16 @@ void OpenGLContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBCh
}
OpenGLFramebuffer::~OpenGLFramebuffer() {
unregister_gl_resource_holder(this);
CHECK_GL_ERROR_IF_DEBUG();
if (gl_extensions.ARB_framebuffer_object || gl_extensions.IsGLES) {
if (handle) {
glBindFramebuffer(GL_FRAMEBUFFER, handle);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (handle)
glDeleteFramebuffers(1, &handle);
}
if (z_stencil_buffer)
glDeleteRenderbuffers(1, &z_stencil_buffer);
if (z_buffer)
@ -1640,12 +1669,13 @@ OpenGLFramebuffer::~OpenGLFramebuffer() {
glDeleteRenderbuffers(1, &stencil_buffer);
} else if (gl_extensions.EXT_framebuffer_object) {
#ifndef USING_GLES2
if (handle) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, handle);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER_EXT, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
if (handle)
glDeleteFramebuffersEXT(1, &handle);
}
if (z_stencil_buffer)
glDeleteRenderbuffers(1, &z_stencil_buffer);
if (z_buffer)