Optimizing Shaders for Mobile Games: TBDR, Mali, Adreno
We develop custom shaders for mobile games with the architecture of mobile GPUs in mind — TBDR (tile-based deferred rendering) from Apple and Arm Mali, which differs from desktop immediate mode. If you don't understand this difference, performance can drop by 3-5 times. Our experience shows that proper shader optimization yields FPS gains without quality loss. For example, on devices with Mali-G78, replacing float with half in UV samples increased FPS from 25 to 55. The rendering budget savings are 20-40%, and response time drops by up to 30%. Contact us for a preliminary assessment of your project.
How Shader Optimization Affects Performance
Why Half Precision Is Faster Than Float
On mobile, the difference between float (32-bit), half (16-bit), and fixed (10-bit, outdated) is significant. Arm Mali and Adreno support native 16-bit arithmetic, and on them half works twice as fast as float. Vertex positions use float; colors and UVs use half. In GLSL ES 3.0 this corresponds to mediump vs highp.
| Type | Bit Width | Usage | Relative Speed |
|---|---|---|---|
| float | 32 bits | Vertex positions, matrices | 1x |
| half | 16 bits | Colors, UV coordinates | 2x |
| fixed | 10 bits | Deprecated, not recommended | - |
How to Reduce Overdraw on Mobile GPUs
Overdraw is the repeated painting of the same pixel. In TBDR, each extra pass increases fillrate. To reduce it, use early-z rejection and transparent objects only when necessary. In Unity: proper Sorting Layer and Queue in the shader (Geometry before Transparent) allow the renderer to use early-z. Transparent objects kill early-z — their count in a mobile game should be minimal. In a typical scene, the benefit from sorting can be up to 40% of frame time. According to Arm Mali documentation, overdraw is the primary cause of performance drops.
Shaders in Unity: URP vs Built-in
Universal Render Pipeline is the standard for mobile Unity games. Shader Graph lets you build shaders visually, but bottlenecks still need HLSL adjustments. A custom SubGraph in Shader Graph is a reusable node that compiles once.
Example of a simple dissolve shader in URP HLSL:
half4 frag(Varyings input) : SV_Target { half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv); half noiseValue = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, input.uv).r; half edge = smoothstep(_Threshold - _EdgeWidth, _Threshold + _EdgeWidth, noiseValue); clip(noiseValue - _Threshold); half3 edgeColor = lerp(baseColor.rgb, _EdgeColor.rgb, edge); return half4(edgeColor, baseColor.a); } clip() works fast on mobile — the TBDR architecture discards a tile before rasterization if all its pixels are clipped.
How We Optimize Shaders for Specific GPUs
Our process includes:
- Analysis of target devices (Apple GPU, Mali, Adreno) and their capabilities.
- Choosing precision and eliminating overdraw by sorting the render queue.
- Using Shader Graph with custom HLSL inserts for fine tuning.
- Testing on real devices with profiling in RenderDoc or Xcode GPU Frame Capture.
- Iterative optimization until target FPS (usually 60) is reached.
Which Post-Processing Effects Are Acceptable on Mobile Devices?
Full-screen post-effects (bloom, depth of field, motion blur) are expensive. Strategy:
| Effect | Recommendation | Savings (vs naive) |
|---|---|---|
| Bloom | Kawase blur instead of Gaussian | Up to 50% samples |
| Color grading | Tonemapping + Color Adjustments | ~0.5 ms |
| DOF | Only for cinematics | 3+ ms on 778G |
| Motion blur | Trail Renderer instead of velocity buffer | ~2 ms per frame |
In URP, use the Bloom override with High Quality Filtering disabled for the mobile preset.
Particle Shaders and VFX
VFX Graph in Unity requires Compute Shader — supported on Vulkan (Android 7+) and Metal (iOS 11+). The old Particle System runs on CPU, which is worse for large particle counts. For mobile targets: use VFX Graph on modern devices, CPU particles as fallback for budget models.
A particle shader must be as simple as possible: Unlit, Additive blending, half precision, no Lighting. A Lit shader on 500 particles is a common mistake that costs -10 FPS.
Godot and GLSL ES
In Godot 4, shaders are written in a GLSL dialect with custom extensions. Godot's shader language compiles to SPIR-V (Vulkan) or GLSL ES (compatibility). For mobile export, Godot uses the Compatibility renderer based on OpenGL ES 3.0 — it's wider supported than Vulkan on older Android.
What Our Work Includes
- Receiving and analyzing references and target devices.
- Developing up to 10 custom shaders considering TBDR and precision.
- Integration into your project (Unity, Godot, Unreal).
- Profiling and optimization to 60 FPS.
- Shader documentation and settings.
- 30-day support guarantee after delivery.
Common Beginner Mistakes
- Using float everywhere instead of half.
- Forgetting mip-maps.
- Applying full-screen post-effects without considering the budget.
Get a free assessment of your project — contact us for a consultation. Our engineers have 5+ years of experience in mobile graphics and have completed 50+ projects for iOS and Android. Investment in optimization pays off through FPS gains and reduced hardware requirements. Order a shader audit for your game — we will evaluate the optimization potential.
More details on TBDR architecture can be found on Wikipedia.







