Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 19 additions & 0 deletions osu.Android/LogManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,25 @@ public static void NormaliseFrameworkIniRendererDefault()
string sentinelPath = Path.Combine(root, renderer_migration_sentinel);
if (File.Exists(sentinelPath)) return;

// On fresh installs (no framework.ini yet), default to OpenGL instead of Vulkan.
// Vulkan causes black screens on several Adreno GPU families (7xx series in
// particular) because the Veldrid Vulkan backend either times out its 5s
// SurfaceHandle poll or hands a stale ANativeWindow to vkCreateAndroidSurfaceKHR.
// OpenGL ES is the safer default; users can switch to Vulkan in Settings.
string iniPath = Path.Combine(root, "framework.ini");
if (!File.Exists(iniPath))
{
try
{
File.WriteAllText(iniPath, "Renderer = OpenGL" + Environment.NewLine);
Debug.WriteLine("[osu!] Fresh install — defaulted renderer to OpenGL.");
}
catch (Exception e)
{
Debug.WriteLine($"[osu!] Could not write fresh-install renderer default: {e.Message}");
}
}

tryDropSentinel(sentinelPath);
}
catch (Exception e)
Expand Down
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