Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions osu.Android/OsuGameActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using osu.Framework.Android;
using osu.Game.Database;
using osu.Framework.Logging;
using osu.Android.Native;

namespace osu.Android
{
Expand Down Expand Up @@ -891,9 +892,6 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
setFormatPending = true;
setFormatAttempts = 1;

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

// Tick the native watchdog BEFORE calling SetFormat. SetFormat triggers a
// synchronous surface teardown on the UI thread that can block for hundreds
// of milliseconds. During this window the Update thread may not get a chance
// to send a heartbeat (especially during startup when the runtime is under
// heavy load), and the native watchdog (10s default) would fire, killing the
// process and producing a black screen.
//
// Calling Heartbeat() here resets the watchdog timer from the UI thread,
// giving SetFormat time to complete without triggering a false positive.
// The native watchdog checks g_lastHeartbeatMonotonicSec which is updated
// by a simple atomic store — safe to call from any thread.
try
{
NativeWatchdog.Heartbeat();
Debug.WriteLine("[osu!] Native watchdog heartbeat ticked before SetFormat(RGBA8888)");
}
catch (Exception e)
{
Debug.WriteLine($"[osu!] NativeWatchdog.Heartbeat failed (non-fatal): {e.Message}");
}

try
{
holder.SetFormat(global::Android.Graphics.Format.Rgba8888);
Expand All @@ -911,17 +930,13 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
Debug.WriteLine($"[osu!] Failed to request RGBA8888 format change for Vulkan: {e.Message}");
}

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

// Reset the surface event so GetSurfaceGlobalRef() does NOT unblock with the
// old (about-to-be-invalidated) surface handle. The event will be re-set when
// SurfaceChanged fires again for the new RGBA8888 surface.
surfaceEvent.Reset();
Debug.WriteLine("[osu!] Native surface signal reset (RGB565→RGBA8888 format change pending)");

return;
}

Expand Down
Loading