bevyengine / bevyengine/bevy

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

Open
#24,926 1 comment 0 reactions 0 assignees View on GitHub
A-Animation A-Rendering C-Bug O-Android S-Needs-Testing
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## 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:

```text
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:

```rust
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)](https://github.com/user-attachments/files/29846990/bevy_android_skinning_repro.zip)

Android reproduction steps:

```powershell
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:

```rust
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.

Contributor guide

Open the contributing guide

Research direction

Start with bevy_pbr/src/render/skin.rs and the included bevy_pbr_android_uniform_skinning.patch; reproduce the failure with the attached Android project using the storage-buffer path, then compare it with the uniform-buffer result. Trace the joint-matrix storage-buffer data through the Android/Vulkan run, and consider the issue resolved when the same skinned GLB renders correctly on the reported device without corruption.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, rust
Domain
computer-graphics, mobile-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.