Android: fix Vulkan black screen by forcing RGBA8888 on the SurfaceHolder - #286
Merged
Conversation
…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>
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
winnerspiros
approved these changes
May 1, 2026
…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>
winnerspiros
approved these changes
May 1, 2026
There was a problem hiding this comment.
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
RGBA8888viaSurfaceHolder.SetFormat()before registering the surface callback. - Adds a reactive
SurfaceChangedguard to detectRGB565(when Vulkan is configured) and request a format change toRGBA8888, with diagnostic logging. - Resolves
Android.Graphics.Formatnamespace ambiguity viaglobal::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()) | ||
| { |
4 tasks
winnerspiros
added a commit
that referenced
this pull request
May 1, 2026
…reen-issue Android: fix OpenGL black screen regression from PR #286
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Android.Graphics.Formatwas resolving toosu.Android.Graphics.Formatdue to namespace ambiguity — addedglobal::qualifierOriginal 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:
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
1777640011and1777640039. In both, Vulkan initializes, BASS starts, textures begin uploading… and then the log just stops mid-load (native crash, no managed exception logged):Suspicious clues to investigate
Swapchain pixel format differs between renderers:
SDL_PIXELFORMAT_RGBA8888 (1)→ works.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.
Reported drawable size is rotated/swapped:
1440x3088@120(portrait, matches device).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_swapchainpre-transform handling, which can result in a black frame and/or driver crash when the swapchain extent doesn't match the window.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).The performance log shows we already have Vulkan-specific affinity backoff:
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.zipfor the full set of logs. Try to fetch and unzip it (e.g. viacurl -Lof the raw URLhttps://raw.githubusercontent.com/winnerspiros/osu/master/logs.zipandunzip) 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), AndroidSurfaceViewlifecycle.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 likelyosu-frameworkand/orveldrid), bump the corresponding package/submodule reference inwinnerspiros/osuand open companion PRs in those repos as well, then link them all from thewinnerspiros/osuPR description.Tasks
Reproduce the failure path in code. Trace, in
winnerspiros/osu-frameworkandwinnerspiros/veldrid, how the SDL3 Android window's pixel format is chosen and how the VulkanSwapchainDescription/VkSurfaceFormatKHRis selected. Identify why Vulkan ends up withRGB565while OpenGL picksRGBA8888.Fix the swapchain format selection so Vulkan on Android uses...
This pull request was created from Copilot chat.