godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.31k stars 21.06k forks source link

GPU particles won't emit subparticles at end when parent FPS is sufficiently higher than child FPS #98072

Open AtlaStar opened 1 week ago

AtlaStar commented 1 week ago

Tested versions

Godot v4.4.dev (db66bd35a) Godot v4.3.stable.mono

System information

Godot v4.4.dev (76a135926) - Windows 10.0.19045 - Multi-window, 1 monitor - Vulkan (Forward+) - dedicated AMD Radeon RX 6650 XT (Advanced Micro Devices, Inc.; 31.0.24033.1003) - AMD Ryzen 7 3800X 8-Core Processor (16 threads)

Issue description

If a parent GPU particle node's FPS is significantly greater than its sub emitter, it will not emit particles. Doing a deep dive into the matter I believe has exposed the main culprit; vkCmdCopyBuffer calls will write to the sub-emitter buffer while a read on the next frame is required to reset and activate particles in the child.

As you can see here, the source/dest buffer is written to as 2 particles were set to be emitted by emit_subparticle within the compute shader. image In addition you can tell that at this step in the frame, another vkCmdCopyBuffer is queued working over the same buffers as in EID 56 and 57...and as you can see on the next copy to buffer, the value is reset image

The compute shader being dispatched at this point are particles.glsl shaders of the parent, as the child runs its particles compute and copy particles compute before the parent dispatches for additional context. This is important because the child shader runs at the beginning of the compute step in the frame, and relies on the buffer not having a 0 value in order to reset particles. image As demonstrated above, the "flushing" of particles is done on the first part of the compute pass with the amount to reset set on the previous frame.

It is also possible that accumulation of fractional delta values when using non-integer lifetimes can allow particles to sometimes be emitted, as the write to the buffer saying new particles were created can happen at the end, it just isn't likely while using integer lifetime values for the parent...although using fractional lifetime values and changing the lifetime to an integer amount can retain enough fractional accumulation that the emission trigger will occur at the end of the compute stage, making it appear as if the bug sometimes is present and sometimes not.

As far as a fix goes, my thought is that in particles storage that you'd need to read the particle count value from the emission buffer and only write to that buffer if the particles count is less than 0, although I am not sure if that is just a band-aid solution. As such I think a fix may warrant a discussion (although I have tested locally that it does indeed work for the test case).

Steps to reproduce

Create a GPU particles parent node set its FPS to 60 Create a GPU particles child node Set as the child retain its FPS of 30.

See how no particles are generated

Minimal reproduction project (MRP)

particlebugreport.zip

AtlaStar commented 1 week ago

Since I forgot to link it when I submitted the bug, this is a branch fixing a slew of GPU particles bugs which includes the mentioned solution, a potential fix #97904, and the changes in PR #97871 which I opened the other day. https://github.com/AtlaStar/godot/tree/Continued-GPU-particles-fixes