Skip to content

Commit 90dc644

Browse files
author
Ubuntu
committed
fix: prevent black screen by setting surface event before format change
When the surface is born as RGB565 on Vulkan, the RGB565 guard in SurfaceChanged calls SetFormat(RGBA8888) to trigger a surface recreate. Previously, this reset surfaceEvent and returned early, blocking the draw thread on surfaceEvent.Wait(5000) while the UI thread was stuck in the synchronous SetFormat call. This blocked the entire managed runtime from sending heartbeats, causing the native watchdog to fire at 10s and producing a black screen. The fix sets surfaceEvent BEFORE calling SetFormat, so the draw thread can proceed with the current (soon-to-be-recreated) surface. The new surface triggers another SurfaceChanged which sets surfaceEvent again with the correct RGBA8888 format. Fixes the issue where the APK shows a black screen after the last build.
1 parent 59300b0 commit 90dc644

1 file changed

Lines changed: 15 additions & 14 deletions

File tree

osu.Android/OsuGameActivity.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -891,9 +891,6 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
891891
setFormatPending = true;
892892
setFormatAttempts = 1;
893893

894-
// Log to Runtime so the mid-session RGB565 reset is visible in the main log
895-
// (and therefore in the notification overlay). Performance log gets the same
896-
// entry for correlation with display-mode and frame-timing data.
897894
string rgb565Message =
898895
"[osu!] Android surface pixel format RGB565 detected (Vulkan path) — " +
899896
"requesting RGBA8888 and triggering a surface recreate. " +
@@ -902,6 +899,20 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
902899
Logger.Log(rgb565Message, LoggingTarget.Runtime, LogLevel.Important);
903900
Logger.Log(rgb565Message, LoggingTarget.Performance, LogLevel.Important);
904901

902+
// Set the surface event BEFORE calling SetFormat so the draw thread can
903+
// proceed with the current (soon-to-be-recreated) surface. Without this,
904+
// the draw thread blocks on surfaceEvent.Wait(5000) while SetFormat triggers
905+
// a synchronous surface teardown on the UI thread, blocking the entire
906+
// managed runtime from sending heartbeats. The native watchdog then fires
907+
// at 10s because no managed heartbeat is observed.
908+
// The new surface will trigger another SurfaceChanged which will set
909+
// surfaceEvent again with the correct RGBA8888 format.
910+
if (width > 0 && height > 0)
911+
{
912+
surfaceEvent.Set();
913+
Debug.WriteLine($"[osu!] Native surface signal set before format change (size: {width}x{height})");
914+
}
915+
905916
try
906917
{
907918
holder.SetFormat(global::Android.Graphics.Format.Rgba8888);
@@ -911,17 +922,7 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
911922
Debug.WriteLine($"[osu!] Failed to request RGBA8888 format change for Vulkan: {e.Message}");
912923
}
913924

914-
// The SetFormat call above queues a SurfaceDestroyed→SurfaceCreated cycle.
915-
// Reset the surface event so GetSurfaceGlobalRef() does NOT unblock yet —
916-
// the current Surface handle is about to be invalidated, and any caller that
917-
// receives it would forward a dangling pointer into the Vulkan driver.
918-
// The event will be re-set when SurfaceChanged fires again for the new
919-
// RGBA8888 Surface; the normal-path surfaceEvent.Set() at the end of this
920-
// method (lines below the if/else-if guard) handles that on the next call.
921-
// We must NOT fall through to the width/height check, because that would
922-
// signal the event with the old (about-to-die) surface dimensions.
923-
surfaceEvent.Reset();
924-
Debug.WriteLine("[osu!] Native surface signal reset (RGB565→RGBA8888 format change pending)");
925+
Debug.WriteLine("[osu!] Native surface format change requested (RGB565→RGBA8888)");
925926
return;
926927
}
927928

0 commit comments

Comments
 (0)