Skip to content

Android: fix Vulkan black screen by forcing RGBA8888 on the SurfaceHolder - #286

Merged
winnerspiros merged 4 commits into
masterfrom
copilot/fix-vulkan-renderer-crash
May 1, 2026
Merged

Android: fix Vulkan black screen by forcing RGBA8888 on the SurfaceHolder#286
winnerspiros merged 4 commits into
masterfrom
copilot/fix-vulkan-renderer-crash

Conversation

Copilot AI commented May 1, 2026

Copy link
Copy Markdown
  • Investigate logs - confirmed Vulkan gets RGB565 surface format, OpenGL gets RGBA8888
  • Confirmed crash fingerprint: Toolbar load → RGB565 swapchain → native crash on Draw thread
  • Fix 1: Force RGBA8888 on Android SurfaceHolder in OsuGameActivity.cs (DecorView.Post lambda) - proactive fix before Surface creation
  • Fix 2: Add RGB565+Vulkan reactive guard in SurfaceChanged callback (diagnostic warning + format change request as belt-and-braces fallback)
  • Fix CI build error CS0234: Android.Graphics.Format was resolving to osu.Android.Graphics.Format due to namespace ambiguity — added global:: qualifier
  • Run parallel validation (passed - 0 CodeQL alerts)
Original prompt

Problem: Vulkan renderer produces black screen / crashes Draw thread on Android

When the user selects the Vulkan renderer on Android, the app opens to a black screen and the Draw thread crashes natively. On the next launch, our safe-mode fallback detects the prior native crash and forces OpenGL:

2026-05-01 12:54:31 [verbose]: [osu!] Android safe-mode renderer fallback (Draw-thread native crash detected): Renderer Vulkan → OpenGL (persisted; user can re-select Vulkan from Settings → Graphics → Renderer)

This means OpenGL works fine, but Vulkan is fully broken — which is a regression we must fix.

Evidence from the attached logs

The user provided runtime/performance/network/input logs from several launches. The relevant Vulkan launches are sessions 1777640011 and 1777640039. In both, Vulkan initializes, BASS starts, textures begin uploading… and then the log just stops mid-load (native crash, no managed exception logged):

2026-05-01 12:53:31 🖼️ Configuration renderer choice: Vulkan
2026-05-01 12:53:31 🖼️ Initialising "Veldrid" renderer with "Vulkan" surface
2026-05-01 12:53:31 Updated display mode to desktop resolution: 3088x1440@120, SDL_PIXELFORMAT_RGB565
2026-05-01 12:53:31 Android surface ready after 0 ms — drawable size 3088×1440.
2026-05-01 12:53:32 Vulkan Initialized (Adreno 740, driver 512.676.73, API 1.3.128)
...
2026-05-01 12:53:34 Loading OsuLogo...
2026-05-01 12:53:34 Texture -'s upload queue is large (100/200/300)
[ log ends — process killed ]

Suspicious clues to investigate

  1. Swapchain pixel format differs between renderers:

    • OpenGL session: SDL_PIXELFORMAT_RGBA8888 (1) → works.
    • Vulkan session: SDL_PIXELFORMAT_RGB565 → black screen + native crash.

    RGB565 swapchain on Android Vulkan is almost certainly wrong for our pipeline (we render in RGBA8 / sRGB and most of our shaders/atlases assume 8-bit-per-channel formats). This is a very strong candidate for the root cause.

  2. Reported drawable size is rotated/swapped:

    • OpenGL: 1440x3088@120 (portrait, matches device).
    • Vulkan: 3088x1440@120 ("Android surface ready after 0 ms — drawable size 3088×1440.") — landscape.

    The Vulkan surface appears to be using the un-rotated/native panel orientation, while OpenGL uses the rotated logical orientation. On Adreno + Android this typically means we are missing VK_QCOM_render_pass_transform / VK_KHR_swapchain pre-transform handling, which can result in a black frame and/or driver crash when the swapchain extent doesn't match the window.

  3. Texture upload queue grows very large right before the crash (Texture -'s upload queue is large (100/200/300)). The texture name is empty (-), which suggests a missing/broken texture-name path that may also be related (likely cosmetic, but worth noting).

  4. The performance log shows we already have Vulkan-specific affinity backoff:

    [osu!] Vulkan renderer detected from framework.ini — backing off Draw/Input big-core pinning and background-worker LITTLE-core pinning to keep Adreno/Mali driver workers schedulable.
    

    So pinning is not the cause this time — the crash happens regardless.

Additional log archive

The user also pointed at https://github.com/winnerspiros/osu/blob/master/logs.zip for the full set of logs. Try to fetch and unzip it (e.g. via curl -L of the raw URL https://raw.githubusercontent.com/winnerspiros/osu/master/logs.zip and unzip) to confirm the findings above against more sessions. If the zip cannot be fetched in the sandbox, proceed with the inline log evidence above — it is sufficient to act on.

Repos involved

The Vulkan path goes through these forks (all owned by winnerspiros):

  • winnerspiros/osu — Android host, renderer selection, safe-mode fallback, framework.ini handling.
  • winnerspiros/osu-framework — Veldrid renderer wiring, SDL3 window/surface creation, swapchain description (pixel format, color space), Android SurfaceView lifecycle.
  • winnerspiros/veldrid — Vulkan backend, swapchain creation, surface format selection, pre-transform handling.
  • winnerspiros/veldrid-spirv — shader cross-compile (less likely culprit, but check for any recent Android/Vulkan-specific changes).

The PR should be opened against winnerspiros/osu. If a fix needs to land in one of the sibling repos (most likely osu-framework and/or veldrid), bump the corresponding package/submodule reference in winnerspiros/osu and open companion PRs in those repos as well, then link them all from the winnerspiros/osu PR description.

Tasks

  1. Reproduce the failure path in code. Trace, in winnerspiros/osu-framework and winnerspiros/veldrid, how the SDL3 Android window's pixel format is chosen and how the Vulkan SwapchainDescription / VkSurfaceFormatKHR is selected. Identify why Vulkan ends up with RGB565 while OpenGL picks RGBA8888.

  2. Fix the swapchain format selection so Vulkan on Android uses...

This pull request was created from Copilot chat.

Copilot AI and others added 2 commits May 1, 2026 13:14
…een fix

The root cause of the Vulkan black-screen + Draw-thread native crash on Android is that SDL3 only calls setFormat(RGBA8888) for OpenGL, not Vulkan. Android's default SurfaceView pixel format on many high-density displays is RGB565. An RGB565 ANativeWindow causes Veldrid to negotiate a R5G6B5_UNORM Vulkan swapchain format which is incompatible with the 8-bit-per-channel rendering pipeline, producing a black screen and eventual native crash on Adreno GPUs.

Two-layer fix in OsuGameActivity:
1. Proactive: Call holder.SetFormat(RGBA8888) in the DecorView.Post lambda before AddCallback, ensuring the Surface is created with the correct format in the normal case.
2. Reactive: In SurfaceChanged, detect RGB565 + Vulkan configured → log a loud warning (LogLevel.Important) and call SetFormat(RGBA8888) to trigger a surface recreate. Veldrid's VkSurfaceKHR-loss recovery picks up the new RGBA8888 ANativeWindow on the next pass.

Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/0bf6f340-f206-4105-88e4-30e371bcfd12

Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix Vulkan renderer crash on Android Android: fix Vulkan black screen by forcing RGBA8888 on the SurfaceHolder May 1, 2026
Copilot AI requested a review from winnerspiros May 1, 2026 13:17
…GameActivity

Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/a24b8feb-25ae-4d67-afd0-495430d3e5c3

Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
Copilot AI requested a review from winnerspiros May 1, 2026 13:43
@winnerspiros
winnerspiros marked this pull request as ready for review May 1, 2026 13:59
Copilot AI review requested due to automatic review settings May 1, 2026 13:59
@winnerspiros
winnerspiros merged commit dc4b5d6 into master May 1, 2026
13 of 15 checks passed
@gitar-bot

gitar-bot Bot commented May 1, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adjusts Android SurfaceHolder configuration to prevent Vulkan sessions from receiving an RGB565 surface (leading to black-screen / native Draw-thread crashes), aligning Vulkan’s surface format behavior with the known-good OpenGL path.

Changes:

  • Proactively requests RGBA8888 via SurfaceHolder.SetFormat() before registering the surface callback.
  • Adds a reactive SurfaceChanged guard to detect RGB565 (when Vulkan is configured) and request a format change to RGBA8888, with diagnostic logging.
  • Resolves Android.Graphics.Format namespace ambiguity via global:: qualification.
Comments suppressed due to low confidence (1)

osu.Android/OsuGameActivity.cs:656

  • After calling holder.SetFormat(Rgba8888) above, this method still proceeds to set surfaceEvent when width/height are valid. If SetFormat triggers a surface recreate (as the comment states), signalling readiness here can wake the SDL/Veldrid side with the old RGB565 surface/global-ref just before SurfaceDestroyed runs, undermining the fallback and potentially reintroducing the crash. When requesting a format change, reset the readiness signal and return early so only the subsequent SurfaceChanged for the corrected surface sets surfaceEvent.
            if (width > 0 && height > 0)
            {
                surfaceEvent.Set();
                Debug.WriteLine($"[osu!] Native surface signal set (size: {width}x{height})");
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +632 to +633
if (format == global::Android.Graphics.Format.Rgb565 && LogManagement.IsVulkanConfigured())
{
winnerspiros added a commit that referenced this pull request May 1, 2026
…reen-issue

Android: fix OpenGL black screen regression from PR #286
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants