From f5f27bacdbd6677c16b82729dd3f4824609cf2ed Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Wed, 19 Aug 2020 10:38:24 -0300 Subject: [PATCH] Re-Implement GPU particles on master. -No new features yet -Unlike godot 3.x, sorting happens using GPU --- scene/resources/particles_material.cpp | 2 +- servers/rendering/rasterizer.h | 2 + .../rasterizer_rd/rasterizer_effects_rd.cpp | 85 ++ .../rasterizer_rd/rasterizer_effects_rd.h | 24 +- .../rasterizer_scene_high_end_rd.cpp | 76 +- .../rasterizer_rd/rasterizer_scene_rd.h | 2 +- .../rasterizer_rd/rasterizer_storage_rd.cpp | 880 +++++++++++++++++- .../rasterizer_rd/rasterizer_storage_rd.h | 315 ++++++- .../rasterizer_rd/shader_compiler_rd.cpp | 14 + .../rasterizer_rd/shader_compiler_rd.h | 3 + servers/rendering/rasterizer_rd/shaders/SCsub | 3 + .../rasterizer_rd/shaders/particles.glsl | 262 ++++++ .../rasterizer_rd/shaders/particles_copy.glsl | 82 ++ .../rendering/rasterizer_rd/shaders/sort.glsl | 203 ++++ servers/rendering/rendering_server_scene.cpp | 1 + servers/rendering/shader_types.cpp | 28 +- 16 files changed, 1919 insertions(+), 63 deletions(-) create mode 100644 servers/rendering/rasterizer_rd/shaders/particles.glsl create mode 100644 servers/rendering/rasterizer_rd/shaders/particles_copy.glsl create mode 100644 servers/rendering/rasterizer_rd/shaders/sort.glsl diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index fc92a721db..a0095ed952 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -277,7 +277,7 @@ void ParticlesMaterial::_update_shader() { code += "}\n"; code += "\n"; - code += "void vertex() {\n"; + code += "void compute() {\n"; code += " uint base_number = NUMBER / uint(trail_divisor);\n"; code += " uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n"; code += " float angle_rand = rand_from_seed(alt_seed);\n"; diff --git a/servers/rendering/rasterizer.h b/servers/rendering/rasterizer.h index 78eb369672..41dcd387de 100644 --- a/servers/rendering/rasterizer.h +++ b/servers/rendering/rasterizer.h @@ -678,6 +678,8 @@ public: virtual int particles_get_draw_passes(RID p_particles) const = 0; virtual RID particles_get_draw_pass_mesh(RID p_particles, int p_pass) const = 0; + virtual void particles_set_view_axis(RID p_particles, const Vector3 &p_axis) = 0; + /* GLOBAL VARIABLES */ virtual void global_variable_add(const StringName &p_name, RS::GlobalVariableType p_type, const Variant &p_value) = 0; diff --git a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp index 93ddcb1cff..527ed09584 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp @@ -1273,6 +1273,76 @@ void RasterizerEffectsRD::filter_shadow(RID p_shadow, RID p_backing_shadow, cons RD::get_singleton()->compute_list_dispatch_threads(compute_list, p_source_rect.size.width, p_source_rect.size.height, 1, 8, 8, 1); } } + +void RasterizerEffectsRD::sort_buffer(RID p_uniform_set, int p_size) { + Sort::PushConstant push_constant; + push_constant.total_elements = p_size; + + bool done = true; + + int numThreadGroups = ((p_size - 1) >> 9) + 1; + + if (numThreadGroups > 1) { + done = false; + } + + RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); + + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, sort.pipelines[SORT_MODE_BLOCK]); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, p_uniform_set, 1); + RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(Sort::PushConstant)); + RD::get_singleton()->compute_list_dispatch(compute_list, numThreadGroups, 1, 1); + + int presorted = 512; + + while (!done) { + RD::get_singleton()->compute_list_add_barrier(compute_list); + + done = true; + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, sort.pipelines[SORT_MODE_STEP]); + + numThreadGroups = 0; + + if (p_size > presorted) { + if (p_size > presorted * 2) { + done = false; + } + + int pow2 = presorted; + while (pow2 < p_size) { + pow2 *= 2; + } + numThreadGroups = pow2 >> 9; + } + + unsigned int nMergeSize = presorted * 2; + + for (unsigned int nMergeSubSize = nMergeSize >> 1; nMergeSubSize > 256; nMergeSubSize = nMergeSubSize >> 1) { + push_constant.job_params[0] = nMergeSubSize; + if (nMergeSubSize == nMergeSize >> 1) { + push_constant.job_params[1] = (2 * nMergeSubSize - 1); + push_constant.job_params[2] = -1; + } else { + push_constant.job_params[1] = nMergeSubSize; + push_constant.job_params[2] = 1; + } + push_constant.job_params[3] = 0; + + RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(Sort::PushConstant)); + RD::get_singleton()->compute_list_dispatch(compute_list, numThreadGroups, 1, 1); + RD::get_singleton()->compute_list_add_barrier(compute_list); + } + + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, sort.pipelines[SORT_MODE_INNER]); + RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(Sort::PushConstant)); + RD::get_singleton()->compute_list_dispatch(compute_list, numThreadGroups, 1, 1); + + presorted *= 2; + } + + RD::get_singleton()->compute_list_end(); +} + RasterizerEffectsRD::RasterizerEffectsRD() { { // Initialize copy Vector copy_modes; @@ -1618,6 +1688,21 @@ RasterizerEffectsRD::RasterizerEffectsRD() { } } + { + Vector sort_modes; + sort_modes.push_back("\n#define MODE_SORT_BLOCK\n"); + sort_modes.push_back("\n#define MODE_SORT_STEP\n"); + sort_modes.push_back("\n#define MODE_SORT_INNER\n"); + + sort.shader.initialize(sort_modes); + + sort.shader_version = sort.shader.version_create(); + + for (int i = 0; i < SORT_MODE_MAX; i++) { + sort.pipelines[i] = RD::get_singleton()->compute_pipeline_create(sort.shader.version_get_shader(sort.shader_version, i)); + } + } + RD::SamplerState sampler; sampler.mag_filter = RD::SAMPLER_FILTER_LINEAR; sampler.min_filter = RD::SAMPLER_FILTER_LINEAR; diff --git a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.h b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.h index 6dff835e21..e434bbc372 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.h @@ -47,6 +47,7 @@ #include "servers/rendering/rasterizer_rd/shaders/screen_space_reflection_filter.glsl.gen.h" #include "servers/rendering/rasterizer_rd/shaders/screen_space_reflection_scale.glsl.gen.h" #include "servers/rendering/rasterizer_rd/shaders/shadow_reduce.glsl.gen.h" +#include "servers/rendering/rasterizer_rd/shaders/sort.glsl.gen.h" #include "servers/rendering/rasterizer_rd/shaders/specular_merge.glsl.gen.h" #include "servers/rendering/rasterizer_rd/shaders/ssao.glsl.gen.h" #include "servers/rendering/rasterizer_rd/shaders/ssao_blur.glsl.gen.h" @@ -545,9 +546,28 @@ class RasterizerEffectsRD { struct ShadowReduce { ShadowReduceShaderRD shader; RID shader_version; - RID pipelines[2]; + RID pipelines[SHADOW_REDUCE_MAX]; } shadow_reduce; + enum SortMode { + SORT_MODE_BLOCK, + SORT_MODE_STEP, + SORT_MODE_INNER, + SORT_MODE_MAX + }; + + struct Sort { + struct PushConstant { + uint32_t total_elements; + uint32_t pad[3]; + int32_t job_params[4]; + }; + + SortShaderRD shader; + RID shader_version; + RID pipelines[SORT_MODE_MAX]; + } sort; + RID default_sampler; RID default_mipmap_sampler; RID index_buffer; @@ -650,6 +670,8 @@ public: void reduce_shadow(RID p_source_shadow, RID p_dest_shadow, const Size2i &p_source_size, const Rect2i &p_source_rect, int p_shrink_limit, RenderingDevice::ComputeListID compute_list); void filter_shadow(RID p_shadow, RID p_backing_shadow, const Size2i &p_source_size, const Rect2i &p_source_rect, RS::EnvVolumetricFogShadowFilter p_filter, RenderingDevice::ComputeListID compute_list, bool p_vertical = true, bool p_horizontal = true); + void sort_buffer(RID p_uniform_set, int p_size); + RasterizerEffectsRD(); ~RasterizerEffectsRD(); }; diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp index 11abb8f4a8..efa16628e4 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp @@ -782,8 +782,7 @@ void RasterizerSceneHighEndRD::_fill_instances(RenderList::Element **p_elements, for (int i = 0; i < p_element_count; i++) { const RenderList::Element *e = p_elements[i]; InstanceData &id = scene_state.instances[i]; - RasterizerStorageRD::store_transform(e->instance->transform, id.transform); - RasterizerStorageRD::store_transform(Transform(e->instance->transform.basis.inverse().transposed()), id.normal_transform); + bool store_transform = true; id.flags = 0; id.mask = e->instance->layer_mask; id.instance_uniforms_ofs = e->instance->instance_allocated_shader_parameters_offset >= 0 ? e->instance->instance_allocated_shader_parameters_offset : 0; @@ -807,12 +806,42 @@ void RasterizerSceneHighEndRD::_fill_instances(RenderList::Element **p_elements, } id.flags |= (stride << INSTANCE_DATA_FLAGS_MULTIMESH_STRIDE_SHIFT); + } else if (e->instance->base_type == RS::INSTANCE_PARTICLES) { + id.flags |= INSTANCE_DATA_FLAG_MULTIMESH; + uint32_t stride; + if (false) { // 2D particles + id.flags |= INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D; + stride = 2; + } else { + stride = 3; + } + + id.flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR; + stride += 1; + + id.flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA; + stride += 1; + + id.flags |= (stride << INSTANCE_DATA_FLAGS_MULTIMESH_STRIDE_SHIFT); + + if (!storage->particles_is_using_local_coords(e->instance->base)) { + store_transform = false; + } + } else if (e->instance->base_type == RS::INSTANCE_MESH) { if (e->instance->skeleton.is_valid()) { id.flags |= INSTANCE_DATA_FLAG_SKELETON; } } + if (store_transform) { + RasterizerStorageRD::store_transform(e->instance->transform, id.transform); + RasterizerStorageRD::store_transform(Transform(e->instance->transform.basis.inverse().transposed()), id.normal_transform); + } else { + RasterizerStorageRD::store_transform(Transform(), id.transform); + RasterizerStorageRD::store_transform(Transform(), id.normal_transform); + } + if (p_for_depth) { id.gi_offset = 0xFFFFFFFF; continue; @@ -967,7 +996,12 @@ void RasterizerSceneHighEndRD::_render_list(RenderingDevice::DrawListID p_draw_l ERR_CONTINUE(true); //should be a bug } break; case RS::INSTANCE_PARTICLES: { - ERR_CONTINUE(true); //should be a bug + RID mesh = storage->particles_get_draw_pass_mesh(e->instance->base, e->surface_index >> 16); + ERR_CONTINUE(!mesh.is_valid()); //should be a bug + primitive = storage->mesh_surface_get_primitive(mesh, e->surface_index & 0xFFFF); + + xforms_uniform_set = storage->particles_get_instance_buffer_uniform_set(e->instance->base, default_shader_rd, TRANSFORMS_UNIFORM_SET); + } break; default: { ERR_CONTINUE(true); //should be a bug @@ -1036,7 +1070,9 @@ void RasterizerSceneHighEndRD::_render_list(RenderingDevice::DrawListID p_draw_l ERR_CONTINUE(true); //should be a bug } break; case RS::INSTANCE_PARTICLES: { - ERR_CONTINUE(true); //should be a bug + RID mesh = storage->particles_get_draw_pass_mesh(e->instance->base, e->surface_index >> 16); + ERR_CONTINUE(!mesh.is_valid()); //should be a bug + storage->mesh_surface_get_arrays_and_format(mesh, e->surface_index & 0xFFFF, pipeline->get_vertex_input_mask(), vertex_array_rd, index_array_rd, vertex_format); } break; default: { ERR_CONTINUE(true); //should be a bug @@ -1092,6 +1128,8 @@ void RasterizerSceneHighEndRD::_render_list(RenderingDevice::DrawListID p_draw_l case RS::INSTANCE_IMMEDIATE: { } break; case RS::INSTANCE_PARTICLES: { + uint32_t instances = storage->particles_get_amount(e->instance->base); + RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instances); } break; default: { ERR_CONTINUE(true); //should be a bug @@ -1524,31 +1562,31 @@ void RasterizerSceneHighEndRD::_fill_render_list(InstanceBase **p_cull_result, i _add_geometry(immediate, inst, nullptr, -1, p_depth_pass, p_shadow_pass); } break; +#endif case RS::INSTANCE_PARTICLES: { + int draw_passes = storage->particles_get_draw_passes(inst->base); - RasterizerStorageGLES3::Particles *particles = storage->particles_owner.getornull(inst->base); - ERR_CONTINUE(!particles); - - for (int j = 0; j < particles->draw_passes.size(); j++) { - - RID pmesh = particles->draw_passes[j]; - if (!pmesh.is_valid()) + for (int j = 0; j < draw_passes; j++) { + RID mesh = storage->particles_get_draw_pass_mesh(inst->base, j); + if (!mesh.is_valid()) continue; - RasterizerStorageGLES3::Mesh *mesh = storage->mesh_owner.getornull(pmesh); - if (!mesh) - continue; //mesh not assigned - int ssize = mesh->surfaces.size(); + const RID *materials = nullptr; + uint32_t surface_count; - for (int k = 0; k < ssize; k++) { + materials = storage->mesh_get_surface_count_and_materials(mesh, surface_count); + if (!materials) { + continue; //nothing to do + } - RasterizerStorageGLES3::Surface *s = mesh->surfaces[k]; - _add_geometry(s, inst, particles, -1, p_depth_pass, p_shadow_pass); + for (uint32_t k = 0; k < surface_count; k++) { + uint32_t surface_index = storage->mesh_surface_get_particles_render_pass_index(mesh, j, render_pass, &geometry_index); + _add_geometry(inst, (j << 16) | k, materials[j], p_pass_mode, surface_index, p_using_sdfgi); } } } break; -#endif + default: { } } diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h index 7fe74a97f5..eb49233b98 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h @@ -359,7 +359,7 @@ private: mutable RID_Owner reflection_probe_instance_owner; - /* REFLECTION PROBE INSTANCE */ + /* DECAL INSTANCE */ struct DecalInstance { RID decal; diff --git a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp index f3ba57e733..e09926c97e 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp @@ -33,6 +33,7 @@ #include "core/engine.h" #include "core/io/resource_loader.h" #include "core/project_settings.h" +#include "rasterizer_rd.h" #include "servers/rendering/shader_language.h" Ref RasterizerStorageRD::_validate_texture_format(const Ref &p_image, TextureToRDFormat &r_format) { @@ -3098,8 +3099,771 @@ void RasterizerStorageRD::_update_dirty_multimeshes() { multimesh_dirty_list = nullptr; } -/* SKELETON */ +/* PARTICLES */ +RID RasterizerStorageRD::particles_create() { + return particles_owner.make_rid(Particles()); +} + +void RasterizerStorageRD::particles_set_emitting(RID p_particles, bool p_emitting) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->emitting = p_emitting; +} + +bool RasterizerStorageRD::particles_get_emitting(RID p_particles) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, false); + + return particles->emitting; +} + +void RasterizerStorageRD::particles_set_amount(RID p_particles, int p_amount) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->amount = p_amount; + + if (particles->particle_buffer.is_valid()) { + RD::get_singleton()->free(particles->particle_buffer); + RD::get_singleton()->free(particles->frame_params_buffer); + RD::get_singleton()->free(particles->particle_instance_buffer); + particles->particles_transforms_buffer_uniform_set = RID(); + particles->particle_buffer = RID(); + + if (particles->particles_sort_buffer.is_valid()) { + RD::get_singleton()->free(particles->particles_sort_buffer); + particles->particles_sort_buffer = RID(); + } + } + + if (particles->amount > 0) { + particles->particle_buffer = RD::get_singleton()->storage_buffer_create(sizeof(ParticleData) * p_amount); + particles->frame_params_buffer = RD::get_singleton()->storage_buffer_create(sizeof(ParticlesFrameParams) * 1); + particles->particle_instance_buffer = RD::get_singleton()->storage_buffer_create(sizeof(float) * 4 * (3 + 1 + 1) * p_amount); + //needs to clear it + + { + Vector uniforms; + + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 0; + u.ids.push_back(particles->frame_params_buffer); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 1; + u.ids.push_back(particles->particle_buffer); + uniforms.push_back(u); + } + + particles->particles_material_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.default_shader_rd, 1); + } + + { + Vector uniforms; + + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 1; + u.ids.push_back(particles->particle_buffer); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 2; + u.ids.push_back(particles->particle_instance_buffer); + uniforms.push_back(u); + } + + 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->prev_ticks = 0; + particles->phase = 0; + particles->prev_phase = 0; + particles->clear = true; +} + +void RasterizerStorageRD::particles_set_lifetime(RID p_particles, float p_lifetime) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + particles->lifetime = p_lifetime; +} + +void RasterizerStorageRD::particles_set_one_shot(RID p_particles, bool p_one_shot) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + particles->one_shot = p_one_shot; +} + +void RasterizerStorageRD::particles_set_pre_process_time(RID p_particles, float p_time) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + particles->pre_process_time = p_time; +} +void RasterizerStorageRD::particles_set_explosiveness_ratio(RID p_particles, float p_ratio) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + particles->explosiveness = p_ratio; +} +void RasterizerStorageRD::particles_set_randomness_ratio(RID p_particles, float p_ratio) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + particles->randomness = p_ratio; +} + +void RasterizerStorageRD::particles_set_custom_aabb(RID p_particles, const AABB &p_aabb) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + particles->custom_aabb = p_aabb; + particles->instance_dependency.instance_notify_changed(true, false); +} + +void RasterizerStorageRD::particles_set_speed_scale(RID p_particles, float p_scale) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->speed_scale = p_scale; +} +void RasterizerStorageRD::particles_set_use_local_coordinates(RID p_particles, bool p_enable) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->use_local_coords = p_enable; +} + +void RasterizerStorageRD::particles_set_fixed_fps(RID p_particles, int p_fps) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->fixed_fps = p_fps; +} + +void RasterizerStorageRD::particles_set_fractional_delta(RID p_particles, bool p_enable) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->fractional_delta = p_enable; +} + +void RasterizerStorageRD::particles_set_process_material(RID p_particles, RID p_material) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->process_material = p_material; +} + +void RasterizerStorageRD::particles_set_draw_order(RID p_particles, RS::ParticlesDrawOrder p_order) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->draw_order = p_order; +} + +void RasterizerStorageRD::particles_set_draw_passes(RID p_particles, int p_passes) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->draw_passes.resize(p_passes); +} + +void RasterizerStorageRD::particles_set_draw_pass_mesh(RID p_particles, int p_pass, RID p_mesh) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + ERR_FAIL_INDEX(p_pass, particles->draw_passes.size()); + particles->draw_passes.write[p_pass] = p_mesh; +} + +void RasterizerStorageRD::particles_restart(RID p_particles) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->restart_request = true; +} + +void RasterizerStorageRD::particles_request_process(RID p_particles) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + if (!particles->dirty) { + particles->dirty = true; + particles->update_list = particle_update_list; + particle_update_list = particles; + } +} + +AABB RasterizerStorageRD::particles_get_current_aabb(RID p_particles) { + const Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, AABB()); + + Vector data; + data.resize(particles->amount); + + Vector buffer = RD::get_singleton()->buffer_get_data(particles->particle_buffer); + + Transform inv = particles->emission_transform.affine_inverse(); + + AABB aabb; + if (buffer.size()) { + bool first = true; + const ParticleData *particle_data = (const ParticleData *)data.ptr(); + for (int i = 0; i < particles->amount; i++) { + if (particle_data[i].active) { + Vector3 pos = Vector3(particle_data[i].xform[12], particle_data[i].xform[13], particle_data[i].xform[14]); + if (!particles->use_local_coords) { + pos = inv.xform(pos); + } + if (first) { + aabb.position = pos; + first = false; + } else { + aabb.expand_to(pos); + } + } + } + } + + float longest_axis_size = 0; + for (int i = 0; i < particles->draw_passes.size(); i++) { + if (particles->draw_passes[i].is_valid()) { + AABB maabb = mesh_get_aabb(particles->draw_passes[i], RID()); + longest_axis_size = MAX(maabb.get_longest_axis_size(), longest_axis_size); + } + } + + aabb.grow_by(longest_axis_size); + + return aabb; +} + +AABB RasterizerStorageRD::particles_get_aabb(RID p_particles) const { + const Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, AABB()); + + return particles->custom_aabb; +} + +void RasterizerStorageRD::particles_set_emission_transform(RID p_particles, const Transform &p_transform) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + particles->emission_transform = p_transform; +} + +int RasterizerStorageRD::particles_get_draw_passes(RID p_particles) const { + const Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, 0); + + return particles->draw_passes.size(); +} + +RID RasterizerStorageRD::particles_get_draw_pass_mesh(RID p_particles, int p_pass) const { + const Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, RID()); + ERR_FAIL_INDEX_V(p_pass, particles->draw_passes.size(), RID()); + + return particles->draw_passes[p_pass]; +} + +void RasterizerStorageRD::_particles_process(Particles *p_particles, float p_delta) { + float new_phase = Math::fmod((float)p_particles->phase + (p_delta / p_particles->lifetime) * p_particles->speed_scale, (float)1.0); + + ParticlesFrameParams &frame_params = p_particles->frame_params; + + if (p_particles->clear) { + p_particles->cycle_number = 0; + p_particles->random_seed = Math::rand(); + } else if (new_phase < p_particles->phase) { + if (p_particles->one_shot) { + p_particles->emitting = false; + } + p_particles->cycle_number++; + } + + frame_params.emitting = p_particles->emitting; + frame_params.system_phase = new_phase; + frame_params.prev_system_phase = p_particles->phase; + + p_particles->phase = new_phase; + + frame_params.time = RasterizerRD::singleton->get_total_time(); + frame_params.delta = p_delta * p_particles->speed_scale; + frame_params.random_seed = p_particles->random_seed; + frame_params.explosiveness = p_particles->explosiveness; + frame_params.randomness = p_particles->randomness; + + if (p_particles->use_local_coords) { + store_transform(Transform(), frame_params.emission_transform); + } else { + store_transform(p_particles->emission_transform, frame_params.emission_transform); + } + + frame_params.cycle = p_particles->cycle_number; + + ParticlesShader::PushConstant push_constant; + + push_constant.clear = p_particles->clear; + push_constant.total_particles = p_particles->amount; + push_constant.lifetime = p_particles->lifetime; + push_constant.trail_size = 1; + push_constant.use_fractional_delta = p_particles->fractional_delta; + + p_particles->clear = false; + + RD::get_singleton()->buffer_update(p_particles->frame_params_buffer, 0, sizeof(ParticlesFrameParams), &frame_params, true); + + ParticlesMaterialData *m = (ParticlesMaterialData *)material_get_data(p_particles->process_material, SHADER_TYPE_PARTICLES); + if (!m) { + m = (ParticlesMaterialData *)material_get_data(particles_shader.default_material, SHADER_TYPE_PARTICLES); + } + + ERR_FAIL_COND(!m); + + //todo should maybe compute all particle systems together? + RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, m->shader_data->pipeline); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles_shader.base_uniform_set, 0); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, p_particles->particles_material_uniform_set, 1); + if (m->uniform_set.is_valid()) { + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, m->uniform_set, 2); + } + + RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(ParticlesShader::PushConstant)); + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, p_particles->amount, 1, 1, 64, 1, 1); + + RD::get_singleton()->compute_list_end(); +} + +void RasterizerStorageRD::particles_set_view_axis(RID p_particles, const Vector3 &p_axis) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + + if (particles->draw_order != RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH) { + return; //uninteresting for other modes + } + + //copy to sort buffer + if (particles->particles_sort_buffer == RID()) { + uint32_t size = particles->amount; + if (size & 1) { + size++; //make multiple of 16 + } + size *= sizeof(float) * 2; + particles->particles_sort_buffer = RD::get_singleton()->storage_buffer_create(size); + { + Vector uniforms; + + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 0; + u.ids.push_back(particles->particles_sort_buffer); + uniforms.push_back(u); + } + + particles->particles_sort_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.copy_shader.version_get_shader(particles_shader.copy_shader_version, ParticlesShader::COPY_MODE_FILL_SORT_BUFFER), 1); + } + } + + Vector3 axis = -p_axis; // cameras look to z negative + + if (particles->use_local_coords) { + axis = particles->emission_transform.basis.xform_inv(axis).normalized(); + } + + ParticlesShader::CopyPushConstant copy_push_constant; + copy_push_constant.total_particles = particles->amount; + copy_push_constant.sort_direction[0] = axis.x; + copy_push_constant.sort_direction[1] = axis.y; + copy_push_constant.sort_direction[2] = axis.z; + + RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, particles_shader.copy_pipelines[ParticlesShader::COPY_MODE_FILL_SORT_BUFFER]); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->particles_copy_uniform_set, 0); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->particles_sort_uniform_set, 1); + RD::get_singleton()->compute_list_set_push_constant(compute_list, ©_push_constant, sizeof(ParticlesShader::CopyPushConstant)); + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, particles->amount, 1, 1, 64, 1, 1); + + RD::get_singleton()->compute_list_end(); + + effects.sort_buffer(particles->particles_sort_uniform_set, particles->amount); + + compute_list = RD::get_singleton()->compute_list_begin(); + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, particles_shader.copy_pipelines[ParticlesShader::COPY_MODE_FILL_INSTANCES_WITH_SORT_BUFFER]); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->particles_copy_uniform_set, 0); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->particles_sort_uniform_set, 1); + RD::get_singleton()->compute_list_set_push_constant(compute_list, ©_push_constant, sizeof(ParticlesShader::CopyPushConstant)); + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, particles->amount, 1, 1, 64, 1, 1); + + RD::get_singleton()->compute_list_end(); +} + +void RasterizerStorageRD::update_particles() { + while (particle_update_list) { + //use transform feedback to process particles + + Particles *particles = particle_update_list; + + //take and remove + particle_update_list = particles->update_list; + particles->update_list = nullptr; + particles->dirty = false; + + if (particles->restart_request) { + particles->prev_ticks = 0; + particles->phase = 0; + particles->prev_phase = 0; + particles->clear = true; + particles->restart_request = false; + } + + if (particles->inactive && !particles->emitting) { + //go next + continue; + } + + if (particles->emitting) { + if (particles->inactive) { + //restart system from scratch + particles->prev_ticks = 0; + particles->phase = 0; + particles->prev_phase = 0; + particles->clear = true; + } + particles->inactive = false; + particles->inactive_time = 0; + } else { + particles->inactive_time += particles->speed_scale * RasterizerRD::singleton->get_frame_delta_time(); + if (particles->inactive_time > particles->lifetime * 1.2) { + particles->inactive = true; + continue; + } + } + + bool zero_time_scale = Engine::get_singleton()->get_time_scale() <= 0.0; + + if (particles->clear && particles->pre_process_time > 0.0) { + float frame_time; + if (particles->fixed_fps > 0) + frame_time = 1.0 / particles->fixed_fps; + else + frame_time = 1.0 / 30.0; + + float todo = particles->pre_process_time; + + while (todo >= 0) { + _particles_process(particles, frame_time); + todo -= frame_time; + } + } + + if (particles->fixed_fps > 0) { + float frame_time; + float decr; + if (zero_time_scale) { + frame_time = 0.0; + decr = 1.0 / particles->fixed_fps; + } else { + frame_time = 1.0 / particles->fixed_fps; + decr = frame_time; + } + float delta = RasterizerRD::singleton->get_frame_delta_time(); + if (delta > 0.1) { //avoid recursive stalls if fps goes below 10 + delta = 0.1; + } else if (delta <= 0.0) { //unlikely but.. + delta = 0.001; + } + float todo = particles->frame_remainder + delta; + + while (todo >= frame_time) { + _particles_process(particles, frame_time); + todo -= decr; + } + + particles->frame_remainder = todo; + + } else { + if (zero_time_scale) + _particles_process(particles, 0.0); + else + _particles_process(particles, RasterizerRD::singleton->get_frame_delta_time()); + } + + //copy particles to instance buffer + + if (particles->draw_order != RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH) { + ParticlesShader::CopyPushConstant copy_push_constant; + copy_push_constant.total_particles = particles->amount; + + RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, particles_shader.copy_pipelines[ParticlesShader::COPY_MODE_FILL_INSTANCES]); + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->particles_copy_uniform_set, 0); + RD::get_singleton()->compute_list_set_push_constant(compute_list, ©_push_constant, sizeof(ParticlesShader::CopyPushConstant)); + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, particles->amount, 1, 1, 64, 1, 1); + + RD::get_singleton()->compute_list_end(); + } + + particle_update_list = particles->update_list; + particles->update_list = nullptr; + + particles->instance_dependency.instance_notify_changed(true, false); //make sure shadows are updated + } +} + +bool RasterizerStorageRD::particles_is_inactive(RID p_particles) const { + const Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, false); + return !particles->emitting && particles->inactive; +} + +/* SKY SHADER */ + +void RasterizerStorageRD::ParticlesShaderData::set_code(const String &p_code) { + //compile + + code = p_code; + valid = false; + ubo_size = 0; + uniforms.clear(); + + if (code == String()) { + return; //just invalid, but no error + } + + ShaderCompilerRD::GeneratedCode gen_code; + ShaderCompilerRD::IdentifierActions actions; + + /* + uses_time = false; + + actions.render_mode_flags["use_half_res_pass"] = &uses_half_res; + actions.render_mode_flags["use_quarter_res_pass"] = &uses_quarter_res; + + actions.usage_flag_pointers["TIME"] = &uses_time; +*/ + + actions.uniforms = &uniforms; + + Error err = base_singleton->particles_shader.compiler.compile(RS::SHADER_PARTICLES, code, &actions, path, gen_code); + + ERR_FAIL_COND(err != OK); + + if (version.is_null()) { + version = base_singleton->particles_shader.shader.version_create(); + } + + base_singleton->particles_shader.shader.version_set_compute_code(version, gen_code.uniforms, gen_code.compute_global, gen_code.compute, gen_code.defines); + ERR_FAIL_COND(!base_singleton->particles_shader.shader.version_is_valid(version)); + + ubo_size = gen_code.uniform_total_size; + ubo_offsets = gen_code.uniform_offsets; + texture_uniforms = gen_code.texture_uniforms; + + //update pipelines + + pipeline = RD::get_singleton()->compute_pipeline_create(base_singleton->particles_shader.shader.version_get_shader(version, 0)); + + valid = true; +} + +void RasterizerStorageRD::ParticlesShaderData::set_default_texture_param(const StringName &p_name, RID p_texture) { + if (!p_texture.is_valid()) { + default_texture_params.erase(p_name); + } else { + default_texture_params[p_name] = p_texture; + } +} + +void RasterizerStorageRD::ParticlesShaderData::get_param_list(List *p_param_list) const { + Map order; + + for (Map::Element *E = uniforms.front(); E; E = E->next()) { + if (E->get().scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_GLOBAL || E->get().scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_INSTANCE) { + continue; + } + + if (E->get().texture_order >= 0) { + order[E->get().texture_order + 100000] = E->key(); + } else { + order[E->get().order] = E->key(); + } + } + + for (Map::Element *E = order.front(); E; E = E->next()) { + PropertyInfo pi = ShaderLanguage::uniform_to_property_info(uniforms[E->get()]); + pi.name = E->get(); + p_param_list->push_back(pi); + } +} + +void RasterizerStorageRD::ParticlesShaderData::get_instance_param_list(List *p_param_list) const { + for (Map::Element *E = uniforms.front(); E; E = E->next()) { + if (E->get().scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_INSTANCE) { + continue; + } + + RasterizerStorage::InstanceShaderParam p; + p.info = ShaderLanguage::uniform_to_property_info(E->get()); + p.info.name = E->key(); //supply name + p.index = E->get().instance_index; + p.default_value = ShaderLanguage::constant_value_to_variant(E->get().default_value, E->get().type, E->get().hint); + p_param_list->push_back(p); + } +} + +bool RasterizerStorageRD::ParticlesShaderData::is_param_texture(const StringName &p_param) const { + if (!uniforms.has(p_param)) { + return false; + } + + return uniforms[p_param].texture_order >= 0; +} + +bool RasterizerStorageRD::ParticlesShaderData::is_animated() const { + return false; +} + +bool RasterizerStorageRD::ParticlesShaderData::casts_shadows() const { + return false; +} + +Variant RasterizerStorageRD::ParticlesShaderData::get_default_parameter(const StringName &p_parameter) const { + if (uniforms.has(p_parameter)) { + ShaderLanguage::ShaderNode::Uniform uniform = uniforms[p_parameter]; + Vector default_value = uniform.default_value; + return ShaderLanguage::constant_value_to_variant(default_value, uniform.type, uniform.hint); + } + return Variant(); +} + +RasterizerStorageRD::ParticlesShaderData::ParticlesShaderData() { + valid = false; +} + +RasterizerStorageRD::ParticlesShaderData::~ParticlesShaderData() { + //pipeline variants will clear themselves if shader is gone + if (version.is_valid()) { + base_singleton->particles_shader.shader.version_free(version); + } +} + +RasterizerStorageRD::ShaderData *RasterizerStorageRD::_create_particles_shader_func() { + ParticlesShaderData *shader_data = memnew(ParticlesShaderData); + return shader_data; +} + +void RasterizerStorageRD::ParticlesMaterialData::update_parameters(const Map &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) { + uniform_set_updated = true; + + if ((uint32_t)ubo_data.size() != shader_data->ubo_size) { + p_uniform_dirty = true; + if (uniform_buffer.is_valid()) { + RD::get_singleton()->free(uniform_buffer); + uniform_buffer = RID(); + } + + ubo_data.resize(shader_data->ubo_size); + if (ubo_data.size()) { + uniform_buffer = RD::get_singleton()->uniform_buffer_create(ubo_data.size()); + memset(ubo_data.ptrw(), 0, ubo_data.size()); //clear + } + + //clear previous uniform set + if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { + RD::get_singleton()->free(uniform_set); + uniform_set = RID(); + } + } + + //check whether buffer changed + if (p_uniform_dirty && ubo_data.size()) { + update_uniform_buffer(shader_data->uniforms, shader_data->ubo_offsets.ptr(), p_parameters, ubo_data.ptrw(), ubo_data.size(), false); + RD::get_singleton()->buffer_update(uniform_buffer, 0, ubo_data.size(), ubo_data.ptrw()); + } + + uint32_t tex_uniform_count = shader_data->texture_uniforms.size(); + + if ((uint32_t)texture_cache.size() != tex_uniform_count) { + texture_cache.resize(tex_uniform_count); + p_textures_dirty = true; + + //clear previous uniform set + if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { + RD::get_singleton()->free(uniform_set); + uniform_set = RID(); + } + } + + if (p_textures_dirty && tex_uniform_count) { + update_textures(p_parameters, shader_data->default_texture_params, shader_data->texture_uniforms, texture_cache.ptrw(), true); + } + + if (shader_data->ubo_size == 0 && shader_data->texture_uniforms.size() == 0) { + // This material does not require an uniform set, so don't create it. + return; + } + + if (!p_textures_dirty && uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { + //no reason to update uniform set, only UBO (or nothing) was needed to update + return; + } + + Vector uniforms; + + { + if (shader_data->ubo_size) { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; + u.binding = 0; + u.ids.push_back(uniform_buffer); + uniforms.push_back(u); + } + + const RID *textures = texture_cache.ptrw(); + for (uint32_t i = 0; i < tex_uniform_count; i++) { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1 + i; + u.ids.push_back(textures[i]); + uniforms.push_back(u); + } + } + + uniform_set = RD::get_singleton()->uniform_set_create(uniforms, base_singleton->particles_shader.shader.version_get_shader(shader_data->version, 0), 2); +} + +RasterizerStorageRD::ParticlesMaterialData::~ParticlesMaterialData() { + if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { + RD::get_singleton()->free(uniform_set); + } + + if (uniform_buffer.is_valid()) { + RD::get_singleton()->free(uniform_buffer); + } +} + +RasterizerStorageRD::MaterialData *RasterizerStorageRD::_create_particles_material_func(ParticlesShaderData *p_shader) { + ParticlesMaterialData *material_data = memnew(ParticlesMaterialData); + material_data->shader_data = p_shader; + material_data->last_frame = false; + //update will happen later anyway so do nothing. + return material_data; +} +//////// /* SKELETON API */ RID RasterizerStorageRD::skeleton_create() { @@ -4683,6 +5447,9 @@ void RasterizerStorageRD::base_update_dependency(RID p_base, RasterizerScene::In } else if (light_owner.owns(p_base)) { Light *l = light_owner.getornull(p_base); p_instance->update_dependency(&l->instance_dependency); + } else if (particles_owner.owns(p_base)) { + Particles *p = particles_owner.getornull(p_base); + p_instance->update_dependency(&p->instance_dependency); } } @@ -4715,6 +5482,9 @@ RS::InstanceType RasterizerStorageRD::get_base_type(RID p_rid) const { if (lightmap_owner.owns(p_rid)) { return RS::INSTANCE_LIGHTMAP; } + if (particles_owner.owns(p_rid)) { + return RS::INSTANCE_PARTICLES; + } return RS::INSTANCE_NONE; } @@ -5618,6 +6388,8 @@ void RasterizerStorageRD::update_dirty_resources() { _update_dirty_multimeshes(); _update_dirty_skeletons(); _update_decal_atlas(); + + update_particles(); } bool RasterizerStorageRD::has_os_feature(const String &p_feature) const { @@ -6211,6 +6983,112 @@ RasterizerStorageRD::RasterizerStorageRD() { } lightmap_probe_capture_update_speed = GLOBAL_GET("rendering/lightmapper/probe_capture_update_speed"); + + /* Particles */ + + { + // Initialize particles + Vector particles_modes; + particles_modes.push_back(""); + particles_shader.shader.initialize(particles_modes, String()); + } + shader_set_data_request_function(RasterizerStorageRD::SHADER_TYPE_PARTICLES, _create_particles_shader_funcs); + material_set_data_request_function(RasterizerStorageRD::SHADER_TYPE_PARTICLES, _create_particles_material_funcs); + + { + ShaderCompilerRD::DefaultIdentifierActions actions; + + actions.renames["COLOR"] = "PARTICLE.color"; + actions.renames["VELOCITY"] = "PARTICLE.velocity"; + //actions.renames["MASS"] = "mass"; ? + actions.renames["ACTIVE"] = "PARTICLE.is_active"; + actions.renames["RESTART"] = "restart"; + actions.renames["CUSTOM"] = "PARTICLE.custom"; + actions.renames["TRANSFORM"] = "PARTICLE.xform"; + actions.renames["TIME"] = "FRAME.time"; + actions.renames["LIFETIME"] = "params.lifetime"; + actions.renames["DELTA"] = "local_delta"; + actions.renames["NUMBER"] = "particle"; + actions.renames["INDEX"] = "index"; + //actions.renames["GRAVITY"] = "current_gravity"; + actions.renames["EMISSION_TRANSFORM"] = "FRAME.emission_transform"; + actions.renames["RANDOM_SEED"] = "FRAME.random_seed"; + + actions.render_mode_defines["disable_force"] = "#define DISABLE_FORCE\n"; + actions.render_mode_defines["disable_velocity"] = "#define DISABLE_VELOCITY\n"; + actions.render_mode_defines["keep_data"] = "#define ENABLE_KEEP_DATA\n"; + + actions.sampler_array_name = "material_samplers"; + actions.base_texture_binding_index = 1; + actions.texture_layout_set = 2; + actions.base_uniform_string = "material."; + actions.base_varying_index = 10; + + actions.default_filter = ShaderLanguage::FILTER_LINEAR_MIPMAP; + actions.default_repeat = ShaderLanguage::REPEAT_ENABLE; + actions.global_buffer_array_variable = "global_variables.data"; + + particles_shader.compiler.initialize(actions); + } + + { + // default material and shader for particles shader + particles_shader.default_shader = shader_create(); + shader_set_code(particles_shader.default_shader, "shader_type particles; void compute() { COLOR = vec4(1.0); } \n"); + particles_shader.default_material = material_create(); + material_set_shader(particles_shader.default_material, particles_shader.default_shader); + + ParticlesMaterialData *md = (ParticlesMaterialData *)material_get_data(particles_shader.default_material, RasterizerStorageRD::SHADER_TYPE_PARTICLES); + particles_shader.default_shader_rd = particles_shader.shader.version_get_shader(md->shader_data->version, 0); + + Vector uniforms; + + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_SAMPLER; + u.binding = 1; + u.ids.resize(12); + RID *ids_ptr = u.ids.ptrw(); + ids_ptr[0] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED); + ids_ptr[1] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED); + ids_ptr[2] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED); + ids_ptr[3] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED); + ids_ptr[4] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED); + ids_ptr[5] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED); + ids_ptr[6] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED); + ids_ptr[7] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED); + ids_ptr[8] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED); + ids_ptr[9] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED); + ids_ptr[10] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED); + ids_ptr[11] = sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED); + uniforms.push_back(u); + } + + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 2; + u.ids.push_back(global_variables_get_storage_buffer()); + uniforms.push_back(u); + } + + particles_shader.base_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.default_shader_rd, 0); + } + + { + Vector copy_modes; + copy_modes.push_back("\n#define MODE_FILL_INSTANCES\n"); + copy_modes.push_back("\n#define MODE_FILL_SORT_BUFFER\n#define USE_SORT_BUFFER\n"); + copy_modes.push_back("\n#define MODE_FILL_INSTANCES\n#define USE_SORT_BUFFER\n"); + + particles_shader.copy_shader.initialize(copy_modes); + + particles_shader.copy_shader_version = particles_shader.copy_shader.version_create(); + + for (int i = 0; i < ParticlesShader::COPY_MODE_MAX; i++) { + particles_shader.copy_pipelines[i] = RD::get_singleton()->compute_pipeline_create(particles_shader.copy_shader.version_get_shader(particles_shader.copy_shader_version, i)); + } + } } RasterizerStorageRD::~RasterizerStorageRD() { diff --git a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h index 6e5923953b..e09b4a52eb 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h @@ -36,6 +36,8 @@ #include "servers/rendering/rasterizer_rd/rasterizer_effects_rd.h" #include "servers/rendering/rasterizer_rd/shader_compiler_rd.h" #include "servers/rendering/rasterizer_rd/shaders/giprobe_sdf.glsl.gen.h" +#include "servers/rendering/rasterizer_rd/shaders/particles.glsl.gen.h" +#include "servers/rendering/rasterizer_rd/shaders/particles_copy.glsl.gen.h" #include "servers/rendering/rendering_device.h" class RasterizerStorageRD : public RasterizerStorage { @@ -386,6 +388,9 @@ private: uint32_t multimesh_render_index = 0; uint64_t multimesh_render_pass = 0; + + uint32_t particles_render_index = 0; + uint64_t particles_render_pass = 0; }; uint32_t blend_shape_count = 0; @@ -448,6 +453,215 @@ private: _FORCE_INLINE_ void _multimesh_re_create_aabb(MultiMesh *multimesh, const float *p_data, int p_instances); void _update_dirty_multimeshes(); + /* PARTICLES */ + + struct ParticleData { + float xform[16]; + float velocity[3]; + uint32_t active; + float color[4]; + float custom[3]; + float lifetime; + uint32_t pad[3]; + }; + + struct ParticlesFrameParams { + uint32_t emitting; + float system_phase; + float prev_system_phase; + uint32_t cycle; + + float explosiveness; + float randomness; + float time; + float delta; + + uint32_t random_seed; + uint32_t pad[3]; + + float emission_transform[16]; + }; + + struct Particles { + bool inactive; + float inactive_time; + bool emitting; + bool one_shot; + int amount; + float lifetime; + float pre_process_time; + float explosiveness; + float randomness; + bool restart_request; + AABB custom_aabb; + bool use_local_coords; + RID process_material; + + RS::ParticlesDrawOrder draw_order; + + Vector draw_passes; + + RID particle_buffer; + RID particle_instance_buffer; + RID frame_params_buffer; + + RID particles_material_uniform_set; + RID particles_copy_uniform_set; + RID particles_transforms_buffer_uniform_set; + + RID particles_sort_buffer; + RID particles_sort_uniform_set; + + bool dirty = false; + Particles *update_list = nullptr; + + float phase; + float prev_phase; + uint64_t prev_ticks; + uint32_t random_seed; + + uint32_t cycle_number; + + float speed_scale; + + int fixed_fps; + bool fractional_delta; + float frame_remainder; + + bool clear; + + Transform emission_transform; + + Particles() : + inactive(true), + inactive_time(0.0), + emitting(false), + one_shot(false), + amount(0), + lifetime(1.0), + pre_process_time(0.0), + explosiveness(0.0), + randomness(0.0), + restart_request(false), + custom_aabb(AABB(Vector3(-4, -4, -4), Vector3(8, 8, 8))), + use_local_coords(true), + draw_order(RS::PARTICLES_DRAW_ORDER_INDEX), + prev_ticks(0), + random_seed(0), + cycle_number(0), + speed_scale(1.0), + fixed_fps(0), + fractional_delta(false), + frame_remainder(0), + clear(true) { + } + + RasterizerScene::InstanceDependency instance_dependency; + + ParticlesFrameParams frame_params; + }; + + void _particles_process(Particles *p_particles, float p_delta); + + struct ParticlesShader { + struct PushConstant { + float lifetime; + uint32_t clear; + uint32_t total_particles; + uint32_t trail_size; + uint32_t use_fractional_delta; + uint32_t pad[3]; + }; + + ParticlesShaderRD shader; + ShaderCompilerRD compiler; + + RID default_shader; + RID default_material; + RID default_shader_rd; + + RID base_uniform_set; + + struct CopyPushConstant { + float sort_direction[3]; + uint32_t total_particles; + }; + + enum { + COPY_MODE_FILL_INSTANCES, + COPY_MODE_FILL_SORT_BUFFER, + COPY_MODE_FILL_INSTANCES_WITH_SORT_BUFFER, + COPY_MODE_MAX, + }; + + ParticlesCopyShaderRD copy_shader; + RID copy_shader_version; + RID copy_pipelines[COPY_MODE_MAX]; + + } particles_shader; + + Particles *particle_update_list = nullptr; + + struct ParticlesShaderData : public ShaderData { + bool valid; + RID version; + + //RenderPipelineVertexFormatCacheRD pipelines[SKY_VERSION_MAX]; + Map uniforms; + Vector texture_uniforms; + + Vector ubo_offsets; + uint32_t ubo_size; + + String path; + String code; + Map default_texture_params; + + RID pipeline; + + bool uses_time; + + virtual void set_code(const String &p_Code); + virtual void set_default_texture_param(const StringName &p_name, RID p_texture); + virtual void get_param_list(List *p_param_list) const; + virtual void get_instance_param_list(List *p_param_list) const; + virtual bool is_param_texture(const StringName &p_param) const; + virtual bool is_animated() const; + virtual bool casts_shadows() const; + virtual Variant get_default_parameter(const StringName &p_parameter) const; + ParticlesShaderData(); + virtual ~ParticlesShaderData(); + }; + + ShaderData *_create_particles_shader_func(); + static RasterizerStorageRD::ShaderData *_create_particles_shader_funcs() { + return base_singleton->_create_particles_shader_func(); + } + + struct ParticlesMaterialData : public MaterialData { + uint64_t last_frame; + ParticlesShaderData *shader_data; + RID uniform_buffer; + RID uniform_set; + Vector texture_cache; + Vector ubo_data; + bool uniform_set_updated; + + virtual void set_render_priority(int p_priority) {} + virtual void set_next_pass(RID p_pass) {} + virtual void update_parameters(const Map &p_parameters, bool p_uniform_dirty, bool p_textures_dirty); + virtual ~ParticlesMaterialData(); + }; + + MaterialData *_create_particles_material_func(ParticlesShaderData *p_shader); + static RasterizerStorageRD::MaterialData *_create_particles_material_funcs(ShaderData *p_shader) { + return base_singleton->_create_particles_material_func(static_cast(p_shader)); + } + + void update_particles(); + + mutable RID_Owner particles_owner; + /* Skeleton */ struct Skeleton { @@ -977,6 +1191,19 @@ public: return s->multimesh_render_index; } + _FORCE_INLINE_ uint32_t mesh_surface_get_particles_render_pass_index(RID p_mesh, uint32_t p_surface_index, uint64_t p_render_pass, uint32_t *r_index) { + Mesh *mesh = mesh_owner.getornull(p_mesh); + Mesh::Surface *s = mesh->surfaces[p_surface_index]; + + if (s->particles_render_pass != p_render_pass) { + (*r_index)++; + s->particles_render_pass = p_render_pass; + s->particles_render_index = *r_index; + } + + return s->particles_render_index; + } + /* MULTIMESH API */ RID multimesh_create(); @@ -1407,39 +1634,75 @@ public: /* PARTICLES */ - RID particles_create() { return RID(); } + RID particles_create(); - void particles_set_emitting(RID p_particles, bool p_emitting) {} - void particles_set_amount(RID p_particles, int p_amount) {} - void particles_set_lifetime(RID p_particles, float p_lifetime) {} - void particles_set_one_shot(RID p_particles, bool p_one_shot) {} - void particles_set_pre_process_time(RID p_particles, float p_time) {} - void particles_set_explosiveness_ratio(RID p_particles, float p_ratio) {} - void particles_set_randomness_ratio(RID p_particles, float p_ratio) {} - void particles_set_custom_aabb(RID p_particles, const AABB &p_aabb) {} - void particles_set_speed_scale(RID p_particles, float p_scale) {} - void particles_set_use_local_coordinates(RID p_particles, bool p_enable) {} - void particles_set_process_material(RID p_particles, RID p_material) {} - void particles_set_fixed_fps(RID p_particles, int p_fps) {} - void particles_set_fractional_delta(RID p_particles, bool p_enable) {} - void particles_restart(RID p_particles) {} + void particles_set_emitting(RID p_particles, bool p_emitting); + void particles_set_amount(RID p_particles, int p_amount); + void particles_set_lifetime(RID p_particles, float p_lifetime); + void particles_set_one_shot(RID p_particles, bool p_one_shot); + void particles_set_pre_process_time(RID p_particles, float p_time); + void particles_set_explosiveness_ratio(RID p_particles, float p_ratio); + void particles_set_randomness_ratio(RID p_particles, float p_ratio); + void particles_set_custom_aabb(RID p_particles, const AABB &p_aabb); + void particles_set_speed_scale(RID p_particles, float p_scale); + void particles_set_use_local_coordinates(RID p_particles, bool p_enable); + void particles_set_process_material(RID p_particles, RID p_material); + void particles_set_fixed_fps(RID p_particles, int p_fps); + void particles_set_fractional_delta(RID p_particles, bool p_enable); + void particles_restart(RID p_particles); - void particles_set_draw_order(RID p_particles, RS::ParticlesDrawOrder p_order) {} + void particles_set_draw_order(RID p_particles, RS::ParticlesDrawOrder p_order); - void particles_set_draw_passes(RID p_particles, int p_count) {} - void particles_set_draw_pass_mesh(RID p_particles, int p_pass, RID p_mesh) {} + void particles_set_draw_passes(RID p_particles, int p_count); + void particles_set_draw_pass_mesh(RID p_particles, int p_pass, RID p_mesh); - void particles_request_process(RID p_particles) {} - AABB particles_get_current_aabb(RID p_particles) { return AABB(); } - AABB particles_get_aabb(RID p_particles) const { return AABB(); } + void particles_request_process(RID p_particles); + AABB particles_get_current_aabb(RID p_particles); + AABB particles_get_aabb(RID p_particles) const; - void particles_set_emission_transform(RID p_particles, const Transform &p_transform) {} + void particles_set_emission_transform(RID p_particles, const Transform &p_transform); - bool particles_get_emitting(RID p_particles) { return false; } - int particles_get_draw_passes(RID p_particles) const { return 0; } - RID particles_get_draw_pass_mesh(RID p_particles, int p_pass) const { return RID(); } + bool particles_get_emitting(RID p_particles); + int particles_get_draw_passes(RID p_particles) const; + RID particles_get_draw_pass_mesh(RID p_particles, int p_pass) const; - virtual bool particles_is_inactive(RID p_particles) const { return false; } + void particles_set_view_axis(RID p_particles, const Vector3 &p_axis); + + virtual bool particles_is_inactive(RID p_particles) const; + + _FORCE_INLINE_ uint32_t particles_get_amount(RID p_particles) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, 0); + + return particles->amount; + } + + _FORCE_INLINE_ uint32_t particles_is_using_local_coords(RID p_particles) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, false); + + return particles->use_local_coords; + } + + _FORCE_INLINE_ RID particles_get_instance_buffer_uniform_set(RID p_particles, RID p_shader, uint32_t p_set) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND_V(!particles, RID()); + if (particles->particles_transforms_buffer_uniform_set.is_null()) { + Vector uniforms; + + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 0; + u.ids.push_back(particles->particle_instance_buffer); + uniforms.push_back(u); + } + + particles->particles_transforms_buffer_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, p_shader, p_set); + } + + return particles->particles_transforms_buffer_uniform_set; + } /* GLOBAL VARIABLES API */ diff --git a/servers/rendering/rasterizer_rd/shader_compiler_rd.cpp b/servers/rendering/rasterizer_rd/shader_compiler_rd.cpp index 1820c39c5a..f70ddbb75a 100644 --- a/servers/rendering/rasterizer_rd/shader_compiler_rd.cpp +++ b/servers/rendering/rasterizer_rd/shader_compiler_rd.cpp @@ -537,6 +537,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge r_gen_code.vertex_global += struct_code; r_gen_code.fragment_global += struct_code; + r_gen_code.compute_global += struct_code; } int max_texture_uniforms = 0; @@ -591,6 +592,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge if (SL::is_sampler_type(E->get().type)) { r_gen_code.vertex_global += ucode; r_gen_code.fragment_global += ucode; + r_gen_code.compute_global += ucode; GeneratedCode::Texture texture; texture.name = E->key(); @@ -700,6 +702,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge vcode += ";\n"; r_gen_code.vertex_global += "layout(location=" + itos(index) + ") " + interp_mode + "out " + vcode; r_gen_code.fragment_global += "layout(location=" + itos(index) + ") " + interp_mode + "in " + vcode; + r_gen_code.compute_global += "layout(location=" + itos(index) + ") " + interp_mode + "out " + vcode; index++; } @@ -724,6 +727,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge gcode += ";\n"; r_gen_code.vertex_global += gcode; r_gen_code.fragment_global += gcode; + r_gen_code.compute_global += gcode; } Map function_code; @@ -741,6 +745,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge Set added_vtx; Set added_fragment; //share for light + Set added_compute; //share for light for (int i = 0; i < pnode->functions.size(); i++) { SL::FunctionNode *fnode = pnode->functions[i].function; @@ -763,6 +768,12 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge _dump_function_deps(pnode, fnode->name, function_code, r_gen_code.fragment_global, added_fragment); r_gen_code.light = function_code[light_name]; } + + if (fnode->name == compute_name) { + _dump_function_deps(pnode, fnode->name, function_code, r_gen_code.compute_global, added_compute); + r_gen_code.compute = function_code[compute_name]; + } + function = nullptr; } @@ -1245,6 +1256,8 @@ Error ShaderCompilerRD::compile(RS::ShaderMode p_mode, const String &p_code, Ide r_gen_code.vertex_global = String(); r_gen_code.fragment = String(); r_gen_code.fragment_global = String(); + r_gen_code.compute = String(); + r_gen_code.compute_global = String(); r_gen_code.light = String(); r_gen_code.uses_fragment_time = false; r_gen_code.uses_vertex_time = false; @@ -1266,6 +1279,7 @@ void ShaderCompilerRD::initialize(DefaultIdentifierActions p_actions) { vertex_name = "vertex"; fragment_name = "fragment"; + compute_name = "compute"; light_name = "light"; time_name = "TIME"; diff --git a/servers/rendering/rasterizer_rd/shader_compiler_rd.h b/servers/rendering/rasterizer_rd/shader_compiler_rd.h index ce94fb743f..565520ec65 100644 --- a/servers/rendering/rasterizer_rd/shader_compiler_rd.h +++ b/servers/rendering/rasterizer_rd/shader_compiler_rd.h @@ -68,6 +68,8 @@ public: String fragment_global; String fragment; String light; + String compute_global; + String compute; bool uses_global_textures; bool uses_fragment_time; @@ -104,6 +106,7 @@ private: StringName vertex_name; StringName fragment_name; StringName light_name; + StringName compute_name; StringName time_name; Set texture_functions; diff --git a/servers/rendering/rasterizer_rd/shaders/SCsub b/servers/rendering/rasterizer_rd/shaders/SCsub index 3aa863be98..9d531d63ad 100644 --- a/servers/rendering/rasterizer_rd/shaders/SCsub +++ b/servers/rendering/rasterizer_rd/shaders/SCsub @@ -37,3 +37,6 @@ if "RD_GLSL" in env["BUILDERS"]: env.RD_GLSL("sdfgi_debug_probes.glsl") env.RD_GLSL("volumetric_fog.glsl") env.RD_GLSL("shadow_reduce.glsl") + env.RD_GLSL("particles.glsl") + env.RD_GLSL("particles_copy.glsl") + env.RD_GLSL("sort.glsl") diff --git a/servers/rendering/rasterizer_rd/shaders/particles.glsl b/servers/rendering/rasterizer_rd/shaders/particles.glsl new file mode 100644 index 0000000000..7cdedfcbfe --- /dev/null +++ b/servers/rendering/rasterizer_rd/shaders/particles.glsl @@ -0,0 +1,262 @@ +#[compute] + +#version 450 + +VERSION_DEFINES + +layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +#define SAMPLER_NEAREST_CLAMP 0 +#define SAMPLER_LINEAR_CLAMP 1 +#define SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP 2 +#define SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP 3 +#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_CLAMP 4 +#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_CLAMP 5 +#define SAMPLER_NEAREST_REPEAT 6 +#define SAMPLER_LINEAR_REPEAT 7 +#define SAMPLER_NEAREST_WITH_MIPMAPS_REPEAT 8 +#define SAMPLER_LINEAR_WITH_MIPMAPS_REPEAT 9 +#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT 10 +#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT 11 + +/* SET 0: GLOBAL DATA */ + +layout(set = 0, binding = 1) uniform sampler material_samplers[12]; + +layout(set = 0, binding = 2, std430) restrict readonly buffer GlobalVariableData { + vec4 data[]; +} +global_variables; + +/* Set 1: FRAME AND PARTICLE DATA */ + +// a frame history is kept for trail deterministic behavior +struct FrameParams { + bool emitting; + float system_phase; + float prev_system_phase; + uint cycle; + + float explosiveness; + float randomness; + float time; + float delta; + + uint random_seed; + uint pad[3]; + + mat4 emission_transform; +}; + +layout(set = 1, binding = 0, std430) restrict buffer FrameHistory { + FrameParams data[]; +} +frame_history; + +struct ParticleData { + mat4 xform; + vec3 velocity; + bool is_active; + vec4 color; + vec4 custom; +}; + +layout(set = 1, binding = 1, std430) restrict buffer Particles { + ParticleData data[]; +} +particles; + +/* SET 2: MATERIAL */ + +#ifdef USE_MATERIAL_UNIFORMS +layout(set = 2, binding = 0, std140) uniform MaterialUniforms{ + /* clang-format off */ +MATERIAL_UNIFORMS + /* clang-format on */ +} material; +#endif + +layout(push_constant, binding = 0, std430) uniform Params { + float lifetime; + bool clear; + uint total_particles; + uint trail_size; + bool use_fractional_delta; + uint pad[3]; +} +params; + +uint hash(uint x) { + x = ((x >> uint(16)) ^ x) * uint(0x45d9f3b); + x = ((x >> uint(16)) ^ x) * uint(0x45d9f3b); + x = (x >> uint(16)) ^ x; + return x; +} + +/* clang-format off */ + +COMPUTE_SHADER_GLOBALS + +/* clang-format on */ + +void main() { + uint particle = gl_GlobalInvocationID.x; + + if (particle >= params.total_particles * params.trail_size) { + return; //discard + } + + uint index = particle / params.trail_size; + uint frame = (particle % params.trail_size); + +#define FRAME frame_history.data[frame] +#define PARTICLE particles.data[particle] + + bool apply_forces = true; + bool apply_velocity = true; + float local_delta = FRAME.delta; + + float mass = 1.0; + + float restart_phase = float(index) / float(params.total_particles); + + if (FRAME.randomness > 0.0) { + uint seed = FRAME.cycle; + if (restart_phase >= FRAME.system_phase) { + seed -= uint(1); + } + seed *= uint(params.total_particles); + seed += uint(index); + float random = float(hash(seed) % uint(65536)) / 65536.0; + restart_phase += FRAME.randomness * random * 1.0 / float(params.total_particles); + } + + restart_phase *= (1.0 - FRAME.explosiveness); + + bool restart = false; + + if (FRAME.system_phase > FRAME.prev_system_phase) { + // restart_phase >= prev_system_phase is used so particles emit in the first frame they are processed + + if (restart_phase >= FRAME.prev_system_phase && restart_phase < FRAME.system_phase) { + restart = true; + if (params.use_fractional_delta) { + local_delta = (FRAME.system_phase - restart_phase) * params.lifetime; + } + } + + } else if (FRAME.delta > 0.0) { + if (restart_phase >= FRAME.prev_system_phase) { + restart = true; + if (params.use_fractional_delta) { + local_delta = (1.0 - restart_phase + FRAME.system_phase) * params.lifetime; + } + + } else if (restart_phase < FRAME.system_phase) { + restart = true; + if (params.use_fractional_delta) { + local_delta = (FRAME.system_phase - restart_phase) * params.lifetime; + } + } + } + + uint current_cycle = FRAME.cycle; + + if (FRAME.system_phase < restart_phase) { + current_cycle -= uint(1); + } + + uint particle_number = current_cycle * uint(params.total_particles) + particle; + + if (restart) { + PARTICLE.is_active = FRAME.emitting; + } + +#ifdef ENABLE_KEEP_DATA + if (params.clear) { +#else + if (params.clear || restart) { +#endif + PARTICLE.color = vec4(1.0); + PARTICLE.custom = vec4(0.0); + PARTICLE.velocity = vec3(0.0); + if (!restart) { + PARTICLE.is_active = false; + } + PARTICLE.xform = mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0)); + } + + if (PARTICLE.is_active) { + /* clang-format off */ + +COMPUTE_SHADER_CODE + + /* clang-format on */ + } + +#if !defined(DISABLE_VELOCITY) + + if (PARTICLE.is_active) { + PARTICLE.xform[3].xyz += PARTICLE.velocity * local_delta; + } +#endif + +#if 0 + if (PARTICLE.is_active) { + //execute shader + + + + + //!defined(DISABLE_FORCE) + + if (false) { + vec3 force = vec3(0.0); + for (int i = 0; i < attractor_count; i++) { + vec3 rel_vec = xform[3].xyz - attractors[i].pos; + float dist = length(rel_vec); + if (attractors[i].radius < dist) + continue; + if (attractors[i].eat_radius > 0.0 && attractors[i].eat_radius > dist) { + out_velocity_active.a = 0.0; + } + + rel_vec = normalize(rel_vec); + + float attenuation = pow(dist / attractors[i].radius, attractors[i].attenuation); + + if (attractors[i].dir == vec3(0.0)) { + //towards center + force += attractors[i].strength * rel_vec * attenuation * mass; + } else { + force += attractors[i].strength * attractors[i].dir * attenuation * mass; + } + } + + out_velocity_active.xyz += force * local_delta; + } + +#if !defined(DISABLE_VELOCITY) + + if (true) { + xform[3].xyz += out_velocity_active.xyz * local_delta; + } +#endif + } else { + xform = mat4(0.0); + } + + + xform = transpose(xform); + + out_velocity_active.a = mix(0.0, 1.0, shader_active); + + out_xform_1 = xform[0]; + out_xform_2 = xform[1]; + out_xform_3 = xform[2]; +#endif +} diff --git a/servers/rendering/rasterizer_rd/shaders/particles_copy.glsl b/servers/rendering/rasterizer_rd/shaders/particles_copy.glsl new file mode 100644 index 0000000000..6c782b6045 --- /dev/null +++ b/servers/rendering/rasterizer_rd/shaders/particles_copy.glsl @@ -0,0 +1,82 @@ +#[compute] + +#version 450 + +VERSION_DEFINES + +layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +struct ParticleData { + mat4 xform; + vec3 velocity; + bool is_active; + vec4 color; + vec4 custom; +}; + +layout(set = 0, binding = 1, std430) restrict readonly buffer Particles { + ParticleData data[]; +} +particles; + +layout(set = 0, binding = 2, std430) restrict writeonly buffer Transforms { + vec4 data[]; +} +instances; + +#ifdef USE_SORT_BUFFER + +layout(set = 1, binding = 0, std430) restrict buffer SortBuffer { + vec2 data[]; +} +sort_buffer; + +#endif // USE_SORT_BUFFER + +layout(push_constant, binding = 0, std430) uniform Params { + vec3 sort_direction; + uint total_particles; +} +params; + +void main() { +#ifdef MODE_FILL_SORT_BUFFER + + uint particle = gl_GlobalInvocationID.x; + if (particle >= params.total_particles) { + return; //discard + } + + sort_buffer.data[particle].x = dot(params.sort_direction, particles.data[particle].xform[3].xyz); + sort_buffer.data[particle].y = float(particle); +#endif + +#ifdef MODE_FILL_INSTANCES + + uint particle = gl_GlobalInvocationID.x; + uint write_offset = gl_GlobalInvocationID.x * (3 + 1 + 1); //xform + color + custom + + if (particle >= params.total_particles) { + return; //discard + } + +#ifdef USE_SORT_BUFFER + particle = uint(sort_buffer.data[particle].y); //use index from sort buffer +#endif + + mat4 txform; + + if (particles.data[particle].is_active) { + txform = transpose(particles.data[particle].xform); + } else { + txform = mat4(vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0)); //zero scale, becomes invisible + } + + instances.data[write_offset + 0] = txform[0]; + instances.data[write_offset + 1] = txform[1]; + instances.data[write_offset + 2] = txform[2]; + instances.data[write_offset + 3] = particles.data[particle].color; + instances.data[write_offset + 4] = particles.data[particle].custom; + +#endif +} diff --git a/servers/rendering/rasterizer_rd/shaders/sort.glsl b/servers/rendering/rasterizer_rd/shaders/sort.glsl new file mode 100644 index 0000000000..e5ebb9c64b --- /dev/null +++ b/servers/rendering/rasterizer_rd/shaders/sort.glsl @@ -0,0 +1,203 @@ +#[compute] + +#version 450 + +VERSION_DEFINES + +// Original version here: +// https://github.com/GPUOpen-LibrariesAndSDKs/GPUParticles11/blob/master/gpuparticles11/src/Shaders + +// +// Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#define SORT_SIZE 512 +#define NUM_THREADS (SORT_SIZE / 2) +#define INVERSION (16 * 2 + 8 * 3) +#define ITERATIONS 1 + +layout(local_size_x = NUM_THREADS, local_size_y = 1, local_size_z = 1) in; + +#ifndef MODE_SORT_STEP + +shared vec2 g_LDS[SORT_SIZE]; + +#endif + +layout(set = 1, binding = 0, std430) restrict buffer SortBuffer { + vec2 data[]; +} +sort_buffer; + +layout(push_constant, binding = 0, std430) uniform Params { + uint total_elements; + uint pad[3]; + ivec4 job_params; +} +params; + +void main() { +#ifdef MODE_SORT_BLOCK + + uvec3 Gid = gl_WorkGroupID; + uvec3 DTid = gl_GlobalInvocationID; + uvec3 GTid = gl_LocalInvocationID; + uint GI = gl_LocalInvocationIndex; + + int GlobalBaseIndex = int((Gid.x * SORT_SIZE) + GTid.x); + int LocalBaseIndex = int(GI); + int numElementsInThreadGroup = int(min(SORT_SIZE, params.total_elements - (Gid.x * SORT_SIZE))); + + // Load shared data + + int i; + for (i = 0; i < 2 * ITERATIONS; ++i) { + if (GI + i * NUM_THREADS < numElementsInThreadGroup) + g_LDS[LocalBaseIndex + i * NUM_THREADS] = sort_buffer.data[GlobalBaseIndex + i * NUM_THREADS]; + } + + groupMemoryBarrier(); + barrier(); + + // Bitonic sort + for (int nMergeSize = 2; nMergeSize <= SORT_SIZE; nMergeSize = nMergeSize * 2) { + for (int nMergeSubSize = nMergeSize >> 1; nMergeSubSize > 0; nMergeSubSize = nMergeSubSize >> 1) { + for (i = 0; i < ITERATIONS; ++i) { + int tmp_index = int(GI + NUM_THREADS * i); + int index_low = tmp_index & (nMergeSubSize - 1); + int index_high = 2 * (tmp_index - index_low); + int index = index_high + index_low; + + int nSwapElem = nMergeSubSize == nMergeSize >> 1 ? index_high + (2 * nMergeSubSize - 1) - index_low : index_high + nMergeSubSize + index_low; + if (nSwapElem < numElementsInThreadGroup) { + vec2 a = g_LDS[index]; + vec2 b = g_LDS[nSwapElem]; + + if (a.x > b.x) { + g_LDS[index] = b; + g_LDS[nSwapElem] = a; + } + } + groupMemoryBarrier(); + barrier(); + } + } + } + + // Store shared data + for (i = 0; i < 2 * ITERATIONS; ++i) { + if (GI + i * NUM_THREADS < numElementsInThreadGroup) { + sort_buffer.data[GlobalBaseIndex + i * NUM_THREADS] = g_LDS[LocalBaseIndex + i * NUM_THREADS]; + } + } + +#endif + +#ifdef MODE_SORT_STEP + + uvec3 Gid = gl_WorkGroupID; + uvec3 GTid = gl_LocalInvocationID; + + ivec4 tgp; + + tgp.x = int(Gid.x) * 256; + tgp.y = 0; + tgp.z = int(params.total_elements); + tgp.w = min(512, max(0, tgp.z - int(Gid.x) * 512)); + + uint localID = int(tgp.x) + GTid.x; // calculate threadID within this sortable-array + + uint index_low = localID & (params.job_params.x - 1); + uint index_high = 2 * (localID - index_low); + + uint index = tgp.y + index_high + index_low; + uint nSwapElem = tgp.y + index_high + params.job_params.y + params.job_params.z * index_low; + + if (nSwapElem < tgp.y + tgp.z) { + vec2 a = sort_buffer.data[index]; + vec2 b = sort_buffer.data[nSwapElem]; + + if (a.x > b.x) { + sort_buffer.data[index] = b; + sort_buffer.data[nSwapElem] = a; + } + } + +#endif + +#ifdef MODE_SORT_INNER + + uvec3 Gid = gl_WorkGroupID; + uvec3 DTid = gl_GlobalInvocationID; + uvec3 GTid = gl_LocalInvocationID; + uint GI = gl_LocalInvocationIndex; + + ivec4 tgp; + + tgp.x = int(Gid.x * 256); + tgp.y = 0; + tgp.z = int(params.total_elements.x); + tgp.w = int(min(512, max(0, params.total_elements - Gid.x * 512))); + + int GlobalBaseIndex = int(tgp.y + tgp.x * 2 + GTid.x); + int LocalBaseIndex = int(GI); + int i; + + // Load shared data + for (i = 0; i < 2; ++i) { + if (GI + i * NUM_THREADS < tgp.w) + g_LDS[LocalBaseIndex + i * NUM_THREADS] = sort_buffer.data[GlobalBaseIndex + i * NUM_THREADS]; + } + + groupMemoryBarrier(); + barrier(); + + // sort threadgroup shared memory + for (int nMergeSubSize = SORT_SIZE >> 1; nMergeSubSize > 0; nMergeSubSize = nMergeSubSize >> 1) { + int tmp_index = int(GI); + int index_low = tmp_index & (nMergeSubSize - 1); + int index_high = 2 * (tmp_index - index_low); + int index = index_high + index_low; + + int nSwapElem = index_high + nMergeSubSize + index_low; + + if (nSwapElem < tgp.w) { + vec2 a = g_LDS[index]; + vec2 b = g_LDS[nSwapElem]; + + if (a.x > b.x) { + g_LDS[index] = b; + g_LDS[nSwapElem] = a; + } + } + groupMemoryBarrier(); + barrier(); + } + + // Store shared data + for (i = 0; i < 2; ++i) { + if (GI + i * NUM_THREADS < tgp.w) { + sort_buffer.data[GlobalBaseIndex + i * NUM_THREADS] = g_LDS[LocalBaseIndex + i * NUM_THREADS]; + } + } + +#endif +} diff --git a/servers/rendering/rendering_server_scene.cpp b/servers/rendering/rendering_server_scene.cpp index 2024f5b983..d8e52a5aae 100644 --- a/servers/rendering/rendering_server_scene.cpp +++ b/servers/rendering/rendering_server_scene.cpp @@ -2044,6 +2044,7 @@ void RenderingServerScene::_prepare_scene(const Transform p_cam_transform, const keep = false; } else { RSG::storage->particles_request_process(ins->base); + RSG::storage->particles_set_view_axis(ins->base, -p_cam_transform.basis.get_axis(2).normalized()); //particles visible? request redraw RenderingServerRaster::redraw_request(); } diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index 76aeb5989c..9c4f44b928 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -270,20 +270,20 @@ ShaderTypes::ShaderTypes() { /************ PARTICLES **************************/ shader_modes[RS::SHADER_PARTICLES].functions["global"].built_ins["TIME"] = constt(ShaderLanguage::TYPE_FLOAT); - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["COLOR"] = ShaderLanguage::TYPE_VEC4; - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["VELOCITY"] = ShaderLanguage::TYPE_VEC3; - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["MASS"] = ShaderLanguage::TYPE_FLOAT; - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["ACTIVE"] = ShaderLanguage::TYPE_BOOL; - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["RESTART"] = constt(ShaderLanguage::TYPE_BOOL); - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["CUSTOM"] = ShaderLanguage::TYPE_VEC4; - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["TRANSFORM"] = ShaderLanguage::TYPE_MAT4; - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["LIFETIME"] = constt(ShaderLanguage::TYPE_FLOAT); - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["DELTA"] = constt(ShaderLanguage::TYPE_FLOAT); - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["NUMBER"] = constt(ShaderLanguage::TYPE_UINT); - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["INDEX"] = constt(ShaderLanguage::TYPE_INT); - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["EMISSION_TRANSFORM"] = constt(ShaderLanguage::TYPE_MAT4); - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].built_ins["RANDOM_SEED"] = constt(ShaderLanguage::TYPE_UINT); - shader_modes[RS::SHADER_PARTICLES].functions["vertex"].can_discard = false; + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["COLOR"] = ShaderLanguage::TYPE_VEC4; + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["VELOCITY"] = ShaderLanguage::TYPE_VEC3; + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["MASS"] = ShaderLanguage::TYPE_FLOAT; + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["ACTIVE"] = ShaderLanguage::TYPE_BOOL; + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART"] = constt(ShaderLanguage::TYPE_BOOL); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["CUSTOM"] = ShaderLanguage::TYPE_VEC4; + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["TRANSFORM"] = ShaderLanguage::TYPE_MAT4; + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["LIFETIME"] = constt(ShaderLanguage::TYPE_FLOAT); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["DELTA"] = constt(ShaderLanguage::TYPE_FLOAT); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["NUMBER"] = constt(ShaderLanguage::TYPE_UINT); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["INDEX"] = constt(ShaderLanguage::TYPE_INT); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["EMISSION_TRANSFORM"] = constt(ShaderLanguage::TYPE_MAT4); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RANDOM_SEED"] = constt(ShaderLanguage::TYPE_UINT); + shader_modes[RS::SHADER_PARTICLES].functions["compute"].can_discard = false; shader_modes[RS::SHADER_PARTICLES].modes.push_back("disable_force"); shader_modes[RS::SHADER_PARTICLES].modes.push_back("disable_velocity");