Raju Shingadiya Raju Shingadiya
All posts

Profiling Android GPU work with AGI (Android GPU Inspector)

CPU profilers kept telling me everything was fine. The phone kept telling me otherwise. This is the tool that finally showed me what the GPU was doing.

Hands holding an Android phone running a racing game
Photo by Amanz on Unsplash

A few months back I was helping a team with a 3D-heavy Android app. On a Pixel it ran fine. On a mid-range Adreno device it hovered around 40fps and stuttered every couple of seconds. Systrace showed a main thread that was mostly idle. Android Studio's profiler showed CPU usage nowhere near the ceiling. Everything looked healthy, and the app still felt bad.

The problem, obviously in hindsight, was that I was looking at the wrong processor. All my tools were showing me CPU work, and the bottleneck was on the GPU. That's the gap Android GPU Inspector — AGI — fills.

What AGI actually is

AGI is Google's graphics profiler for Android. It's a desktop app that talks to a connected device over adb and pulls out two very different kinds of trace:

I've come to think of the system profiler as the thing that tells you where to look, and the frame profiler as the thing that tells you what to fix. Using the second one first is a good way to waste an afternoon.

It reads counters from Qualcomm Adreno, Arm Mali and Imagination PowerVR GPUs, which covers pretty much every Android device you'd realistically ship to.

Before you start: the checklist that saved me

AGI is fussier about setup than most Android tools, and the failure mode is usually a trace that captures nothing rather than a clear error. Go through this list first.

On your machine

On the device

In your app

The manifest needs the app marked debuggable:

<application
    android:name=".App"
    android:debuggable="true">

You'll get a lint warning about hardcoding this. In practice I keep a dedicated profiling build variant that sets it, so it never ends up anywhere near a release build.

If your app uses Vulkan, you also need to install AGI's debug layers on the device before capturing:

app_package=com.example.yourapp
abi=arm64v8a   # or armeabi-v7a, x86

adb shell settings put global enable_gpu_debug_layers 1
adb shell settings put global gpu_debug_app ${app_package}
adb shell settings put global gpu_debug_layer_app com.google.android.gapid.${abi}
adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation

Remember to clear these afterwards. Leaving the debug layers enabled globally quietly slows down every graphics app on the device, and then you spend a week wondering why your benchmarks regressed.

A phone in hand next to a desktop monitor and laptop on a white desk
Photo by Rafal Jedrzejek on Unsplash

Installing it

Grab the build for your OS from the releases page on GitHub — Windows gets an .msi or a zip, and there are macOS and Linux builds alongside them. You have to accept the Android SDK licence terms to download.

First launch asks for your adb path, then drops you on a screen with a Get Started button. Plug in the device and AGI runs a validation pass to check the GPU driver supports profiling. It takes about ten seconds and it tells you not to touch the device while it runs — take that literally, because interrupting it gives you a cached "unsupported" result that's annoying to clear.

Capturing a system profile

This is the one I reach for first, every time.

  1. Click Capture a new trace.
  2. Pick your device from the Device dropdown, and your app under Application.
  3. Set Type to System profile.
  4. Set Start at to Manual and Duration to something short. Two seconds is the documented default and it's genuinely enough — these traces get enormous fast.
  5. Hit Configure under Trace Options, then Select in the GPU section, then default to take the default counter set. Click OK twice to get back.
  6. Choose an Output Directory and click OK. Your app launches on the device.
  7. Get the app into the state you actually care about — the loading screen tells you nothing — then press Start.
  8. When it finishes, click Open Trace.

That "Manual" start setting is the whole trick. If you let it capture automatically at launch you'll profile your splash screen. I always navigate to the heaviest scene in the app, wait for it to settle, and only then start the capture.

Reading the timeline without drowning in it

The system profile view is dense. The first few times I opened one I just scrolled around feeling vaguely intimidated. What made it click was picking a specific question before looking.

A laptop screen showing performance analytics graphs and charts
Photo by Luke Chesser on Unsplash

The question that matters most: is the GPU busy, or is it waiting? Look at the GPU queue track against your frame boundaries.

What you seeWhat it usually meansWhere to look next
GPU busy nearly 100% of the frame You're GPU bound. Rendering is the bottleneck. Counters, then a frame trace
GPU has big idle gaps, CPU threads busy CPU bound — the GPU is starved waiting for work Thread scheduling track, then a CPU profiler
Both mostly idle, frames still long Usually a sync or presentation stall Frame processing times, vsync track
Memory bandwidth counters pinned Texture or vertex traffic, not raw shader cost Texture / vertex bandwidth views

The counters worth learning first, in the order I actually use them:

Google has separate guides for memory efficiency, texture bandwidth and thread scheduling, and they're short. Worth reading once so you know they exist.

One thing that consistently confuses people: mobile GPUs are tile-based. Work is deferred and batched in ways that make the timeline look nothing like a desktop GPU trace. A render pass that starts late isn't necessarily slow — it may just have been scheduled that way. Read the counters, not your intuition.

Capturing a frame profile

Once the system profile says "yes, you're GPU bound", the frame profiler tells you which part of the frame is eating the budget.

  1. Capture a new trace again, same device and application.
  2. In Type, pick Vulkan or OpenGL on ANGLE — whichever your app actually uses.
  3. Start at: Manual, pick an output directory, click OK.
  4. If your renderer runs in a separate process, set the process name in the optional field. Easy to miss, and without it you capture nothing.
  5. Drive the app to the frame you want, press Start, wait a few seconds, then Open Trace.

Getting the Type wrong is the single most common reason a frame capture comes back empty. AGI won't warn you — it just hands you a trace with no graphics commands in it, and you assume the tool is broken. If you're not sure which API your engine uses, check what it loads at runtime before you start guessing.

What's in the frame view

The frame profiler splits into panes and it takes a while to learn which ones matter to you:

My routine is: sort render passes by GPU time, open the most expensive one, scrub the framebuffer to see what it's drawing, then check the textures and shader it uses. Four clicks, and usually the answer is sitting right there.

What we actually found

In our case, three things, none of which I would have guessed:

The biggest was texture bandwidth. A batch of environment textures had been exported as uncompressed RGBA8 instead of ASTC. Nobody noticed because on a Pixel there was enough headroom to absorb it. Re-encoding them cut the frame time by roughly a third on the mid-range device.

Second, a post-processing pass was running at full resolution when half resolution was visually identical for what it did. The framebuffer pane made that one obvious — I could see the blur being computed at a fidelity nothing downstream used.

Third, and this is the one that stung: a UI overlay was drawing behind an opaque panel on every single frame. Pure waste, invisible in every tool except the framebuffer view, and it had been there for over a year.

None of these were exotic. That's sort of the point. GPU problems on mobile are usually boring problems that no one could see.

Things worth knowing before you sink a day into it

Was it worth it

The setup is more work than it should be, and the first trace you open will look like noise. But I'd been guessing at GPU behaviour for years, tuning by trial and error and hoping the numbers moved. Being able to see which render pass costs what, and which texture is eating the bandwidth budget, changed how I think about rendering performance entirely.

If you ship anything with meaningful GPU work on Android, spend an afternoon with it. Start with a system profile on a device you know feels bad, ask one question, and follow it down.


Official docs: developer.android.com/agi · Quickstart · Downloads

← All posts