Android GL: Add a native EGL path, behind a dev toggle

First step towards removing JavaGL. The point isn't EGL itself - it's who
owns the render thread. With GLSurfaceView, Java creates the thread and the
context and calls us back, so a lost context is only observable after the
fact as a second onSurfaceCreated. That's what forces displayInit to tear
down and restart the emu thread from inside a JNI callback, and to call
NotifyContextLost first because the queue is about to be drained on a thread
with no context. None of that is needed when we own EGL: the render thread
joins the emu thread itself, with the context still current, so the drain is
real work rather than something to skip.

AndroidEGLGraphicsContext subclasses OpenGLGraphicsContext (whose own
InitSurface assumes someone else already made a context current) and adds
display/config/surface/context plus a swap function. With it, GL reuses the
Vulkan lifecycle wholesale: queryConfig("androidJavaGL") returns false, so
Java builds a plain SurfaceView and starts the native render loop when a
surface arrives - including all of the surface/pause ordering fixes from the
previous branch. runVulkanRenderLoop/requestExitVulkanRenderLoop are now
runRenderLoop/requestExitRenderLoop and serve both backends; the thread they
spawn branches on NeedsSeparateEmuThread, because the two backends split the
work in opposite directions (Vulkan: this thread is the main loop and the
render manager spawns the render thread; GL: this thread *is* the render
thread and spawns the emu thread).

Deliberately a port, not an improvement:

- GLES client version still follows NativeRenderer - 3 for VR, 2 otherwise.
  Not a cap; GLES3 is detected from GL_VERSION at runtime.
- No swap-interval function. GLSurfaceView gave us no way to set one, so
  PresentMode::IMMEDIATE has always been ignored here, and honoring it now
  would be a behavior change. Worth revisiting once this path is the only one.

The config ladder does take the user's word that Mali 400 and the Xperia Play
are no longer a factor: it asks for RGBA8888/D24S8 and falls back twice,
rather than reproducing GLSurfaceView's ask-for-nothing default that the
comments in PpssppActivity.onCreate exist to explain. ANativeWindow's format
is set from EGL_NATIVE_VISUAL_ID, which is the proper fix for the EGL_BAD_MATCH
those comments describe.

Off by default, behind Developer tools / Graphics / "Use native EGL instead
of GLSurfaceView" (OpenGL backend, Android only). Needs an app restart, since
the context is chosen in NativeApp.init.

Builds for arm64-v8a and on Windows; NOT yet run on a device.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-10-08 15:22:25 +02:00
1 parent d74f16ef51
commit f868976af2
10 files changed
+384 -29

No files matched your search

+11
View File
@@ -539,6 +539,17 @@ void DeveloperToolsScreen::CreateGraphicsTab(UI::LinearLayout *list) {
});
}
#if PPSSPP_PLATFORM(ANDROID)
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
list->Add(new ItemHeader(dev->T("OpenGL")));
// Temporary, while the EGL path is being tested on devices - see Core/Config.h.
list->Add(new CheckBox(&g_Config.bAndroidNativeEGL, dev->T("Use native EGL instead of GLSurfaceView"), ""))->OnClick.Add([](UI::EventParams &e) {
// TODO: Not translating yet, same as the multi-threaded rendering checkbox above.
g_OSD.Show(OSDType::MESSAGE_WARNING, "Restart required");
});
}
#endif
if (GetGPUBackend() == GPUBackend::VULKAN && SupportsCustomDriver()) {
auto driverChoice = list->Add(new Choice(gr->T("AdrenoTools driver manager")));
driverChoice->OnClick.Add([=](UI::EventParams &e) {