diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml index 9ba64feebc8..e42aca1ada5 100644 --- a/doc/classes/GPUParticles3D.xml +++ b/doc/classes/GPUParticles3D.xml @@ -67,6 +67,7 @@ Particle draw order. Uses [enum DrawOrder] values. + [b]Note:[/b] [constant DRAW_ORDER_INDEX] is the only option that supports motion vectors for effects like TAA. It is suggested to use this draw order if the particles are opaque to fix ghosting artifacts. [Mesh] that is drawn for the first draw pass. diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index c98752fb39b..2e64946fd26 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -252,6 +252,7 @@ bool RenderForwardClustered::free(RID p_rid) { template void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p_draw_list, RenderingDevice::FramebufferFormatID p_framebuffer_Format, RenderListParameters *p_params, uint32_t p_from_element, uint32_t p_to_element) { RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton(); + RendererRD::ParticlesStorage *particles_storage = RendererRD::ParticlesStorage::get_singleton(); RD::DrawListID draw_list = p_draw_list; RD::FramebufferFormatID framebuffer_format = p_framebuffer_Format; @@ -477,7 +478,9 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p prev_material_uniform_set = material_uniform_set; } - if ((surf->owner->base_flags & (INSTANCE_DATA_FLAG_MULTIMESH | INSTANCE_DATA_FLAG_PARTICLES)) == INSTANCE_DATA_FLAG_MULTIMESH) { + if (surf->owner->base_flags & INSTANCE_DATA_FLAG_PARTICLES) { + particles_storage->particles_get_instance_buffer_motion_vectors_offsets(surf->owner->data->base, push_constant.multimesh_motion_vectors_current_offset, push_constant.multimesh_motion_vectors_previous_offset); + } else if (surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH) { mesh_storage->_multimesh_get_motion_vectors_offsets(surf->owner->data->base, push_constant.multimesh_motion_vectors_current_offset, push_constant.multimesh_motion_vectors_previous_offset); } else { push_constant.multimesh_motion_vectors_current_offset = 0; @@ -3716,6 +3719,10 @@ void RenderForwardClustered::_geometry_instance_update(RenderGeometryInstance *p // Particles haven't been cleared or updated, update once now to ensure they are ready to render. particles_storage->update_particles(); } + + if (ginstance->data->dirty_dependencies) { + particles_storage->particles_update_dependency(ginstance->data->base, &ginstance->data->dependency_tracker); + } } else if (ginstance->data->base_type == RS::INSTANCE_MESH) { if (mesh_storage->skeleton_is_valid(ginstance->data->skeleton)) { ginstance->transforms_uniform_set = mesh_storage->skeleton_get_3d_uniform_set(ginstance->data->skeleton, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET); @@ -3755,6 +3762,7 @@ void RenderForwardClustered::_geometry_instance_dependency_changed(Dependency::D case Dependency::DEPENDENCY_CHANGED_MATERIAL: case Dependency::DEPENDENCY_CHANGED_MESH: case Dependency::DEPENDENCY_CHANGED_PARTICLES: + case Dependency::DEPENDENCY_CHANGED_PARTICLES_INSTANCES: case Dependency::DEPENDENCY_CHANGED_MULTIMESH: case Dependency::DEPENDENCY_CHANGED_SKELETON_DATA: { static_cast(p_tracker->userdata)->_mark_dirty(); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 90d770399ea..63d20cf8e13 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -2614,6 +2614,10 @@ void RenderForwardMobile::_geometry_instance_update(RenderGeometryInstance *p_ge // Particles haven't been cleared or updated, update once now to ensure they are ready to render. particles_storage->update_particles(); } + + if (ginstance->data->dirty_dependencies) { + particles_storage->particles_update_dependency(ginstance->data->base, &ginstance->data->dependency_tracker); + } } else if (ginstance->data->base_type == RS::INSTANCE_MESH) { if (mesh_storage->skeleton_is_valid(ginstance->data->skeleton)) { ginstance->transforms_uniform_set = mesh_storage->skeleton_get_3d_uniform_set(ginstance->data->skeleton, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET); diff --git a/servers/rendering/renderer_rd/shaders/particles_copy.glsl b/servers/rendering/renderer_rd/shaders/particles_copy.glsl index 67a894b86f9..6a06979c2c5 100644 --- a/servers/rendering/renderer_rd/shaders/particles_copy.glsl +++ b/servers/rendering/renderer_rd/shaders/particles_copy.glsl @@ -45,6 +45,9 @@ layout(set = 2, binding = 0, std430) restrict readonly buffer TrailBindPoses { } trail_bind_poses; +#define PARAMS_FLAG_ORDER_BY_LIFETIME 1 +#define PARAMS_FLAG_COPY_MODE_2D 2 + layout(push_constant, std430) uniform Params { vec3 sort_direction; uint total_particles; @@ -57,10 +60,10 @@ layout(push_constant, std430) uniform Params { vec3 align_up; uint align_mode; - bool order_by_lifetime; uint lifetime_split; bool lifetime_reverse; - bool copy_mode_2d; + uint motion_vectors_current_offset; + uint flags; mat4 inv_emission_transform; } @@ -103,7 +106,7 @@ void main() { particle = uint(sort_buffer.data[particle].y); //use index from sort buffer } #else - if (params.order_by_lifetime) { + if (bool(params.flags & PARAMS_FLAG_ORDER_BY_LIFETIME)) { if (params.trail_size > 1) { uint limit = (params.total_particles / params.trail_size) - params.lifetime_split; @@ -201,7 +204,7 @@ void main() { txform = txform * trail_bind_poses.data[part_ofs]; } - if (params.copy_mode_2d) { + if (bool(params.flags & PARAMS_FLAG_COPY_MODE_2D)) { // In global mode, bring 2D particles to local coordinates // as they will be drawn with the node position as origin. txform = params.inv_emission_transform * txform; @@ -213,15 +216,16 @@ void main() { } txform = transpose(txform); - if (params.copy_mode_2d) { - uint write_offset = gl_GlobalInvocationID.x * (2 + 1 + 1); //xform + color + custom + uint instance_index = gl_GlobalInvocationID.x + params.motion_vectors_current_offset; + if (bool(params.flags & PARAMS_FLAG_COPY_MODE_2D)) { + uint write_offset = instance_index * (2 + 1 + 1); //xform + color + custom instances.data[write_offset + 0] = txform[0]; instances.data[write_offset + 1] = txform[1]; instances.data[write_offset + 2] = particles.data[particle].color; instances.data[write_offset + 3] = particles.data[particle].custom; } else { - uint write_offset = gl_GlobalInvocationID.x * (3 + 1 + 1); //xform + color + custom + uint write_offset = instance_index * (3 + 1 + 1); //xform + color + custom instances.data[write_offset + 0] = txform[0]; instances.data[write_offset + 1] = txform[1]; diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp index 0a91672544d..3a415e97e0e 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp @@ -666,6 +666,19 @@ RID ParticlesStorage::particles_get_draw_pass_mesh(RID p_particles, int p_pass) return particles->draw_passes[p_pass]; } +void ParticlesStorage::particles_update_dependency(RID p_particles, DependencyTracker *p_instance) { + Particles *particles = particles_owner.get_or_null(p_particles); + ERR_FAIL_COND(!particles); + p_instance->update_dependency(&particles->dependency); +} + +void ParticlesStorage::particles_get_instance_buffer_motion_vectors_offsets(RID p_particles, uint32_t &r_current_offset, uint32_t &r_prev_offset) { + Particles *particles = particles_owner.get_or_null(p_particles); + ERR_FAIL_COND(!particles); + r_current_offset = particles->instance_motion_vectors_current_offset; + r_prev_offset = particles->instance_motion_vectors_previous_offset; +} + void ParticlesStorage::particles_add_collision(RID p_particles, RID p_particles_collision_instance) { Particles *particles = particles_owner.get_or_null(p_particles); ERR_FAIL_COND(!particles); @@ -1185,6 +1198,7 @@ void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME); copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount; copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME; + copy_push_constant.motion_vectors_current_offset = particles->instance_motion_vectors_current_offset; copy_push_constant.frame_remainder = particles->interpolate ? particles->frame_remainder : 0.0; copy_push_constant.total_particles = particles->amount; @@ -1252,28 +1266,50 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) { userdata_count = particle_shader_data->userdata_count; } + bool uses_motion_vectors = RSG::viewport->get_num_viewports_with_motion_vectors() > 0; + bool index_draw_order = particles->draw_order == RS::ParticlesDrawOrder::PARTICLES_DRAW_ORDER_INDEX; + bool enable_motion_vectors = uses_motion_vectors && index_draw_order && !particles->instance_motion_vectors_enabled; + bool only_instances_changed = false; + if (userdata_count != particles->userdata_count) { - // Mismatch userdata, re-create buffers. + // Mismatch userdata, re-create all buffers. _particles_free_data(particles); + } else if (enable_motion_vectors) { + // Only motion vectors are required, release the transforms buffer and uniform set. + if (particles->particle_instance_buffer.is_valid()) { + RD::get_singleton()->free(particles->particle_instance_buffer); + particles->particle_instance_buffer = RID(); + } + + particles->particles_transforms_buffer_uniform_set = RID(); + only_instances_changed = true; + } else if (!particles->particle_buffer.is_null()) { + // No operation is required because a buffer already exists, return early. + return; } - if (particles->amount > 0 && particles->particle_buffer.is_null()) { + if (particles->amount > 0) { int total_amount = particles->amount; if (particles->trails_enabled && particles->trail_bind_poses.size() > 1) { total_amount *= particles->trail_bind_poses.size(); } uint32_t xform_size = particles->mode == RS::PARTICLES_MODE_2D ? 2 : 3; - - particles->particle_buffer = RD::get_singleton()->storage_buffer_create((sizeof(ParticleData) + userdata_count * sizeof(float) * 4) * total_amount); - - particles->userdata_count = userdata_count; + if (particles->particle_buffer.is_null()) { + particles->particle_buffer = RD::get_singleton()->storage_buffer_create((sizeof(ParticleData) + userdata_count * sizeof(float) * 4) * total_amount); + particles->userdata_count = userdata_count; + } PackedByteArray data; - data.resize_zeroed(sizeof(float) * 4 * (xform_size + 1 + 1) * total_amount); + uint32_t particle_instance_buffer_size = total_amount * (xform_size + 1 + 1) * sizeof(float) * 4; + if (uses_motion_vectors) { + particle_instance_buffer_size *= 2; + particles->instance_motion_vectors_enabled = true; + } - particles->particle_instance_buffer = RD::get_singleton()->storage_buffer_create(sizeof(float) * 4 * (xform_size + 1 + 1) * total_amount, data); - //needs to clear it + data.resize_zeroed(particle_instance_buffer_size); + + particles->particle_instance_buffer = RD::get_singleton()->storage_buffer_create(particle_instance_buffer_size, data); { Vector uniforms; @@ -1295,9 +1331,20 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) { particles->particles_copy_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.copy_shader.version_get_shader(particles_shader.copy_shader_version, 0), 0); } + + particles->instance_motion_vectors_current_offset = 0; + particles->instance_motion_vectors_previous_offset = 0; + particles->instance_motion_vectors_last_change = -1; + + if (only_instances_changed) { + // Notify the renderer the instances uniform must be retrieved again, as it's the only element that has been changed because motion vectors were enabled. + particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES_INSTANCES); + } } } void ParticlesStorage::update_particles() { + uint32_t frame = RSG::rasterizer->get_frame_number(); + bool uses_motion_vectors = RSG::viewport->get_num_viewports_with_motion_vectors() > 0; while (particle_update_list) { //use transform feedback to process particles @@ -1461,16 +1508,25 @@ void ParticlesStorage::update_particles() { // Ensure that memory is initialized (the code above should ensure that _particles_process is always called at least once upon clearing). DEV_ASSERT(!particles->clear); + int total_amount = particles->amount; + if (particles->trails_enabled && particles->trail_bind_poses.size() > 1) { + total_amount *= particles->trail_bind_poses.size(); + } + + // Swap offsets for motion vectors. Motion vectors can only be used when the draw order keeps the indices consistent across frames. + bool index_draw_order = particles->draw_order == RS::ParticlesDrawOrder::PARTICLES_DRAW_ORDER_INDEX; + particles->instance_motion_vectors_previous_offset = particles->instance_motion_vectors_current_offset; + if (uses_motion_vectors && index_draw_order && particles->instance_motion_vectors_enabled && (frame - particles->instance_motion_vectors_last_change) == 1) { + particles->instance_motion_vectors_current_offset = total_amount - particles->instance_motion_vectors_current_offset; + } + + particles->instance_motion_vectors_last_change = frame; + // Copy particles to instance buffer. if (particles->draw_order != RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY) { //does not need view dependent operation, do copy here ParticlesShader::CopyPushConstant copy_push_constant; - int total_amount = particles->amount; - if (particles->trails_enabled && particles->trail_bind_poses.size() > 1) { - total_amount *= particles->trail_bind_poses.size(); - } - // Affect 2D only. if (particles->use_local_coords) { // In local mode, particle positions are calculated locally (relative to the node position) @@ -1506,6 +1562,7 @@ void ParticlesStorage::update_particles() { copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME); copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount; copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME; + copy_push_constant.motion_vectors_current_offset = particles->instance_motion_vectors_current_offset; RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); copy_push_constant.copy_mode_2d = particles->mode == RS::PARTICLES_MODE_2D ? 1 : 0; diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.h b/servers/rendering/renderer_rd/storage_rd/particles_storage.h index 612420a36c7..7cea706a8ca 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.h @@ -225,6 +225,11 @@ private: double frame_remainder = 0; real_t collision_base_size = 0.01; + uint32_t instance_motion_vectors_current_offset = 0; + uint32_t instance_motion_vectors_previous_offset = 0; + uint64_t instance_motion_vectors_last_change = -1; + bool instance_motion_vectors_enabled = false; + bool clear = true; bool force_sub_emit = false; @@ -288,10 +293,13 @@ private: float align_up[3]; uint32_t align_mode; - uint32_t order_by_lifetime; uint32_t lifetime_split; uint32_t lifetime_reverse; - uint32_t copy_mode_2d; + uint32_t motion_vectors_current_offset; + struct { + uint32_t order_by_lifetime : 1; + uint32_t copy_mode_2d : 1; + }; float inv_emission_transform[16]; }; @@ -521,12 +529,15 @@ public: return particles->particles_transforms_buffer_uniform_set; } + void particles_get_instance_buffer_motion_vectors_offsets(RID p_particles, uint32_t &r_current_offset, uint32_t &r_prev_offset); + virtual void particles_add_collision(RID p_particles, RID p_particles_collision_instance) override; virtual void particles_remove_collision(RID p_particles, RID p_particles_collision_instance) override; void particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, RID p_texture); virtual void update_particles() override; + void particles_update_dependency(RID p_particles, DependencyTracker *p_instance); Dependency *particles_get_dependency(RID p_particles) const; /* Particles Collision */ diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h index 4745c3d1d3a..29e5ea29fd6 100644 --- a/servers/rendering/renderer_scene_cull.h +++ b/servers/rendering/renderer_scene_cull.h @@ -506,6 +506,9 @@ public: } } break; + default: { + // Ignored notifications. + } break; } } diff --git a/servers/rendering/storage/utilities.h b/servers/rendering/storage/utilities.h index 8fb4907a434..387f71f78a0 100644 --- a/servers/rendering/storage/utilities.h +++ b/servers/rendering/storage/utilities.h @@ -44,6 +44,7 @@ public: DEPENDENCY_CHANGED_MULTIMESH, DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES, DEPENDENCY_CHANGED_PARTICLES, + DEPENDENCY_CHANGED_PARTICLES_INSTANCES, DEPENDENCY_CHANGED_DECAL, DEPENDENCY_CHANGED_SKELETON_DATA, DEPENDENCY_CHANGED_SKELETON_BONES,