agentsclimarketplace

Mobile shader optimization

Skill adevra/unity-shader-agent-skills/skills/mobile-shader-optimization

Curated agent skills for Unity shader development targeting mobile GPUs and WebGL browsers.

Install
npx -y skills add adevra/unity-shader-agent-skills --skill mobile-shader-optimization

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

GPU architecture, precision types, fillrate, overdraw, baked lighting, and LOD optimization for Unity mobile/WebGL shaders

SKILL.md

11.7 KB, as published. Nobody here has run it

Mobile Shader Optimization for Unity

TRIGGER

Use this skill when the user is writing, reviewing, or optimizing Unity shaders for mobile platforms (Android, iOS) or any GPU-constrained environment (Meta Quest VR, low-end devices). Triggers include: mentions of "mobile", "Android", "iOS", "Mali", "Adreno", "PowerVR", "performance", "optimization", "fillrate", "overdraw", "bandwidth", "frame budget", "half precision", "LOD", "baked lighting", or any shader work targeting sub-60fps scenarios. Also trigger when the user is profiling shaders, asking about GPU architecture, or comparing Simple Lit vs Complex Lit in URP.


CORE RULES — HARD CONSTRAINTS

These rules must always be followed when writing mobile shaders:

  1. Use half precision by default. Only use float for world-space positions, UV coordinates requiring high precision, and depth calculations. On Mali and Adreno GPUs, half triggers 16-bit fast paths that are 2x faster.

  2. Never use discard (clip/alpha-test) on PowerVR or Apple GPUs. It breaks Early-Z/Hidden Surface Removal, forcing the GPU to process fragments it would otherwise skip. Use alpha blending instead, or move discard to a separate pass.

  3. Keep Shader Graph node count under 100 for mobile. Each node compiles to shader instructions. Exceeding ~100 nodes risks exceeding mobile GPU instruction limits and causes performance cliffs.

  4. Never use while or do-while loops in shaders targeting OpenGL ES 2.0 / WebGL 1.0. Only counting for loops are allowed. Even on ES 3.0+, avoid dynamic loop bounds — they prevent the compiler from unrolling.

  5. Avoid dynamic branching on mobile. Mobile GPUs often execute both branches regardless. Use step(), lerp(), saturate() for branchless alternatives.

  6. Multiply scalars before vectors. float3 result = scalar1 * scalar2 * vector is faster than float3 result = vector * scalar1 * scalar2 because the first scalar multiply is 1 ALU op instead of 3.

  7. Limit texture samples. Each sample costs bandwidth. Target ≤4 texture samples per fragment on low-end mobile. Use channel packing to combine maps (see texture-packing-variant-stripping skill).

  8. Use noforwardadd surface shader directive to skip additional per-pixel light passes. Each additional light pass doubles overdraw.


MOBILE GPU ARCHITECTURE

Understanding tile-based rendering is essential for mobile shader optimization.

Tile-Based Deferred Rendering (TBDR)

Mobile GPUs split the screen into small tiles (16×16 for Mali, variable for Adreno) and process each tile entirely on-chip before writing to main memory. This means:

  • Bandwidth is the primary bottleneck, not compute. Every texture read, framebuffer write, and render target switch costs main memory bandwidth.
  • Overdraw is extremely expensive because each overlapping fragment consumes on-chip tile memory and potentially triggers main memory writes.
  • MSAA is nearly free on tiled GPUs (resolved on-chip), unlike desktop where it multiplies framebuffer bandwidth.
  • Render target switches are costly — each switch flushes tile memory to main memory. Minimize post-processing passes.
  • Power budget is 3–6W (vs 300W desktop). Thermal throttling will reduce clock speeds within minutes if shaders are too complex.

Per-GPU Family Notes

  • Mali (ARM): 16×16 tiles. Use Mali Offline Compiler to check cycle counts. Forward rendering preferred over deferred.
  • Adreno (Qualcomm): Flexible tile size (~256K). Supports LRZ (Low-Resolution Z) for early rejection. Slightly more tolerant of complex fragment shaders.
  • PowerVR (Apple/Imagination): True TBDR with HSR (Hidden Surface Removal). Alpha testing/discard breaks HSR. Apple GPUs have dedicated half hardware.
  • Apple A-series: Tightly integrated with Metal. half is genuinely 16-bit and 2x throughput. float is 32-bit. No fixed type.

PRECISION TYPES

// HLSL/ShaderLab precision mapping to GLSL:
// float  → highp   (32-bit) — positions, UVs, depth
// half   → mediump (16-bit) — colors, normals, most calculations
// fixed  → lowp    (11-bit) — simple color ops only (deprecated on many GPUs)

// USE half FOR:
half4 color = tex2D(_MainTex, uv);         // Texture samples
half3 normal = normalize(i.worldNormal);    // Normals
half NdotL = saturate(dot(normal, lightDir)); // Lighting
half fresnel = pow(1.0h - NdotL, 4.0h);    // Fresnel

// USE float FOR:
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // World position
float2 uv = TRANSFORM_TEX(v.texcoord, _MainTex);         // UV transforms
float depth = Linear01Depth(rawDepth);                     // Depth

VERTEX VS FRAGMENT OPTIMIZATION

On fillrate-bound mobile GPUs, vertex count is orders of magnitude lower than fragment count. Move calculations to the vertex shader whenever visually acceptable:

// BAD — per-pixel calculation (runs millions of times)
half4 frag(v2f i) : SV_Target {
    half rim = 1.0h - saturate(dot(normalize(i.viewDir), normalize(i.worldNormal)));
    half rimPower = pow(rim, _RimPower);
    return _RimColor * rimPower;
}

// GOOD — per-vertex calculation (runs thousands of times)
v2f vert(appdata v) {
    v2f o;
    o.vertex = TransformObjectToHClip(v.vertex.xyz);
    float3 viewDir = normalize(GetWorldSpaceViewDir(TransformObjectToWorld(v.vertex.xyz)));
    float3 worldNormal = TransformObjectToWorldNormal(v.normal);
    o.rimFactor = pow(1.0 - saturate(dot(viewDir, worldNormal)), _RimPower);
    return o;
}

half4 frag(v2f i) : SV_Target {
    return _RimColor * i.rimFactor; // Just interpolated value
}

What to move to vertex shader:

  • Rim/fresnel calculations
  • View direction normalization
  • Fog factor computation
  • Simple UV animation offsets
  • Light direction dot products (for flat/low-poly styles)

What must stay in fragment shader:

  • Texture sampling
  • Normal map lookups
  • Precise specular highlights
  • Screen-space effects

SHADER LOD AND FALLBACK STRATEGY

Use LOD tiers to serve different shader complexity per device capability:

Shader "Game/Character" {
    // LOD 300: Full quality (desktop, high-end mobile)
    SubShader {
        LOD 300
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
        Pass {
            // Normal map + specular + rim + emission
        }
    }
    
    // LOD 150: Medium quality (mid-range mobile)
    SubShader {
        LOD 150
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
        Pass {
            // Diffuse + simple lighting, no normal map
        }
    }
    
    // LOD 100: Minimum quality (low-end mobile)
    SubShader {
        LOD 100
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
        Pass {
            // Vertex-lit only, single texture
        }
    }
    
    Fallback "Universal Render Pipeline/Simple Lit"
}

Set at runtime based on device:

// In a device detection script:
if (SystemInfo.graphicsMemorySize < 1024)
    Shader.globalMaximumLOD = 100;
else if (SystemInfo.graphicsMemorySize < 2048)
    Shader.globalMaximumLOD = 150;
else
    Shader.globalMaximumLOD = 300;

Built-in LOD values: Mobile/VertexLit = 100, Mobile/Diffuse = 150, Mobile/Bumped = 200.


BAKED LIGHTING FOR MOBILE

Fully baked lighting eliminates real-time shadow maps and reduces fragment shaders to a single lightmap fetch. This is often the difference between 30 and 60 FPS on low-end devices.

Rules:

  • Use baked lighting for all static geometry on mobile. The lightmap sample is far cheaper than real-time shadow mapping.
  • Limit real-time lights to 1 directional light (the main light). Additional lights add per-pixel passes.
  • Directional lights are cheapest, point lights most expensive (require 6-direction shadow maps if real-time).
  • Use Light Probes for dynamic objects instead of real-time lights.
  • Deferred rendering is poor on mobile — it requires multiple render targets, which means multiple tile memory flushes. Always use Forward rendering.

URP ASSET SETTINGS FOR MOBILE

Configure the URP Asset for mobile:

  • Rendering: Forward rendering, no Depth Priming (adds a prepass), MSAA 2x or 4x (cheap on tiled GPUs)
  • Quality: Disable HDR unless needed for Bloom (HDR doubles framebuffer bandwidth). Disable Additional Lights if only using main directional.
  • Shadows: Reduce shadow resolution (1024 or 512 for mobile). Reduce shadow distance. Use 1 shadow cascade for mobile (2 max).
  • Post-Processing: Disable if not needed. If needed, use a single-pass approach (see mobile-post-processing skill).

OPTIMIZATION CHECKLIST

When reviewing a mobile shader, verify:

  • All color/normal calculations use half precision
  • Only world positions and UVs use float
  • No discard/clip on iOS/PowerVR targets (or isolated in separate pass)
  • No dynamic branching (replaced with step/lerp/saturate)
  • Texture samples ≤ 4 per fragment
  • No while/do-while loops
  • Scalar multiplications happen before vector multiplications
  • View-independent calculations moved to vertex shader
  • SRP Batcher compatible (properties in CBUFFER)
  • LOD SubShaders defined for at least 2 tiers
  • noforwardadd used on surface shaders (or additional lights disabled in URP)
  • No Standard Shader — use Simple Lit or custom Unlit

PROFILING TOOLS

  • Unity Frame Debugger (Window > Analysis > Frame Debugger) — inspect draw calls, shader passes, overdraw
  • Mali Offline Compiler — check instruction cycle counts for ARM Mali GPUs
  • Snapdragon Profiler — profile Adreno GPU counters
  • Arm Performance Studio (Streamline) — detailed Mali GPU performance analysis
  • RenderDoc — frame capture and per-draw analysis
  • Xcode GPU Profiler — Apple GPU profiling with shader cost heatmaps

SOURCES

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.