Skip to content

Skinned GLB corrupted on Android Vulkan / Adreno when using storage-buffer skinning path #24926

Description

@Toikron

Bevy version and features

  • Bevy 0.19.0 and bevy 0.20 dev
  • wgpu 29.0.3 (from Cargo.lock)
  • rustc 1.96.0 / cargo 1.96.0
  • default-features = false with the rendering, PBR, glTF, animation, windowing, asset and Android native activity features enabled. The attached minimal repro contains the exact feature list.
  • Also reproduced on Bevy main (0.20.0-dev, commit 49642fb5b247ccd9d0eeb982a29fabdbaff7b7cb, tested 2026-07-09).

Relevant system information

  • Android 13 / API 33, arm64

  • Device: ZTE A2023P

  • SoC: Snapdragon 8 Gen 1

  • GPU: Adreno (TM) 730

  • Backend: Vulkan through wgpu-hal

  • Adreno Vulkan driver: 0615.0 (build date 2022-07-19)

  • The GLB was exported from Mixamo

Relevant adapter/driver output:

AdapterInfo { name: "Adreno (TM) 730", driver: "Qualcomm Technologies Inc. Adreno Vulkan Driver", backend: Vulkan, ... }
Driver Path      : /vendor/lib64/hw/vulkan.adreno.so
Driver Version   : 0615.0
Application Name : wgpu
Engine Name      : wgpu-hal

What you did

I reduced this from a game to a small Bevy app with no custom shaders or game systems. It loads one skinned GLB scene, creates an animation graph from the first clip, and plays it repeatedly:

commands.spawn((
    WorldAssetRoot(asset_server.load(
        GltfAssetLabel::Scene(0).from_asset("running.glb"),
    )),
    Transform::from_scale(Vec3::splat(1.0)),
));

let clip = asset_server.load(
    GltfAssetLabel::Animation(0).from_asset("running.glb"),
);
let (graph, node) = AnimationGraph::from_clip(clip);

The same GLB renders and animates correctly in a desktop build.

Reproduction project: [bevy_android_skinning_repro.zip](https://github.com/user-attachments/files/29846990/bevy_android_skinning_repro.zip)

Android reproduction steps:

rustup target add aarch64-linux-android
cargo apk build --release --lib --target aarch64-linux-android
adb install -r target\release\apk\bevy-android-skinning-repro.apk
adb shell am start -n com.neonoverkill.skinrepro/android.app.NativeActivity

So far I have reproduced this on one Android device: ZTE A2023P / Snapdragon 8 Gen 1 / Adreno 730.

What went wrong

Expected: the Android/Vulkan output should match the desktop output for the same skinned GLB.

Actual: the character is corrupted into large stretched fragments. The fragments move between frames, so the animation is advancing, but the skinned vertex output is wrong. The material colors are present on the fragments.

Static GLBs render correctly on the same device. Baked non-skinned frames generated from this character also render correctly, which made this look specific to the skinned mesh render path rather than asset packaging or general GLB loading.

Broken Android screenshot:

Broken Android Vulkan skinning output

Additional information

I tested a local change in bevy_pbr/src/render/skin.rs that forces Android to use the uniform-buffer skinning path:

pub fn skins_use_uniform_buffers(limits: &WgpuLimits) -> bool {
    cfg!(target_os = "android")
        || bevy_render::storage_buffers_are_unsupported(limits)
}

With that change, the same APK, GLB and phone render the complete character correctly and the animation advances normally. I also tested five separate instances of the same animated scene at once; all five rendered correctly with the uniform-buffer path.

I repeated the test against the current Bevy main commit listed above. The unmodified storage-buffer build still failed: the character was not visible apart from a few corrupted pixels near the bottom of the screen. Rebuilding that same commit after applying the uniform-buffer change restored the complete animated character. So the issue is still present on current main; it is not specific to the 0.19.0 release.

Patched screenshots:

Patched Android uniform-buffer skinning screenshot 1 Patched Android uniform-buffer skinning screenshot 2 Patched Android uniform-buffer skinning screenshot 3

The minimal repro defaults to unmodified Bevy 0.19.0, so it shows the failure. The small Bevy patch is included separately as bevy_pbr_android_uniform_skinning.patch.

My best guess is that the storage-buffer skinning path is producing or reading incorrect joint matrix data on this Android/Vulkan/Adreno combination. I am not sure yet whether the underlying fault is in Bevy's shader/buffer handling, wgpu, or the Adreno driver, but selecting Bevy's uniform-buffer path avoids the corruption consistently on this device.

I do not think forcing uniform buffers on all Android devices is necessarily the right fix; I included it only because it isolates the failing path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-BugAn unexpected or incorrect behaviorS-Needs-TriageThis issue needs to be labelled

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions