godotengine / godotengine/godot
GPUParticles3D nodes skip update for one frame whenever a new GPUParticles3D is created
- Dominant language
- C++
- Stars
- 117k
- Forks
- 26.8k
- PR merge metrics
- PR metrics pending
Description
### Tested versions
Reproducible in: v4.4.beta1.mono.official [d33da79d3]
### System information
Godot v4.4.beta1.mono - Windows 10 (build 19045) - Single-window, 1 monitor - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 2060 (NVIDIA; 31.0.15.5275) - Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz (12 threads)
### Issue description
Whenever a scene with a GPUParticles3D node is instantiated, all existing GPUParticles3D nodes skip updates for one frame. Adding the instantiated scene to the scene tree via e.g. `add_sibling` is not necessary for the issue to occur.
The issue is difficult to see with high FPS and/or slow moving particles, but easy to notice with a low FPS cap (e.g. 20) and fast moving particles.
https://github.com/user-attachments/assets/56202ffb-1775-4385-9c0a-3f830713a3ae
The GIF below illustrates the moment of creating a new particle system in slowdown. It can be seen that the frame following the one with the creation of the new GPUParticles3D has the exact same configuration of all particles as the frame prior, but slightly offset due to independent camera movement.

### Steps to reproduce
- In the project settings, set the max_fps option to a low value, e.g. 20. This will make the issue easier to see.
- Create a scene with a GPUParticles3D as a root node. The content is unimportant as long as the particles are visible and not one-shot.
- Create a preview scene.
- Put a Camera3D node in the root of the preview scene, e.g. unrotated, at z=6m. This is for preview purposes.
- Put a Node3D node in the root of the preview scene, e.g. unrotated, at x=-7, y=5. This is a cursor for spawning the particle scene. Attach a script to the node, for example:
```gdscript
extends Node3D
@export var particle_scene: PackedScene
@onready var spawned_particles: Array = []
func _physics_process(_delta):
if Input.is_action_just_pressed("ui_accept"):
if global_position.x < 7.0:
# create a new particle system
var particle = particle_scene.instantiate()
particle.position = global_position
add_sibling(particle)
spawned_particles.push_back(particle)
# push the position
global_position.x += 1.0
else:
# if the position was pushed far enough, reset
for particle in spawned_particles:
particle.queue_free()
spawned_particles.clear()
global_position.x = -7.0
```
- Make sure the particle scene is linked the the cursor node's export variable.
- Run the game in play mode on the preview scene. Pressing Enter should result in the cursor spawning a particle scene and moving.
With the low FPS value, it should be easy to see that every time a particle scene is spawned, all the GPUParticles3D nodes stop updating for exactly one frame. Commenting out the `add_sibling` line shows that the issue still occurs when the new GPUParticles3D node is not added to the scene tree.
If there are other moving nodes on the scene, it can be seen that this is a local issue rather than a global hang-up. Minimal reproduction project comes with slight camera movement to illustrate that the movement of the rest of the game continues smoothly.
### Minimal reproduction project (MRP)
[particletest2_2025-01-18_15-33-43.zip](https://github.com/user-attachments/files/18464787/particletest2_2025-01-18_15-33-43.zip)
Contributor guide
Research direction
Start with the attached minimal reproduction project and follow the reproduction steps with a low FPS cap to confirm the one-frame pause. Trace GPUParticles3D creation and update scheduling, including the case where the new node is not added to the scene tree; done means existing particle systems update continuously when another GPUParticles3D is instantiated.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- computer-graphics, game-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100