About Store Forum Documentation Contact



Post Reply 
Android Vulkan
Author Message
Fex Offline
Gold Supporter

Post: #1
Android Vulkan
[Image: android-world-1.png]

[Image: android-world-moving.png]



AI summary:

The engine now builds and runs on Android arm64, using the exact same Vulkan 1.3 backend as the PC path. There is no GLES renderer, no cut-down mobile fork, and no separate shader set. The renderer that draws your desktop frame is the renderer that draws the phone's.

It renders, takes touch input, handles rotation, survives being backgrounded, plays positional audio, loads mobile-compressed textures, and composites an HTML/CSS HUD over a streamed 3D world with a playable character.

And rather than claim "it works on Android", I ran it on a device farm. 176 device/OS combinations on real hardware. The full results — including everything that fails, and why — are in this post.



What's in it
  • Vulkan 1.3 + descriptor indexing (bindless) as the baseline — the same requirement as PC. WSI via vkCreateAndroidSurfaceKHR.
  • Touch input, including a virtual joystick (left thumb moves, right thumb turns).
  • Display rotation and resume from background.
  • Positional audio — openal-soft is now the Android sound provider, so the whole environmental audio pillar (EFX reverb, HRTF, occlusion) lights up on a phone.
  • ASTC / EAC textures — proper mobile formats, with a headless publish step: TitanEditor --publish=android.
  • RmlUi — the HTML/CSS UI stack cross-compiles for Android and is fully interactive by touch.
  • A real game demo — streamed Game::World, textured terrain, a playable character, RmlUi HUD, in landscape.

The deliverable is a shared libEsenthelProject.so loaded by a NativeActivity subclass. Editor and tutorials are gated off for Android.



The engine's render defaults are DESKTOP defaults

This was the biggest perf lesson, and it will bite anyone porting: the engine's defaults are desktop defaults, and on a phone they are ruinous. Six shadow cascades at 2048², SSAO, and an FSR-style upscaler are all perfectly sensible on a GPU with a fan.

Measured with GPU timestamps on an Adreno 740 at 2316x1080: 47 -> 72 fps, from three changes.
  • Shadows — 6 cascades @2048² down to 3 @1024². Light phase 6.8 -> 3.9 ms.
  • Ambient — SSAO down to AMBIENT_FLAT.
  • Density — 75% render scale. Opaque 4.9 -> 3.2 ms, light 3.9 -> 2.4 ms.

And one genuine trap. The default density filter is FILTER_EASU (FSR-style upscaling), and it costs more than the pixels it saves. Rendering at 75% with EASU (49 fps) was slower than rendering at 100% and not upscaling at all (55 fps). Pair any density change with FILTER_LINEAR, or you will conclude — wrongly — that resolution scaling does nothing.

I did not trust that. It was one GPU.



So I measured it on four

"EASU is a trap" is a claim about one GPU's ALU-to-bandwidth balance. Generalising that into a shipping default without checking is exactly the kind of thing that quietly embarrasses you later. So: same world, same fixed camera, same frame counts, four render presets, swept inside a single run on each device.

GPU milliseconds. "Shipped" is 75% + LINEAR. "+EASU" changes ONLY the filter, so the difference between those two columns is the cost of EASU and nothing else.

Code:
GPU           device            shipped   +EASU   native(100%)   EASU costs
-----------------------------------------------------------------------------
Adreno 740    S23 Ultra           5.04    11.32       8.95        +5.5 ms
Adreno 750    Galaxy S24          3.36     5.73       4.89        +2.5 ms
Mali-G710     Pixel 7 Pro         6.11    12.53       8.67        +6.0 ms
Mali-G68      Galaxy A54         18.97    32.95      29.79       +15.1 ms

The trap is universal, and far worse on Mali than on the Adreno where I found it. On every GPU measured, upscaling and then running EASU costs more GPU time than simply rendering every pixel. A Mali-G68 pays +15.1 ms — on a device whose entire 60 fps budget is 16.7 ms. Generalising the Adreno figure would have understated the problem by 3x.

The reassuring half: the shipped default (75% + LINEAR) is the fastest preset on all four GPUs. The setting was right. The evidence for it was thin, and now it isn't.

Read GPU milliseconds, not fps. The Adreno 750 sits at ~70 fps under every preset while its GPU time swings 3.4 -> 6.4 ms. It is panel/CPU-capped, so fps there measures the ceiling, not the render cost. Benchmarking a phone by frame rate alone measures the screen.



The device matrix — 176 combos on real hardware

Code:
Runs             94    Vulkan 1.3, bindless, 0 validation errors
Below baseline   80    Vulkan 1.1 — not ONE exposes dynamic_rendering
Fails             2    Mali-G52 MC2 — no descriptor indexing
Measured        176 / 199 catalogue combos

Phones it RUNS on — 0 validation errors

"fps" is filled in only where I ran the perf sweep; it is a property of the GPU, not the phone. "BC" is whether the GPU can read the engine's BC7 textures natively — see further down, this turns out to matter more than the failure column.

Code:
GPU                    fps   BC   Devices
------------------------------------------------------------------------
Mali-G715               —    no   Pixel 8, 8 Pro, 8a, 9, 9 Pro, 9 Pro XL,
                                  9 Pro Fold, 9a, 10a
Adreno 740              88   yes  Galaxy S23, S23+, S23 Ultra, Tab S9,
                                  Z Flip5, Z Fold5, Xperia 1 V
Adreno 750              69   yes  Galaxy S24, S24+, S24 Ultra,
                                  Z Flip6, Z Fold6
Mali-G710               53   no   Pixel 7, 7 Pro, 7a, Pixel Fold, Pixel Tablet
Mali-G68                25   no   Galaxy A16, A26, A35, A54, Tab S9 FE
Adreno 830              —    yes  Galaxy S25, S25+, S25 Edge, S25 Ultra,
                                  Z TriFold
PowerVR DXT-48-1536     —    no   Pixel 10, 10 Pro, 10 Pro XL, 10 Pro Fold
Samsung Xclipse 940     —    no   Galaxy S24, S24+, S24 FE   (Exynos variants)
Mali-G57 MC2            —    no   Galaxy A15, XCover7, moto g (2025)
Adreno 840              —    yes  Galaxy S26, S26+
Adreno 710              —    yes  Galaxy A36
Mali-G68 MC4            —    no   Galaxy A34, Lenovo Tab P12
Mali-G57                —    no   moto g04, realme C53
Samsung Xclipse 540     —    no   Galaxy A56
Adreno 735              —    yes  motorola razr plus 2024
Mali-G720 Immortalis    —    no   Galaxy Tab S10+
Samsung Xclipse 530     —    no   Galaxy A55
Samsung Xclipse 920     —    no   Galaxy S23 FE  (Exynos variant)
Mali-G78                —    no   Pixel 6

Adreno, Mali, PowerVR and Samsung Xclipse all run it. Every current flagship line — Pixel 6 through 10, Galaxy S23 through S26, the foldables — plus a decent spread of mid-range.

Phones it FAILS on

Code:
Mali-G52 MC2   Galaxy A06, moto g05   driver 49.1.0
               no descriptor indexing — 11 validation errors

Two devices, one GPU class. The engine's materials are bindless, so this is a real product decision — a non-bindless fallback path — not a bug.

Phones BELOW the baseline (Vulkan 1.1 — cannot run it)

Code:
GPU                   Devices
------------------------------------------------------------------------
Adreno 730            Galaxy S22, S22+, S22 Ultra, S23 FE, Tab S8,
                      Tab S8 Ultra, Z Flip4, Z Fold4, OnePlus 10T
Adreno 619            Galaxy A52, Tab A9+, Tab S7 FE, OnePlus Nord CE 2/3
                      Lite, Xperia 10 V, moto g34, CPH2195
Adreno 660            Galaxy S21, S21+, S21 FE, S21 Ultra
Adreno 650            Galaxy Note20, Note20 Ultra, S20 FE, S20 Ultra, Tab S7
Adreno 610            Galaxy A05s, Tab A7, Xperia 10 II, moto g(30),
                      moto g(9) play, moto g play (2021 / 2024)
Mali-G57 MC2          Galaxy A14, A15, A16, A79, moto g (2022)
Mali-G78              Galaxy S21 Ultra, Pixel 6, Pixel 6a
PowerVR Rogue GE8320  Galaxy A03s, A12, A21
Adreno 642L           Galaxy A52s, motorola edge 30
Adreno 710            Xperia 10 VI, moto g96
Mali-G52 / G52 MC2    Galaxy Tab A8, Galaxy A32, Motorola G20,
                      moto g stylus (2023)
Mali-G68              Galaxy A25, Galaxy A53
Mali-G610 MC3         motorola edge 40 neo
Mali-G77 MC9          motorola edge 40
Mali-G72              Galaxy XCover Pro
PowerVR BXM-8-256     moto g54
Adreno 620 / 630      Pixel 5, Pixel 3
Adreno 506            AQUOS sense2 SH-01L

Read that list carefully, because it is stranger than it looks: several phones appear in BOTH tables. Pixel 6. Galaxy A15, A16. Galaxy S23 FE, Galaxy S24. Same model name, opposite verdict.

There are two separate reasons for that, and they are worth keeping apart.

1. Regional silicon. Galaxy S23 FE and S24 ship a Snapdragon in some markets and an Exynos in others — Adreno 730 vs Xclipse 920, Adreno 750 vs Xclipse 940. Those are genuinely different GPUs behind one marketing name. Nothing surprising, but it does mean "does the S24 work?" has no single answer.

2. The driver. This is the real finding.

The baseline is a DRIVER threshold, not hardware

Pixel 6, Galaxy A15/A16 and the Mali-G68 A-series are not regional variants. They are the same silicon, passing or failing purely on which driver version the device shipped with:

Code:
GPU             PASSES on driver          FAILS on driver
--------------------------------------------------------------------
Adreno 710      512.800.35 / 512.800.48   512.615.0 / 512.615.88
Mali-G57 MC2    38.1.0                    32.1.0
Mali-G68        38.1.0                    32.1.0
Mali-G78        38.1.0                    32.1.0 / 38.0.0

The threshold is Mali ≥38 and Adreno ≥512.800. A Pixel 6 on Mali driver 38.1.0 runs the engine; a Pixel 6 on 32.1.0 cannot. Nothing about the phone changed but the driver it shipped with.

Below the threshold the device does not expose Vulkan 1.3 — and crucially, not one below-baseline device exposes dynamic_rendering even as an extension. So supporting them means writing a second render path, not a shim.

And it is not simply a matter of waiting for old phones to age out. I have Galaxy S22s running a current Android 16 that still ship an Adreno 730 driver reporting Vulkan 1.1. The OS is new; the GPU driver is not.

The interesting finding is not in the failure column. Look at the BC column in the first table: 60 of the 94 devices that DO run us have no BC texture support — every Mali, every PowerVR, every Xclipse. The engine's own Engine.pak still ships BC7, so on 64% of our supported devices every texture is silently expanded to RGBA at load. Several times the VRAM. Nothing looks wrong on screen. Only a capability probe can see it, and it is now the biggest open item on the Android side.

Also worth naming: a mid-range Mali-G68 (Galaxy A54) runs the demo at 25 fps even on the mobile preset. If that class of device is in scope, it needs its own settings tier.

One caveat on all of the above: this is the test farm's catalogue, not the market. It over-represents Pixels and Samsungs and whatever Google chose to stock. Read it as a driver/GPU survey, not a market-share estimate.



The farm's PASS column lies

A warning for anyone doing this: a device-farm "pass" on a game-loop test means the activity finished. Nothing more.

An app that fails to load its data pak, exits immediately, and renders absolutely nothing also "passes". That happened here, and I believed it for a while — the pak sat at a path that scoped storage made unreadable, the engine logged "Can't load Pak", quit, drew nothing, and the farm reported Passed across the board.

So every verdict in the tables above comes from the device's own logcat — a capability line the probe prints before any bail-out, so that even hardware we refuse to run on still yields a full data row instead of an empty log. That is why the below-baseline devices have driver versions at all.



Summary
  • Same Vulkan backend as PC — no GLES, no mobile fork, no separate shaders
  • 94 device/OS combos run it clean across Adreno, Mali, PowerVR and Xclipse — 0 validation errors
  • The baseline is a driver threshold, not hardware — the same GPU passes on a newer driver
  • 47 -> 72 fps from mobile render settings — and the EASU trap is now measured on four GPUs, not one
  • Touch, rotation, resume, audio, ASTC/EAC, RmlUi HUD, playable character in a streamed world
  • Five latent engine bugs found and fixed — all of which desktop had been hiding

Open: the Engine.pak BC7 transcode (64% of supported devices are silently decompressing textures), and a settings tier for mid-range Mali.


More info about the device farm, it was Google Cloud's, all of the scripts and apps needed to run the device farm tests are included in the repo:


The scripts

Code:
scripts/android_gpu_matrix.sh        one device per GPU architecture — does it render correctly?
scripts/android_perf_matrix.sh       4 render presets per device (the EASU harness) — NEW
scripts/android_survey_resume.sh     capability survey, idempotent, launches async
scripts/android_survey_collect.sh    pulls the logcats, prints the report
scripts/android_survey_report.py     summary + per-device table + survey.json
scripts/android_tutorial_sweep.sh    37 tutorials on-device with the validation layer
scripts/android_publish_pak.sh       the ASTC/EAC Project.pak
scripts/android_thirdparty_build.sh  one-time arm64 cross-build
scripts/android_game_demo.sh         build + deploy the game demo
scripts/android_fetch_validation_layer.sh

The apps they ship to the devices

AndroidProbe.cpp (capability probe), AndroidPerf.cpp (the perf sweep I added), AndroidSweep.cpp (tutorial sweep), AndroidGame.cpp (the demo) — all tracked, all with CMake targets.

in this fork:
https://github.com/DrewGilpin/EsenthelEngine
07-14-2026 05:23 PM
Find all posts by this user Quote this message in a reply
Post Reply