Quick answer: Replace GPUParticles2D/3D with CPUParticles2D/3D on mobile builds. Set the renderer to Compatibility (GLES 3) for the broadest device support. Cap amount around 200–500 per emitter and lifetime under 2s for safety.
Particles work great on PC. Build to Android — they pop in and out, count varies frame to frame, sometimes nothing emits at all. GPUParticles relies on compute paths that not every mobile GPU supports cleanly.
The Symptom
GPUParticles flicker, drop frames worth of particles, or render zero-duration. Sometimes works on Pixel devices, broken on Samsung. Editor preview is normal. CPUParticles in the same scene work fine.
What Causes This
GPUParticles compute on the GPU via shader storage buffers. Mobile drivers vary in how reliably they support the necessary features:
- Adreno 6xx and newer: usually fine on Vulkan, sometimes broken on GLES.
- Mali G-series: variable; older drivers crash.
- PowerVR: historically problematic.
The Compatibility renderer (GLES 3) silently falls back to a slower path that doesn’t handle GPU particles consistently.
The Fix
Step 1: Switch to CPUParticles. Right-click your GPUParticles2D/3D → Convert to CPUParticles2D/3D. Godot copies most settings over; review the result. Process Material maps to CPUParticles fields directly.
CPUParticles2D:
amount: 200
lifetime: 1.5
emission_shape: Sphere
initial_velocity: 100
scale_amount_curve: ...
Step 2: Pick the right renderer. Project Settings → Rendering → Rendering Device → Driver. For Android target, set rendering_method.mobile = mobile (Vulkan) for newer devices, compatibility (GLES) for the broad market. Test on the lowest-spec device you support.
Step 3: Cap counts and durations. Mobile RAM is finite. Lifetime × emission rate = max alive count. Keep this under 500 unless you have profiled.
Per-Platform Configs
Use platform branches in your spawning code:
extends Node
@export var gpu_scene: PackedScene
@export var cpu_scene: PackedScene
func spawn() -> void:
var scene = cpu_scene if OS.has_feature("mobile") else gpu_scene
add_child(scene.instantiate())
One asset for desktop, one for mobile. The OS feature flag picks at runtime.
Verifying on Device
Run a development export with verbose logging. OS.has_feature("mobile") + RenderingServer.get_video_adapter_name() in a print. Confirm the renderer name matches your expectation; switch if needed.
“CPUParticles on mobile. Compatibility renderer for the long tail. Cap counts. Particles render every frame.”
Related Issues
For shaders failing to compile on mobile, see shader compile. For Godot mobile crash, see Android launch crash.
CPU on mobile. Compatibility renderer. Particles steady.