Android: fix OpenGL black screen regression from PR #286 - #287
Conversation
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/c3bba099-2e22-4796-bf2e-920e891cbcb7 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/93057211-bba3-4d19-a4d6-fbad9da58d26 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adjusts Android surface format handling to prevent an OpenGL black-screen regression introduced by the earlier Vulkan RGB565 workaround, while also bumping framework package versions to the latest published build.
Changes:
- Bump
ppy.osu.Framework/ Android / iOS package references from2026.501.1→2026.501.2. - Gate
SurfaceHolder.SetFormat(Rgba8888)inOsuGameActivityso it only runs for Vulkan-configured sessions (instead of unconditionally). - Add runtime logging indicating whether the surface format stamp was applied or skipped.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
osu.Android/OsuGameActivity.cs |
Gates SurfaceHolder.SetFormat(Rgba8888) behind a Vulkan check to avoid triggering OpenGL surface recreate cycles. |
osu.Game/osu.Game.csproj |
Bumps ppy.osu.Framework package version. |
osu.Android.props |
Bumps ppy.osu.Framework.Android package version. |
osu.iOS.props |
Bumps ppy.osu.Framework.iOS package version. |
Comments suppressed due to low confidence (1)
osu.Game/osu.Game.csproj:45
- The comment explaining the
ppy.Veldrid.SPIRVpin still referencesppy.osu.Framework 2026.501.1, but this PR bumps the framework reference to2026.501.2. Please update the comment so it matches the actual pinned framework version (and remains accurate for future maintenance).
<!--
Explicitly pin `ppy.Veldrid.SPIRV` to the winnerspiros fork build that
`ppy.osu.Framework 2026.501.1` was compiled against. This version is the only
one whose `runtimes/android-arm64/native/libveldrid-spirv.so` is aligned to 16 KB
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| bool isVulkan = false; | ||
| try { isVulkan = LogManagement.IsVulkanConfigured(); } | ||
| catch (Exception e) { Debug.WriteLine($"[osu!] SurfaceHolder format gate: IsVulkanConfigured failed, defaulting to skip SetFormat: {e.Message}"); } | ||
|
|
||
| if (isVulkan) |
There was a problem hiding this comment.
The surface-format gate only checks LogManagement.IsVulkanConfigured(), which returns true only when framework.ini contains Renderer=Vulkan. If a user has Renderer=Automatic and the framework resolves that to Vulkan (which LogManagement.NormaliseFrameworkIniRendererDefault()’s XML docs state is the Android default), this will skip SetFormat() and the Vulkan path can still inherit RGB565. Consider treating Automatic as Vulkan here (or introduce a helper like LogManagement.ShouldForceRgba8888Surface() that covers both Vulkan and Automatic when Vulkan is actually in use), so Vulkan-by-automatic doesn’t regress.
| holder.SetFormat(global::Android.Graphics.Format.Rgba8888); | ||
| Logger.Log("[osu!] SurfaceHolder.SetFormat(Rgba8888) applied (Vulkan renderer).", LoggingTarget.Runtime, LogLevel.Important); | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| Debug.WriteLine($"[osu!] Failed to request RGBA8888 surface format: {e.Message}"); | ||
| } | ||
| } | ||
| catch (Exception e) | ||
| else | ||
| { | ||
| Debug.WriteLine($"[osu!] Failed to request RGBA8888 surface format: {e.Message}"); | ||
| Logger.Log("[osu!] SurfaceHolder.SetFormat skipped (OpenGL/Auto renderer — SDL3 handles format).", LoggingTarget.Runtime, LogLevel.Important); | ||
| } |
There was a problem hiding this comment.
These Logger.Log(..., LogLevel.Important) messages will be emitted on every startup (including the non-Vulkan path). Given that Important-level logs are user-visible in the in-game log overlay, this is likely to create persistent on-screen noise. Suggest lowering these to LogLevel.Verbose/Debug (or removing the non-Vulkan log entirely), keeping Important reserved for actionable warnings like the RGB565+Vulkan guard below.
ppy.osu.Framework 2026.501.2(osu-framework pack run Optimize HitCircleOverlapMarker colour updates #18, commit e72591b4, includes veldrid 4.9.18);ppy.Veldrid.SPIRVunchanged at1.0.15-gb268bf39eaosu.Game/osu.Game.csproj:ppy.osu.Framework2026.501.1→2026.501.2osu.Android.props:ppy.osu.Framework.Android2026.501.1→2026.501.2osu.iOS.props:ppy.osu.Framework.iOS2026.501.1→2026.501.2Original prompt
Problem
After PR #286 ("Android: fix Vulkan black screen by forcing RGBA8888 on the SurfaceHolder") was merged, users are now reporting an intermittent OpenGL black screen on Android: the game opens, audio plays, clicks register on UI elements, but the visible surface is fully black. The new diagnostic warning
(emitted by
osu.Framework.Android/AndroidGameSurface.cs::SurfaceDestroyed, see https://github.com/winnerspiros/osu-framework/blob/27ed3e29d8b8f84c0cd7d4f2a64e28b399446457/osu.Framework.Android/AndroidGameSurface.cs#L122) is shown in the top-right when this happens.The Vulkan renderer is also still black-screening on Adreno 740 even after PR #286 — but that part is being fixed in a companion PR against
winnerspiros/veldrid(swapchainpreTransformplumbing). This PR is scoped to the OpenGL regression only.Root cause of the OpenGL regression
PR #286 added the following block to
osu.Android/OsuGameActivity.cs(seeosu/osu.Android/OsuGameActivity.cs
Lines 237 to 289 in dc4b5d6
The intent was correct for the Vulkan path (SDL3 only calls
setFormat(RGBA8888)from its own GL-context init code, never from the Vulkan path, so Vulkan was inheriting the Android default ofRGB565). But the call is currently fired unconditionally for every renderer, including OpenGL.On the OpenGL path, SDL3's own
SDLSurfacecallback already callssetFormat(RGBA8888)from inside SDL during EGL surface creation. Our posted lambda then callssetFormat(RGBA8888)a second time, after SDL has already bound its EGL surface to the underlyingANativeWindow. Per the AndroidSurfaceHolder.setFormatcontract, any change (even to the same value, on some OEMs) triggers a surface re-create cycle:surfaceDestroyed→surfaceCreated→surfaceChanged. That cycle:drawThreadAcknowledgedTeardownwait inAndroidGameSurface.SurfaceDestroyed(because the Draw thread is mid-base.DrawFrame()and can't reachNotifyDrawThreadIdle()in time) — producing the user-visible warning.ANativeWindow. All subsequenteglSwapBufferscalls silently no-op against a destroyed surface. The Update thread, audio, input handling and Logic all continue to function — exactly matching the user's report ("I can click elements, music plays, but it's black").Fix
Gate the
holder.SetFormat(Rgba8888)call so it only runs when the configured renderer is Vulkan. SDL3 already handles the format correctly for OpenGL/GLES, so we must not re-stamp it.We already have a helper that reads
framework.inito determine the configured renderer:LogManagement.IsVulkanConfigured()(used elsewhere inOsuGameAndroid.handleVulkanProbeChanged, seeosu/osu.Android/OsuGameAndroid.cs
Line 1685 in dc4b5d6
Required code change in
osu.Android/OsuGameActivity.csReplace the unconditional
SetFormatblock with a Vulkan-gated version. Suggested implementation: