summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuan Linietsky <reduzio@gmail.com>2020-09-02 14:39:11 -0300
committerJuan Linietsky <reduzio@gmail.com>2020-09-02 21:37:14 +0200
commitd0913370b5980cbcd248b86ad6de2f14587e0ee5 (patch)
tree3a04659f841cba0bcbed4b27c4ddd96189c30c3d
parenta3f5dac84f3416cf81dd69d90bf52eeacc63955a (diff)
parentf5f27bacdbd6677c16b82729dd3f4824609cf2ed (diff)
Merge pull request #41711 from reduz/particle-shaders
Particle shaders
-rw-r--r--scene/resources/particles_material.cpp2
-rw-r--r--servers/rendering/rasterizer.h2
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp85
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_effects_rd.h24
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp76
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_rd.h2
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp880
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_storage_rd.h315
-rw-r--r--servers/rendering/rasterizer_rd/shader_compiler_rd.cpp14
-rw-r--r--servers/rendering/rasterizer_rd/shader_compiler_rd.h3
-rw-r--r--servers/rendering/rasterizer_rd/shaders/SCsub3
-rw-r--r--servers/rendering/rasterizer_rd/shaders/particles.glsl262
-rw-r--r--servers/rendering/rasterizer_rd/shaders/particles_copy.glsl82
-rw-r--r--servers/rendering/rasterizer_rd/shaders/sort.glsl203
-rw-r--r--servers/rendering/rendering_server_scene.cpp1
-rw-r--r--servers/rendering/shader_types.cpp28
16 files changed, 1919 insertions, 63 deletions
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<String> copy_modes;
@@ -1618,6 +1688,21 @@ RasterizerEffectsRD::RasterizerEffectsRD() {
}
}
+ {
+ Vector<String> 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<ReflectionProbeInstance> 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<Image> RasterizerStorageRD::_validate_texture_format(const Ref<Image> &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<RD::Uniform> 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<RD::Uniform> 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<ParticleData> data;
+ data.resize(particles->amount);
+
+ Vector<uint8_t> 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<RD::Uniform> 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, &copy_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, &copy_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, &copy_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<PropertyInfo> *p_param_list) const {
+ Map<int, StringName> order;
+
+ for (Map<StringName, ShaderLanguage::ShaderNode::Uniform>::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<int, StringName>::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<RasterizerStorage::InstanceShaderParam> *p_param_list) const {
+ for (Map<StringName, ShaderLanguage::ShaderNode::Uniform>::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<ShaderLanguage::ConstantNode::Value> 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<StringName, Variant> &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<RD::Uniform> 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<String> 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<RD::Uniform> 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<String> 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<RID> 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<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms;
+ Vector<ShaderCompilerRD::GeneratedCode::Texture> texture_uniforms;
+
+ Vector<uint32_t> ubo_offsets;
+ uint32_t ubo_size;
+
+ String path;
+ String code;
+ Map<StringName, RID> 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<PropertyInfo> *p_param_list) const;
+ virtual void get_instance_param_list(List<RasterizerStorage::InstanceShaderParam> *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<RID> texture_cache;
+ Vector<uint8_t> 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<StringName, Variant> &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<ParticlesShaderData *>(p_shader));
+ }
+
+ void update_particles();
+
+ mutable RID_Owner<Particles> 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);
+ AABB particles_get_aabb(RID p_particles) const;
- 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_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);
+ int particles_get_draw_passes(RID p_particles) const;
+ RID particles_get_draw_pass_mesh(RID p_particles, int p_pass) const;
+
+ 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;
+ }
- 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(); }
+ _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);
- virtual bool particles_is_inactive(RID p_particles) const { return 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<RD::Uniform> 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<StringName, String> function_code;
@@ -741,6 +745,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge
Set<StringName> added_vtx;
Set<StringName> added_fragment; //share for light
+ Set<StringName> 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<StringName> 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");