Skip to content

Android (Adreno 830): changing aspect ratio or resolution crashes the game #72

Closed

Description

@Blivii

What happens

Hi, all! Big fan of the project so far. Just putting this up front since everything I've done trying to fix this issue has been through Opus 5.5/Sol 5.6, and everything in the hidden section below is fully generated. I definitely didn't figure any of this out myself, so any technical bits up here are in quotes to show they come from Claude, not me.

TL;DR: The app crashes for me on Android if I change the aspect ratio or resolution options. The changed code implemented in the attached .patch and described below fixes the issue for me.

My first fix (with GPT) just "skipped copying the old depth buffer when the phone couldn't blit it, and threw the old contents away." That stopped the crash and has been working fine for me, but Claude pointed out it only really works when the game "clears that buffer right afterwards. If a resize ever lands mid-frame, you could get a frame of depth glitches."

The patch below is the more proper version according to Claude, which I implemented tonight: "Instead of skipping the copy, it does the copy with a small draw, so the depth (and stencil) contents carry over to the new size. Devices that can already blit keep using the blit, so desktop shouldn't change at all."

depth-resize-without-blits.patch

It's a .patch file instead of a PR since I didn't want to go through forking and committing, and it applies cleanly with git apply on current main (9ca2a76). I've tested it on my Fold 7, and Claude also ran it through "the renderer smoke test." Generated details are below if you want them. Happy to provide any logs if needed or test other changes on Android.

Technical details: cause, fix, and testing (generated by Claude)

What happens

On a Galaxy Z Fold 7 (Snapdragon 8 Elite, Adreno 830), changing the aspect ratio or the internal resolution while playing aborts the game. Reproduces on current main (9ca2a76, v0.2.7).

Cause

After a scale or aspect change, rescale() in runtime/src/gfx/vulkan/surfaces.cpp makes a replacement image and copies the old contents into it with resample(), which uses vkCmdBlitImage. resample() checks BLIT_SRC/BLIT_DST and throws Vulkan device cannot blit this surface format when they're missing. The exception isn't caught on the render thread, so the process aborts.

Vulkan doesn't require blit support for any depth/stencil format (BLIT_DST is optional for all of them), and Adreno doesn't report it for some. Desktop drivers and MoltenVK happen to support it, which is why this only shows up on Android. Logging on the Fold 7 shows the formats affected there:

  • VK_FORMAT_D32_SFLOAT (126): hit on the first aspect change
  • VK_FORMAT_D16_UNORM (124): hit on the first resolution change

The scaled-copy branch of copy_surface_impl() (GX2CopySurface) has the same check and would abort the same way for a scaled depth copy.

Fix (attached patch)

When a depth format can't be blitted, the scaled copy is drawn instead:

  • A fullscreen triangle per layer. Each pixel texelFetches the source texel a VK_FILTER_NEAREST blit would pick (the spec's (x+0.5)·src/dst mapping) and writes it with gl_FragDepth.
  • Stencil (D32F+S8) is copied one bit per pass: the depth pass zeroes stencil, then 8 passes each write one bit with discard where the source bit is clear. No VK_EXT_shader_stencil_export needed.
  • Used by both resample() (so rescale() and read_guest_texels()) and the scaled GX2CopySurface path.
  • Devices that can blit keep the blit, so desktop and macOS behavior is unchanged. The first time a format takes the draw path, one log line names it.
  • WWHD_VK_DEPTH_COPY=draw forces the draw path on any device, for testing.
  • Adds vkCmdSetStencilWriteMask (Vulkan 1.0 core) to loader.h.

Old depth contents survive the resize, so a rescale landing mid-frame doesn't leave a frame depth-testing against garbage. Skipping the copy for unblittable depth formats would avoid the crash too, but only works when the game clears the buffer right after.

Testing

Renderer smoke test (Linux, lavapipe, WWHD_VK_VALIDATION=1): new depth_copy_check(), with the draw path forced:

  • D16, D32F and D32F+S8, 2-layer arrays, scaled up (40×24 → 64×37), down (→ 17×11) and same size. Every depth and stencil texel is compared against the nearest-filter mapping.
  • A partial region from part of the source (uMax/vMax < 1). Pixels outside the region keep their earlier clear values.
  • The real resize path: a screen-shaped D32F+S8 target through a 21:9 aspect change (64×36 → 84×36), then 2× resolution (→ 168×72), then back, checked after each step.
  • Full smoke test PASS, zero validation messages. The check fails as expected when the depth lookup is shifted one texel or a stencil bit pass is skipped.

On the Fold 7: aspect ratio Match window (1.11), 16:10, 21:9, 32:9, 16:9, then internal resolution 1.5×, 2×, 3×, 1×, all switched live with no crash.

Steps to reproduce

  1. Run the latest version of the game compiled for Android.

  2. Load the game and attempt to change either the resolution or aspect ratio. The app immediately crashes.

Save state (link), or why you can't add one

Not sure where to find this on Android, but the issue is very easily reproducible.

Version

main (9ca2a76, v0.2.7).

Platform

Android

Hardware

Galaxy Fold 7, Snapdragon 8 Elite, Adreno 830

Renderer

Vulkan

Logs

Can get some runs in on main and get logs for the crashes if needed.

Activity

  1. lschaupp commented on Oct 8, 2026

    @lschaupp
    Contributor

    Hi @Blivii, thanks a lot for this, and for being upfront about how it was made. The analysis was spot on: Vulkan doesn't require blit support for depth formats, Adreno doesn't offer it for D16/D32F, and the exception wasn't caught on the render thread.

    We took your patch as the base (it's in unchanged, with you as co-author) and added a follow-up commit:

    • The draw path is now only chosen after checking that the format can actually be sampled and rendered into on that device, instead of assuming it.
    • Same-size copies without blit use a plain image copy (exact, and always supported) instead of a draw.
    • Nothing aborts any more. If a device can neither blit nor draw a format, a full resize clears the target (the game redraws it the next frame) and a scaled GX2CopySurface is skipped, each with one log line, instead of throwing. That also covers the colour-format and same-image cases your version would still have thrown on.
    • Your WWHD_VK_DEPTH_COPY=draw switch stays, plus =none to test that last resort. Both are documented in docs/vulkan.md, and the smoke test checks the fallback as well.

    On desktop/MoltenVK we forced the draw path and switched resolution and aspect ratio live several times mid-game with validation on. No crashes, no new validation messages, and the pictures match the blit path. Your smoke checks pass texel for texel.

    This will be in the next release (v0.2.8). Could you try it on your Fold 7, cycling through the aspect ratios and 1.5×/2×/3×/1× as before? A log would be great too: you should see one line per depth format saying it's drawn rather than blitted. Thanks again!

  2. lschaupp commented on Oct 8, 2026

    @lschaupp
    Contributor

    @Blivii this is now in v0.2.8 (and on main), with you credited in the release notes. For Android, build v0.2.8 the same way you built your patched version (Android (build it yourself)).

    I'm closing this as fixed, since your own patch already worked on the Fold 7 and the release keeps that path. If v0.2.8 still crashes when you change the aspect ratio or resolution, please comment here with a log and we'll reopen it right away. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions