From 1a407df9e5ecaab5cea63d55cb675475b31398a9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 19 May 2026 20:50:21 +0000
Subject: [PATCH 1/7] fix: Argon skin corruption + ppy cherry-pick + surface
format + safe-mode shader wipe
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Changes:
- Cherry-pick ppy editor toolbox icons commit: new OsuIcon entries (EditorHit,
EditorDrumRoll, EditorSwell, EditorFruit, EditorJuiceStream, EditorBananaShower,
EditorNote, EditorHoldNote) and all 8 composition tools updated to use SpriteIcon
instead of BeatmapStatisticIcon. Bump osu-resources to 2026.519.0.
- Fix Argon skin visual corruption (white circles, off-hue TrianglesV2 on buttons):
Force-wipe the shader pipeline cache in ForceOpenGLRendererIfSafeMode(). When a
Vulkan session dies mid-compile (ANR), the shader cache sentinel is already marked
'done for v241' but partial/misaligned SPIR-V blobs remain on disk. The rescue
OpenGL session then picks up these stale shaders which were compiled against the
old GlobalUniformData layout (pre-UniformPadding12 alignment fix). Result: the
masking uniform (CornerRadius clip) and gradient colour uniform are read from wrong
struct offsets → Argon hit circles appear as white squares, button TrianglesV2
shows wrong hues. Wiping unconditionally on every safe-mode entry fixes this.
- Make pre-SDL Window.SetFormat(RGBA8888) unconditional (remove IsVulkanConfigured
guard). Ensures 32-bit colour in both Vulkan and OpenGL modes; prevents an RGB565
initial surface allocation that causes colour-channel artefacts in early frames of
the OpenGL rescue session.
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/aff86d54-a6b0-494c-b672-682c2bfbd9ff
Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
---
osu.Android/LogManagement.cs | 38 ++++++++++++++++++
osu.Android/OsuGameActivity.cs | 40 ++++++++++---------
.../Edit/BananaShowerCompositionTool.cs | 5 ++-
.../Edit/FruitCompositionTool.cs | 5 ++-
.../Edit/JuiceStreamCompositionTool.cs | 5 ++-
.../Edit/HoldNoteCompositionTool.cs | 5 ++-
.../Edit/NoteCompositionTool.cs | 5 ++-
.../Edit/DrumRollCompositionTool.cs | 5 ++-
.../Edit/HitCompositionTool.cs | 5 ++-
.../Edit/SwellCompositionTool.cs | 5 ++-
osu.Game/Graphics/OsuIcon.cs | 32 +++++++++++++++
osu.Game/osu.Game.csproj | 2 +-
12 files changed, 117 insertions(+), 35 deletions(-)
diff --git a/osu.Android/LogManagement.cs b/osu.Android/LogManagement.cs
index 5ce4a7089a74..c93986f0eec4 100644
--- a/osu.Android/LogManagement.cs
+++ b/osu.Android/LogManagement.cs
@@ -481,6 +481,44 @@ public static void ForceOpenGLRendererIfSafeMode()
string? root = resolveStorageRoot();
if (root == null) return;
+ // The previous launch died (Vulkan ANR or native crash) before the
+ // shader compilation burst finished. The on-disk pipeline cache can
+ // contain:
+ // • SPIR-V blobs compiled against the old GlobalUniformData layout
+ // (before the UniformPadding12 alignment fix in 2026.519.1) if
+ // the WipeShaderCacheOnceForVersion sentinel was already written
+ // but the Vulkan session was killed mid-compile.
+ // • Partially-written or incomplete pipeline objects from the
+ // interrupted Vulkan compile pass.
+ //
+ // Either case causes visual corruption on the rescue OpenGL session:
+ // – Argon hit circles render as white rectangles (masking uniform
+ // at wrong struct offset → CornerRadius clipping broken).
+ // – TrianglesV2 buttons show the wrong hue (gradient colour data
+ // at wrong offset → DrawColourInfo.Colour.Interpolate returns
+ // garbage channel values).
+ //
+ // Wipe the shader cache unconditionally here — bypassing the
+ // version-code sentinel — so the OpenGL rescue session always starts
+ // from a clean slate. The sentinel is NOT reset: the next normal
+ // (non-safe-mode) launch will still skip the version wipe and reuse
+ // the freshly-compiled OpenGL cache from this rescue session.
+ string shaderCacheDir = Path.Combine(root, "cache", "shaders");
+
+ if (Directory.Exists(shaderCacheDir))
+ {
+ try
+ {
+ Directory.Delete(shaderCacheDir, recursive: true);
+ Logger.Log("[osu!] Android safe-mode: shader cache wiped to ensure clean OpenGL recompilation.", LoggingTarget.Runtime);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine($"[osu!] LogManagement: safe-mode shader cache wipe failed ({e.Message}); falling back to per-entry sweep");
+ sweepDirectoryBestEffort(shaderCacheDir);
+ }
+ }
+
string iniPath = Path.Combine(root, "framework.ini");
if (!File.Exists(iniPath))
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index 96b61fda9042..fe871beae404 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -246,25 +246,29 @@ protected override void OnCreate(Bundle? savedInstanceState)
// Stamp RGBA8888 at the Window level BEFORE SDL creates its SurfaceView inside
// base.OnCreate(). Android's default SurfaceView pixel format on many high-density
- // Samsung / Qualcomm panels is RGB565. SDL3 only calls SurfaceHolder.setFormat(
- // RGBA8888) for the OpenGL path — the Vulkan path inherits the window default.
- // Setting the format here, before SDL attaches its SurfaceView, ensures the
- // SurfaceView is born with RGBA8888 and eliminates the format-change teardown
- // (SurfaceHolder.SetFormat in DecorView.Post) that otherwise fires mid-Vulkan-init
- // and can produce the "Draw thread did not acknowledge teardown within 250ms" warning.
- // The DecorView.Post call and the SurfaceChanged reactive guard are retained as
- // belt-and-braces fallbacks for timing windows or OEM variants where this hint is
- // not honoured by the SurfaceView allocation path.
- if (LogManagement.IsVulkanConfigured())
+ // Samsung / Qualcomm panels is RGB565. Setting RGBA8888 here (before SDL attaches
+ // its SurfaceView) ensures the SurfaceView is born with full 32-bit colour in both
+ // Vulkan and OpenGL modes:
+ // - Vulkan: the Veldrid swapchain can request VK_FORMAT_R8G8B8A8_SRGB / BGRA8888
+ // directly, but the underlying ANativeWindow must also support RGBA8888 — a
+ // Window born at RGB565 forces a surface teardown (and the
+ // "Draw thread did not acknowledge teardown within 250ms" warning) when Veldrid
+ // later calls ANativeWindow_setBuffersGeometry with RGBA8888.
+ // - OpenGL safe-mode (after a Vulkan crash): SDL3 does call
+ // SurfaceHolder.setFormat(RGBA8888) for EGL surfaces, but it only does so AFTER
+ // the SurfaceView is created. Pre-stamping the Window format here guarantees
+ // the initial SurfaceView allocation happens at RGBA8888, avoiding a brief
+ // RGB565 render pass that can leave colour-channel artefacts visible in the
+ // first few frames.
+ // Belt-and-braces fallbacks (DecorView.Post watcher, SurfaceChanged reactive guard)
+ // are retained for OEM variants where this Window-level hint is not honoured.
+ try
{
- try
- {
- Window?.SetFormat(global::Android.Graphics.Format.Rgba8888);
- }
- catch (Exception e)
- {
- Debug.WriteLine($"[osu!] Pre-SDL Window.SetFormat(RGBA8888) failed (non-fatal): {e.Message}");
- }
+ Window?.SetFormat(global::Android.Graphics.Format.Rgba8888);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine($"[osu!] Pre-SDL Window.SetFormat(RGBA8888) failed (non-fatal): {e.Message}");
}
// BASS AAudio: if the user opted in, tell BASS to open an AAudio device instead
diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
index be93ba0242ed..3ef47c92c42b 100644
--- a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
@@ -17,7 +18,7 @@ public BananaShowerCompositionTool()
{
}
- public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorBananaShower };
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
}
diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
index 71c1e6690320..0ec79ef6ad7a 100644
--- a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
@@ -17,7 +18,7 @@ public FruitCompositionTool()
{
}
- public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorFruit };
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
}
diff --git a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
index 7a567820f370..7a4bfeee8200 100644
--- a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
@@ -17,7 +18,7 @@ public JuiceStreamCompositionTool()
{
}
- public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorJuiceStream };
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
}
diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
index 592f8d9af7c9..6c433f0b16d5 100644
--- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
+++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
@@ -16,7 +17,7 @@ public HoldNoteCompositionTool()
{
}
- public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorHoldNote };
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
}
diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs
index 2e54d635251c..5c2f8c8fcfda 100644
--- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs
+++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
@@ -17,7 +18,7 @@ public NoteCompositionTool()
{
}
- public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorNote };
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
index ba0fda67712b..5c7ee13e1a1a 100644
--- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
@@ -17,7 +18,7 @@ public DrumRollCompositionTool()
{
}
- public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorDrumRoll };
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
index f58defba83e8..4eb00988091d 100644
--- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
@@ -17,7 +18,7 @@ public HitCompositionTool()
{
}
- public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorHit };
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
index 4ec623e29ef1..67e0987abe1f 100644
--- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
@@ -17,7 +18,7 @@ public SwellCompositionTool()
{
}
- public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSwell };
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
}
diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs
index 1796372bb3ca..acebccc94610 100644
--- a/osu.Game/Graphics/OsuIcon.cs
+++ b/osu.Game/Graphics/OsuIcon.cs
@@ -93,6 +93,14 @@ public static class OsuIcon
public static IconUsage EditorHitCircle => get(OsuIconMapping.EditorHitCircle);
public static IconUsage EditorSlider => get(OsuIconMapping.EditorSlider);
public static IconUsage EditorSpinner => get(OsuIconMapping.EditorSpinner);
+ public static IconUsage EditorHit => get(OsuIconMapping.EditorHit);
+ public static IconUsage EditorDrumRoll => get(OsuIconMapping.EditorDrumRoll);
+ public static IconUsage EditorSwell => get(OsuIconMapping.EditorSwell);
+ public static IconUsage EditorFruit => get(OsuIconMapping.EditorFruit);
+ public static IconUsage EditorJuiceStream => get(OsuIconMapping.EditorJuiceStream);
+ public static IconUsage EditorNote => get(OsuIconMapping.EditorNote);
+ public static IconUsage EditorHoldNote => get(OsuIconMapping.EditorHoldNote);
+ public static IconUsage EditorBananaShower => get(OsuIconMapping.EditorBananaShower);
public static IconUsage EditorGrid => get(OsuIconMapping.EditorGrid);
public static IconUsage EditorAddControlPoint => get(OsuIconMapping.EditorAddControlPoint);
public static IconUsage EditorConvertToStream => get(OsuIconMapping.EditorConvertToStream);
@@ -409,6 +417,30 @@ private enum OsuIconMapping
[Description(@"Editor/spinner")]
EditorSpinner,
+ [Description(@"Editor/hit")]
+ EditorHit,
+
+ [Description(@"Editor/drum-roll")]
+ EditorDrumRoll,
+
+ [Description(@"Editor/swell")]
+ EditorSwell,
+
+ [Description(@"Editor/fruit")]
+ EditorFruit,
+
+ [Description(@"Editor/juice-stream")]
+ EditorJuiceStream,
+
+ [Description(@"Editor/banana-shower")]
+ EditorBananaShower,
+
+ [Description(@"Editor/note")]
+ EditorNote,
+
+ [Description(@"Editor/hold-note")]
+ EditorHoldNote,
+
[Description(@"Editor/grid")]
EditorGrid,
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 30a42f4732c4..f6c5d087d4f6 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -49,7 +49,7 @@
would silently fall back to the 4 KB-aligned nuget.org build and emit NU1903.
-->
-
+
From 22d4059563a0910287f71e02a178c8596c31391a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 19 May 2026 21:40:35 +0000
Subject: [PATCH 2/7] fix(Android): OnPause watchdog to prevent Vulkan
IMMEDIATE-mode ANR
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/e20f5f4d-6cae-4655-808b-9f6bda1c985e
Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
---
osu.Android/OsuGameActivity.cs | 69 ++++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index fe871beae404..6965f34bcd7a 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -967,6 +967,75 @@ public void SurfaceDestroyed(ISurfaceHolder holder)
}
}
+ protected override void OnPause()
+ {
+ // Root cause of the recurring Vulkan IMMEDIATE-mode ANR (process-runtime ~50s):
+ //
+ // 1. Samsung Game Booster (or any surface-lifecycle event) fires onPause() on the
+ // Java main thread at ~50 seconds of active Vulkan gameplay.
+ // 2. SDL3's native onPause() sends SDL_EVENT_DID_ENTER_BACKGROUND through its event
+ // filter synchronously on the calling (Java main) thread.
+ // 3. The event filter calls Window.Suspended → GameHost.Suspend() →
+ // ThreadRunner.Suspend() → DrawThread.Pause() → WaitForState(Paused).
+ // 4. WaitForState spins: `while (state != Paused) Thread.Sleep(1)` — NO TIMEOUT.
+ // 5. The draw thread is stuck inside vkQueuePresentKHR (Vulkan IMMEDIATE mode;
+ // FrameSync=ActualUnlimited) due to an Adreno 7xx driver stall. It can only
+ // check pauseRequested at the START of the next frame — which never comes.
+ // 6. Java main thread spins forever → input dispatching times out after 10s → ANR.
+ //
+ // Fix: watchdog the OnPause() call. If base.OnPause() hasn't returned within 7 seconds
+ // (leaving a 3-second margin before the 10-second ANR), the draw thread is conclusively
+ // stuck in the driver. Kill the process immediately for a clean restart rather than a
+ // frozen 10-second ANR.
+ //
+ // We intentionally do NOT set FLAG_STARTUP_IN_PROGRESS (safe-mode) before killing.
+ // The startup completed successfully; this is a mid-session driver hang triggered by
+ // a transient system event (Game Booster first-session overlay). The next launch will
+ // retry Vulkan normally. Safe-mode is reserved for launch-time hangs where the renderer
+ // itself cannot initialize.
+ //
+ // Only active for Vulkan: OpenGL's eglSwapBuffers cannot stall indefinitely in the way
+ // vkQueuePresentKHR can, so OpenGL sessions are not at risk of this ANR pattern.
+ if (LogManagement.IsVulkanConfigured())
+ {
+ var pauseCompleted = new ManualResetEventSlim(false);
+
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ const int watchdog_ms = 7000;
+
+ if (pauseCompleted.Wait(watchdog_ms))
+ return;
+
+ // base.OnPause() has not returned — draw thread is conclusively stuck in
+ // vkQueuePresentKHR. Write a diagnostic marker and kill cleanly.
+ try
+ {
+ CrashDiagnostics.WriteAliveMarker(
+ $"OnPause watchdog fired after {watchdog_ms}ms: draw thread stuck in vkQueuePresentKHR (Vulkan IMMEDIATE ANR). Killing for clean restart.");
+ }
+ catch { }
+
+ try
+ {
+ Debug.WriteLine(
+ "[osu!] OnPause watchdog: draw thread stuck in vkQueuePresentKHR >7s — killing for clean Vulkan restart.");
+ }
+ catch { }
+
+ try { global::Android.OS.Process.KillProcess(global::Android.OS.Process.MyPid()); }
+ catch { }
+ });
+
+ base.OnPause();
+ pauseCompleted.Set();
+ }
+ else
+ {
+ base.OnPause();
+ }
+ }
+
public override void OnConfigurationChanged(global::Android.Content.Res.Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
From 5b3f5c62eeb023f9398b7ef0f8ef9eac3da83af6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 20 May 2026 04:44:20 +0000
Subject: [PATCH 3/7] chore: bump osu-framework to 2026.520.1
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/ad643f8c-cd6e-4da3-b81a-5821e16c4552
Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
---
osu.Android.props | 2 +-
osu.Game/osu.Game.csproj | 4 ++--
osu.iOS.props | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/osu.Android.props b/osu.Android.props
index bad6fd6c2ba8..6b408a828bed 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+