Browse Source

Implement manual particle emission and particle sub emitters.

reduz 5 years ago
parent
commit
d0bddf53c5

+ 52 - 0
scene/3d/gpu_particles_3d.cpp

@@ -301,6 +301,36 @@ void GPUParticles3D::_validate_property(PropertyInfo &property) const {
 	}
 }
 
+void GPUParticles3D::emit_particle(const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
+	RS::get_singleton()->particles_emit(particles, p_transform, p_velocity, p_color, p_custom, p_emit_flags);
+}
+
+void GPUParticles3D::_attach_sub_emitter() {
+	Node *n = get_node_or_null(sub_emitter);
+	if (n) {
+		GPUParticles3D *sen = Object::cast_to<GPUParticles3D>(n);
+		if (sen && sen != this) {
+			RS::get_singleton()->particles_set_subemitter(particles, sen->particles);
+		}
+	}
+}
+
+void GPUParticles3D::set_sub_emitter(const NodePath &p_path) {
+	if (is_inside_tree()) {
+		RS::get_singleton()->particles_set_subemitter(particles, RID());
+	}
+
+	sub_emitter = p_path;
+
+	if (is_inside_tree() && sub_emitter != NodePath()) {
+		_attach_sub_emitter();
+	}
+}
+
+NodePath GPUParticles3D::get_sub_emitter() const {
+	return sub_emitter;
+}
+
 void GPUParticles3D::_notification(int p_what) {
 	if (p_what == NOTIFICATION_PAUSED || p_what == NOTIFICATION_UNPAUSED) {
 		if (can_process()) {
@@ -319,6 +349,16 @@ void GPUParticles3D::_notification(int p_what) {
 		}
 	}
 
+	if (p_what == NOTIFICATION_ENTER_TREE) {
+		if (sub_emitter != NodePath()) {
+			_attach_sub_emitter();
+		}
+	}
+
+	if (p_what == NOTIFICATION_EXIT_TREE) {
+		RS::get_singleton()->particles_set_subemitter(particles, RID());
+	}
+
 	if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
 		// make sure particles are updated before rendering occurs if they were active before
 		if (is_visible_in_tree() && !RS::get_singleton()->particles_is_inactive(particles)) {
@@ -369,8 +409,14 @@ void GPUParticles3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("restart"), &GPUParticles3D::restart);
 	ClassDB::bind_method(D_METHOD("capture_aabb"), &GPUParticles3D::capture_aabb);
 
+	ClassDB::bind_method(D_METHOD("set_sub_emitter", "path"), &GPUParticles3D::set_sub_emitter);
+	ClassDB::bind_method(D_METHOD("get_sub_emitter"), &GPUParticles3D::get_sub_emitter);
+
+	ClassDB::bind_method(D_METHOD("emit_particle", "xform", "velocity", "color", "custom", "flags"), &GPUParticles3D::emit_particle);
+
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount");
+	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles3D"), "set_sub_emitter", "get_sub_emitter");
 	ADD_GROUP("Time", "");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_EXP_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
@@ -396,6 +442,12 @@ void GPUParticles3D::_bind_methods() {
 	BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME);
 	BIND_ENUM_CONSTANT(DRAW_ORDER_VIEW_DEPTH);
 
+	BIND_ENUM_CONSTANT(EMIT_FLAG_POSITION);
+	BIND_ENUM_CONSTANT(EMIT_FLAG_ROTATION_SCALE);
+	BIND_ENUM_CONSTANT(EMIT_FLAG_VELOCITY);
+	BIND_ENUM_CONSTANT(EMIT_FLAG_COLOR);
+	BIND_ENUM_CONSTANT(EMIT_FLAG_CUSTOM);
+
 	BIND_CONSTANT(MAX_DRAW_PASSES);
 }
 

+ 17 - 0
scene/3d/gpu_particles_3d.h

@@ -64,6 +64,7 @@ private:
 	bool local_coords;
 	int fixed_fps;
 	bool fractional_delta;
+	NodePath sub_emitter;
 
 	Ref<Material> process_material;
 
@@ -71,6 +72,8 @@ private:
 
 	Vector<Ref<Mesh>> draw_passes;
 
+	void _attach_sub_emitter();
+
 protected:
 	static void _bind_methods();
 	void _notification(int p_what);
@@ -121,13 +124,27 @@ public:
 
 	virtual String get_configuration_warning() const override;
 
+	void set_sub_emitter(const NodePath &p_path);
+	NodePath get_sub_emitter() const;
+
 	void restart();
 
+	enum EmitFlags {
+		EMIT_FLAG_POSITION = RS::PARTICLES_EMIT_FLAG_POSITION,
+		EMIT_FLAG_ROTATION_SCALE = RS::PARTICLES_EMIT_FLAG_ROTATION_SCALE,
+		EMIT_FLAG_VELOCITY = RS::PARTICLES_EMIT_FLAG_VELOCITY,
+		EMIT_FLAG_COLOR = RS::PARTICLES_EMIT_FLAG_COLOR,
+		EMIT_FLAG_CUSTOM = RS::PARTICLES_EMIT_FLAG_CUSTOM
+	};
+
+	void emit_particle(const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags);
+
 	AABB capture_aabb() const;
 	GPUParticles3D();
 	~GPUParticles3D();
 };
 
 VARIANT_ENUM_CAST(GPUParticles3D::DrawOrder)
+VARIANT_ENUM_CAST(GPUParticles3D::EmitFlags)
 
 #endif // PARTICLES_H

+ 158 - 119
scene/resources/particles_material.cpp

@@ -91,13 +91,13 @@ void ParticlesMaterial::init_shaders() {
 	shader_names->emission_texture_normal = "emission_texture_normal";
 	shader_names->emission_texture_color = "emission_texture_color";
 
-	shader_names->trail_divisor = "trail_divisor";
-	shader_names->trail_size_modifier = "trail_size_modifier";
-	shader_names->trail_color_modifier = "trail_color_modifier";
-
 	shader_names->gravity = "gravity";
 
 	shader_names->lifetime_randomness = "lifetime_randomness";
+
+	shader_names->sub_emitter_frequency = "sub_emitter_frequency";
+	shader_names->sub_emitter_amount_at_end = "sub_emitter_amount_at_end";
+	shader_names->sub_emitter_keep_velocity = "sub_emitter_keep_velocity";
 }
 
 void ParticlesMaterial::finish_shaders() {
@@ -192,9 +192,17 @@ void ParticlesMaterial::_update_shader() {
 		}
 	}
 
-	code += "uniform vec4 color_value : hint_color;\n";
+	if (sub_emitter_mode != SUB_EMITTER_DISABLED) {
+		if (sub_emitter_mode == SUB_EMITTER_CONSTANT) {
+			code += "uniform float sub_emitter_frequency;\n";
+		}
+		if (sub_emitter_mode == SUB_EMITTER_AT_END) {
+			code += "uniform int sub_emitter_amount_at_end;\n";
+		}
+		code += "uniform bool sub_emitter_keep_velocity;\n";
+	}
 
-	code += "uniform int trail_divisor;\n";
+	code += "uniform vec4 color_value : hint_color;\n";
 
 	code += "uniform vec3 gravity;\n";
 
@@ -239,14 +247,6 @@ void ParticlesMaterial::_update_shader() {
 		code += "uniform sampler2D anim_offset_texture;\n";
 	}
 
-	if (trail_size_modifier.is_valid()) {
-		code += "uniform sampler2D trail_size_modifier;\n";
-	}
-
-	if (trail_color_modifier.is_valid()) {
-		code += "uniform sampler2D trail_color_modifier;\n";
-	}
-
 	//need a random function
 	code += "\n\n";
 	code += "float rand_from_seed(inout uint seed) {\n";
@@ -278,7 +278,7 @@ void ParticlesMaterial::_update_shader() {
 	code += "\n";
 
 	code += "void compute() {\n";
-	code += "	uint base_number = NUMBER / uint(trail_divisor);\n";
+	code += "	uint base_number = NUMBER;\n";
 	code += "	uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n";
 	code += "	float angle_rand = rand_from_seed(alt_seed);\n";
 	code += "	float scale_rand = rand_from_seed(alt_seed);\n";
@@ -293,17 +293,7 @@ void ParticlesMaterial::_update_shader() {
 		code += "	ivec2 emission_tex_size = textureSize(emission_texture_points, 0);\n";
 		code += "	ivec2 emission_tex_ofs = ivec2(point % emission_tex_size.x, point / emission_tex_size.x);\n";
 	}
-	code += "	bool restart = false;\n";
-	code += "	if (CUSTOM.y > CUSTOM.w) {\n";
-	code += "		restart = true;\n";
-	code += "	}\n\n";
-	code += "	if (RESTART || restart) {\n";
-
-	if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
-		code += "		float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n";
-	} else {
-		code += "		float tex_linear_velocity = 0.0;\n";
-	}
+	code += "	if (RESTART) {\n";
 
 	if (tex_parameters[PARAM_ANGLE].is_valid()) {
 		code += "		float tex_angle = textureLod(angle_texture, vec2(0.0, 0.0), 0.0).r;\n";
@@ -319,25 +309,34 @@ void ParticlesMaterial::_update_shader() {
 
 	code += "		float spread_rad = spread * degree_to_rad;\n";
 
+	code += "		if (RESTART_VELOCITY) {\n";
+
+	if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
+		code += "			float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n";
+	} else {
+		code += "			float tex_linear_velocity = 0.0;\n";
+	}
+
 	if (flags[FLAG_DISABLE_Z]) {
-		code += "		float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
-		code += "		angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n";
-		code += "		vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n";
-		code += "		VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n";
+		code += "			float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
+		code += "			angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n";
+		code += "			vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n";
+		code += "			VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n";
 
 	} else {
 		//initiate velocity spread in 3D
-		code += "		float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
-		code += "		float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n";
-		code += "		angle1_rad += direction.z != 0.0 ? atan(direction.x, direction.z) : sign(direction.x) * (pi / 2.0);\n";
-		code += "		angle2_rad += direction.z != 0.0 ? atan(direction.y, abs(direction.z)) : (direction.x != 0.0 ? atan(direction.y, abs(direction.x)) : sign(direction.y) * (pi / 2.0));\n";
-		code += "		vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n";
-		code += "		vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n";
-		code += "		direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n";
-		code += "		vec3 vec_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n";
-		code += "		vec_direction = normalize(vec_direction);\n";
-		code += "		VELOCITY = vec_direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n";
+		code += "			float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
+		code += "			float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n";
+		code += "			angle1_rad += direction.z != 0.0 ? atan(direction.x, direction.z) : sign(direction.x) * (pi / 2.0);\n";
+		code += "			angle2_rad += direction.z != 0.0 ? atan(direction.y, abs(direction.z)) : (direction.x != 0.0 ? atan(direction.y, abs(direction.x)) : sign(direction.y) * (pi / 2.0));\n";
+		code += "			vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n";
+		code += "			vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n";
+		code += "			direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n";
+		code += "			vec3 vec_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n";
+		code += "			vec_direction = normalize(vec_direction);\n";
+		code += "			VELOCITY = vec_direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n";
 	}
+	code += "		}\n";
 
 	code += "		float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n";
 	code += "		CUSTOM.x = base_angle * degree_to_rad;\n"; // angle
@@ -345,35 +344,38 @@ void ParticlesMaterial::_update_shader() {
 	code += "		CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n";
 	code += "		CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random);\n"; // animation offset (0-1)
 
+	code += "		if (RESTART_POSITION) {\n";
+
 	switch (emission_shape) {
 		case EMISSION_SHAPE_POINT: {
-			//do none
+			//do none, identity (will later be multiplied by emission transform)
+			code += "			TRANSFORM = mat4(vec4(1,0,0,0),vec4(0,1,0,0),vec4(0,0,1,0),vec4(0,0,0,1));\n";
 		} break;
 		case EMISSION_SHAPE_SPHERE: {
-			code += "		float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n";
-			code += "		float t = rand_from_seed(alt_seed) * 2.0 * pi;\n";
-			code += "		float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n";
-			code += "		TRANSFORM[3].xyz = vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s);\n";
+			code += "			float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n";
+			code += "			float t = rand_from_seed(alt_seed) * 2.0 * pi;\n";
+			code += "			float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n";
+			code += "			TRANSFORM[3].xyz = vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s);\n";
 		} break;
 		case EMISSION_SHAPE_BOX: {
-			code += "		TRANSFORM[3].xyz = vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0) * emission_box_extents;\n";
+			code += "			TRANSFORM[3].xyz = vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0) * emission_box_extents;\n";
 		} break;
 		case EMISSION_SHAPE_POINTS:
 		case EMISSION_SHAPE_DIRECTED_POINTS: {
-			code += "		TRANSFORM[3].xyz = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n";
+			code += "			TRANSFORM[3].xyz = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n";
 
 			if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) {
 				if (flags[FLAG_DISABLE_Z]) {
-					code += "		mat2 rotm;";
-					code += "		rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy;\n";
-					code += "		rotm[1] = rotm[0].yx * vec2(1.0, -1.0);\n";
-					code += "		VELOCITY.xy = rotm * VELOCITY.xy;\n";
+					code += "			mat2 rotm;";
+					code += "			rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy;\n";
+					code += "			rotm[1] = rotm[0].yx * vec2(1.0, -1.0);\n";
+					code += "			if (RESTART_VELOCITY) VELOCITY.xy = rotm * VELOCITY.xy;\n";
 				} else {
-					code += "		vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xyz;\n";
-					code += "		vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n";
-					code += "		vec3 tangent = normalize(cross(v0, normal));\n";
-					code += "		vec3 bitangent = normalize(cross(tangent, normal));\n";
-					code += "		VELOCITY = mat3(tangent, bitangent, normal) * VELOCITY;\n";
+					code += "			vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xyz;\n";
+					code += "			vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n";
+					code += "			vec3 tangent = normalize(cross(v0, normal));\n";
+					code += "			vec3 bitangent = normalize(cross(tangent, normal));\n";
+					code += "			if (RESTART_VELOCITY) VELOCITY = mat3(tangent, bitangent, normal) * VELOCITY;\n";
 				}
 			}
 		} break;
@@ -381,12 +383,14 @@ void ParticlesMaterial::_update_shader() {
 			break;
 		}
 	}
-	code += "		VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n";
-	code += "		TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n";
+
+	code += "			if (RESTART_VELOCITY) VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n";
+	code += "			TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n";
 	if (flags[FLAG_DISABLE_Z]) {
-		code += "		VELOCITY.z = 0.0;\n";
-		code += "		TRANSFORM[3].z = 0.0;\n";
+		code += "			VELOCITY.z = 0.0;\n";
+		code += "			TRANSFORM[3].z = 0.0;\n";
 	}
+	code += "		}\n";
 
 	code += "	} else {\n";
 
@@ -540,11 +544,6 @@ void ParticlesMaterial::_update_shader() {
 	if (emission_color_texture.is_valid() && (emission_shape == EMISSION_SHAPE_POINTS || emission_shape == EMISSION_SHAPE_DIRECTED_POINTS)) {
 		code += "	COLOR *= texelFetch(emission_texture_color, emission_tex_ofs, 0);\n";
 	}
-	if (trail_color_modifier.is_valid()) {
-		code += "	if (trail_divisor > 1) {\n";
-		code += "		COLOR *= textureLod(trail_color_modifier, vec2(float(int(NUMBER) % trail_divisor) / float(trail_divisor - 1), 0.0), 0.0);\n";
-		code += "	}\n";
-	}
 	code += "\n";
 
 	if (flags[FLAG_DISABLE_Z]) {
@@ -592,11 +591,6 @@ void ParticlesMaterial::_update_shader() {
 	code += "	if (base_scale < 0.000001) {\n";
 	code += "		base_scale = 0.000001;\n";
 	code += "	}\n";
-	if (trail_size_modifier.is_valid()) {
-		code += "	if (trail_divisor > 1) {\n";
-		code += "		base_scale *= textureLod(trail_size_modifier, vec2(float(int(NUMBER) % trail_divisor) / float(trail_divisor - 1), 0.0), 0.0).r;\n";
-		code += "	}\n";
-	}
 
 	code += "	TRANSFORM[0].xyz *= base_scale;\n";
 	code += "	TRANSFORM[1].xyz *= base_scale;\n";
@@ -605,6 +599,33 @@ void ParticlesMaterial::_update_shader() {
 		code += "	VELOCITY.z = 0.0;\n";
 		code += "	TRANSFORM[3].z = 0.0;\n";
 	}
+	if (sub_emitter_mode != SUB_EMITTER_DISABLED) {
+		code += "	int emit_count = 0;\n";
+		switch (sub_emitter_mode) {
+			case SUB_EMITTER_CONSTANT: {
+				code += "	float interval_from = CUSTOM.y * LIFETIME - DELTA;\n";
+				code += "	float interval_rem = sub_emitter_frequency - mod(interval_from,sub_emitter_frequency);\n";
+				code += "	if (DELTA >= interval_rem) emit_count = 1;\n";
+			} break;
+			case SUB_EMITTER_AT_COLLISION: {
+				//not implemented yet
+			} break;
+			case SUB_EMITTER_AT_END: {
+				//not implemented yet
+				code += "	float unit_delta = DELTA/LIFETIME;\n";
+				code += "	float end_time = CUSTOM.w * 0.95;\n"; // if we do at the end we might miss it, as it can just get deactivated by emitter
+				code += "	if (CUSTOM.y < end_time && (CUSTOM.y + unit_delta) >= end_time) emit_count = sub_emitter_amount_at_end;\n";
+			} break;
+			default: {
+			}
+		}
+		code += "	for(int i=0;i<emit_count;i++) {\n";
+		code += "		uint flags = FLAG_EMIT_POSITION|FLAG_EMIT_ROT_SCALE;\n";
+		code += "		if (sub_emitter_keep_velocity) flags|=FLAG_EMIT_VELOCITY;\n";
+		code += "		emit_particle(TRANSFORM,VELOCITY,vec4(0.0),vec4(0.0),flags);\n";
+		code += "	}";
+	}
+
 	code += "	if (CUSTOM.y > CUSTOM.w) {";
 	code += "		ACTIVE = false;\n";
 	code += "	}\n";
@@ -951,41 +972,6 @@ int ParticlesMaterial::get_emission_point_count() const {
 	return emission_point_count;
 }
 
-void ParticlesMaterial::set_trail_divisor(int p_divisor) {
-	trail_divisor = p_divisor;
-	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->trail_divisor, p_divisor);
-}
-
-int ParticlesMaterial::get_trail_divisor() const {
-	return trail_divisor;
-}
-
-void ParticlesMaterial::set_trail_size_modifier(const Ref<CurveTexture> &p_trail_size_modifier) {
-	trail_size_modifier = p_trail_size_modifier;
-
-	Ref<CurveTexture> curve = trail_size_modifier;
-	if (curve.is_valid()) {
-		curve->ensure_default_setup();
-	}
-
-	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->trail_size_modifier, curve);
-	_queue_shader_change();
-}
-
-Ref<CurveTexture> ParticlesMaterial::get_trail_size_modifier() const {
-	return trail_size_modifier;
-}
-
-void ParticlesMaterial::set_trail_color_modifier(const Ref<GradientTexture> &p_trail_color_modifier) {
-	trail_color_modifier = p_trail_color_modifier;
-	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->trail_color_modifier, p_trail_color_modifier);
-	_queue_shader_change();
-}
-
-Ref<GradientTexture> ParticlesMaterial::get_trail_color_modifier() const {
-	return trail_color_modifier;
-}
-
 void ParticlesMaterial::set_gravity(const Vector3 &p_gravity) {
 	gravity = p_gravity;
 	Vector3 gset = gravity;
@@ -1038,11 +1024,54 @@ void ParticlesMaterial::_validate_property(PropertyInfo &property) const {
 		property.usage = 0;
 	}
 
+	if (property.name == "sub_emitter_frequency" && sub_emitter_mode != SUB_EMITTER_CONSTANT) {
+		property.usage = 0;
+	}
+
+	if (property.name == "sub_emitter_amount_at_end" && sub_emitter_mode != SUB_EMITTER_AT_END) {
+		property.usage = 0;
+	}
+
 	if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) {
 		property.usage = 0;
 	}
 }
 
+void ParticlesMaterial::set_sub_emitter_mode(SubEmitterMode p_sub_emitter_mode) {
+	sub_emitter_mode = p_sub_emitter_mode;
+	_queue_shader_change();
+	_change_notify();
+}
+
+ParticlesMaterial::SubEmitterMode ParticlesMaterial::get_sub_emitter_mode() const {
+	return sub_emitter_mode;
+}
+
+void ParticlesMaterial::set_sub_emitter_frequency(float p_frequency) {
+	sub_emitter_frequency = p_frequency;
+	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_frequency, 1.0 / p_frequency); //pas delta instead of frequency, since its easier to compute
+}
+float ParticlesMaterial::get_sub_emitter_frequency() const {
+	return sub_emitter_frequency;
+}
+
+void ParticlesMaterial::set_sub_emitter_amount_at_end(int p_amount) {
+	sub_emitter_amount_at_end = p_amount;
+	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_amount_at_end, p_amount);
+}
+
+int ParticlesMaterial::get_sub_emitter_amount_at_end() const {
+	return sub_emitter_amount_at_end;
+}
+
+void ParticlesMaterial::set_sub_emitter_keep_velocity(bool p_enable) {
+	sub_emitter_keep_velocity = p_enable;
+	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_keep_velocity, p_enable);
+}
+bool ParticlesMaterial::get_sub_emitter_keep_velocity() const {
+	return sub_emitter_keep_velocity;
+}
+
 Shader::Mode ParticlesMaterial::get_shader_mode() const {
 	return Shader::MODE_PARTICLES;
 }
@@ -1096,27 +1125,27 @@ void ParticlesMaterial::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_emission_point_count", "point_count"), &ParticlesMaterial::set_emission_point_count);
 	ClassDB::bind_method(D_METHOD("get_emission_point_count"), &ParticlesMaterial::get_emission_point_count);
 
-	ClassDB::bind_method(D_METHOD("set_trail_divisor", "divisor"), &ParticlesMaterial::set_trail_divisor);
-	ClassDB::bind_method(D_METHOD("get_trail_divisor"), &ParticlesMaterial::get_trail_divisor);
-
-	ClassDB::bind_method(D_METHOD("set_trail_size_modifier", "texture"), &ParticlesMaterial::set_trail_size_modifier);
-	ClassDB::bind_method(D_METHOD("get_trail_size_modifier"), &ParticlesMaterial::get_trail_size_modifier);
-
-	ClassDB::bind_method(D_METHOD("set_trail_color_modifier", "texture"), &ParticlesMaterial::set_trail_color_modifier);
-	ClassDB::bind_method(D_METHOD("get_trail_color_modifier"), &ParticlesMaterial::get_trail_color_modifier);
-
 	ClassDB::bind_method(D_METHOD("get_gravity"), &ParticlesMaterial::get_gravity);
 	ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &ParticlesMaterial::set_gravity);
 
 	ClassDB::bind_method(D_METHOD("set_lifetime_randomness", "randomness"), &ParticlesMaterial::set_lifetime_randomness);
 	ClassDB::bind_method(D_METHOD("get_lifetime_randomness"), &ParticlesMaterial::get_lifetime_randomness);
 
+	ClassDB::bind_method(D_METHOD("get_sub_emitter_mode"), &ParticlesMaterial::get_sub_emitter_mode);
+	ClassDB::bind_method(D_METHOD("set_sub_emitter_mode", "mode"), &ParticlesMaterial::set_sub_emitter_mode);
+
+	ClassDB::bind_method(D_METHOD("get_sub_emitter_frequency"), &ParticlesMaterial::get_sub_emitter_frequency);
+	ClassDB::bind_method(D_METHOD("set_sub_emitter_frequency", "hz"), &ParticlesMaterial::set_sub_emitter_frequency);
+
+	ClassDB::bind_method(D_METHOD("get_sub_emitter_amount_at_end"), &ParticlesMaterial::get_sub_emitter_amount_at_end);
+	ClassDB::bind_method(D_METHOD("set_sub_emitter_amount_at_end", "amount"), &ParticlesMaterial::set_sub_emitter_amount_at_end);
+
+	ClassDB::bind_method(D_METHOD("get_sub_emitter_keep_velocity"), &ParticlesMaterial::get_sub_emitter_keep_velocity);
+	ClassDB::bind_method(D_METHOD("set_sub_emitter_keep_velocity", "enable"), &ParticlesMaterial::set_sub_emitter_keep_velocity);
+
 	ADD_GROUP("Time", "");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness");
-	ADD_GROUP("Trail", "trail_");
-	ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_divisor", PROPERTY_HINT_RANGE, "1,1000000,1"), "set_trail_divisor", "get_trail_divisor");
-	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trail_size_modifier", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_trail_size_modifier", "get_trail_size_modifier");
-	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trail_color_modifier", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_trail_color_modifier", "get_trail_color_modifier");
+
 	ADD_GROUP("Emission Shape", "emission_");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points"), "set_emission_shape", "get_emission_shape");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,or_greater"), "set_emission_sphere_radius", "get_emission_sphere_radius");
@@ -1186,6 +1215,12 @@ void ParticlesMaterial::_bind_methods() {
 	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET);
 	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_OFFSET);
 
+	ADD_GROUP("Sub Emitter", "sub_emitter_");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled,Constant,AtEnd,AtCollision"), "set_sub_emitter_mode", "get_sub_emitter_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sub_emitter_frequency", PROPERTY_HINT_RANGE, "0.01,100,0.01"), "set_sub_emitter_frequency", "get_sub_emitter_frequency");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_end", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_end", "get_sub_emitter_amount_at_end");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity");
+
 	BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY);
 	BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY);
 	BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY);
@@ -1233,11 +1268,15 @@ ParticlesMaterial::ParticlesMaterial() :
 	set_emission_shape(EMISSION_SHAPE_POINT);
 	set_emission_sphere_radius(1);
 	set_emission_box_extents(Vector3(1, 1, 1));
-	set_trail_divisor(1);
 	set_gravity(Vector3(0, -9.8, 0));
 	set_lifetime_randomness(0);
 	emission_point_count = 1;
 
+	set_sub_emitter_mode(SUB_EMITTER_DISABLED);
+	set_sub_emitter_frequency(4);
+	set_sub_emitter_amount_at_end(1);
+	set_sub_emitter_keep_velocity(false);
+
 	for (int i = 0; i < PARAM_MAX; i++) {
 		set_param_randomness(Parameter(i), 0);
 	}

+ 42 - 22
scene/resources/particles_material.h

@@ -34,6 +34,17 @@
 #ifndef PARTICLES_MATERIAL_H
 #define PARTICLES_MATERIAL_H
 
+/*
+ TODO:
+-Path following
+*Manual emission
+-Sub Emitters
+-Attractors
+-Emitter positions deformable by bones
+-Collision
+-Proper trails
+*/
+
 class ParticlesMaterial : public Material {
 	GDCLASS(ParticlesMaterial, Material);
 
@@ -71,6 +82,14 @@ public:
 		EMISSION_SHAPE_MAX
 	};
 
+	enum SubEmitterMode {
+		SUB_EMITTER_DISABLED,
+		SUB_EMITTER_CONSTANT,
+		SUB_EMITTER_AT_END,
+		SUB_EMITTER_AT_COLLISION,
+		SUB_EMITTER_MAX
+	};
+
 private:
 	union MaterialKey {
 		struct {
@@ -78,10 +97,9 @@ private:
 			uint32_t texture_color : 1;
 			uint32_t flags : 4;
 			uint32_t emission_shape : 2;
-			uint32_t trail_size_texture : 1;
-			uint32_t trail_color_texture : 1;
 			uint32_t invalid_key : 1;
 			uint32_t has_emission_color : 1;
+			uint32_t sub_emitter : 2;
 		};
 
 		uint32_t key;
@@ -116,9 +134,8 @@ private:
 
 		mk.texture_color = color_ramp.is_valid() ? 1 : 0;
 		mk.emission_shape = emission_shape;
-		mk.trail_color_texture = trail_color_modifier.is_valid() ? 1 : 0;
-		mk.trail_size_texture = trail_size_modifier.is_valid() ? 1 : 0;
 		mk.has_emission_color = emission_shape >= EMISSION_SHAPE_POINTS && emission_color_texture.is_valid();
+		mk.sub_emitter = sub_emitter_mode;
 
 		return mk;
 	}
@@ -178,13 +195,13 @@ private:
 		StringName emission_texture_normal;
 		StringName emission_texture_color;
 
-		StringName trail_divisor;
-		StringName trail_size_modifier;
-		StringName trail_color_modifier;
-
 		StringName gravity;
 
 		StringName lifetime_randomness;
+
+		StringName sub_emitter_frequency;
+		StringName sub_emitter_amount_at_end;
+		StringName sub_emitter_keep_velocity;
 	};
 
 	static ShaderNames *shader_names;
@@ -218,15 +235,14 @@ private:
 
 	bool anim_loop;
 
-	int trail_divisor;
-
-	Ref<CurveTexture> trail_size_modifier;
-	Ref<GradientTexture> trail_color_modifier;
-
 	Vector3 gravity;
 
 	float lifetime_randomness;
 
+	SubEmitterMode sub_emitter_mode;
+	float sub_emitter_frequency;
+	int sub_emitter_amount_at_end;
+	bool sub_emitter_keep_velocity;
 	//do not save emission points here
 
 protected:
@@ -277,15 +293,6 @@ public:
 	Ref<Texture2D> get_emission_color_texture() const;
 	int get_emission_point_count() const;
 
-	void set_trail_divisor(int p_divisor);
-	int get_trail_divisor() const;
-
-	void set_trail_size_modifier(const Ref<CurveTexture> &p_trail_size_modifier);
-	Ref<CurveTexture> get_trail_size_modifier() const;
-
-	void set_trail_color_modifier(const Ref<GradientTexture> &p_trail_color_modifier);
-	Ref<GradientTexture> get_trail_color_modifier() const;
-
 	void set_gravity(const Vector3 &p_gravity);
 	Vector3 get_gravity() const;
 
@@ -296,6 +303,18 @@ public:
 	static void finish_shaders();
 	static void flush_changes();
 
+	void set_sub_emitter_mode(SubEmitterMode p_sub_emitter_mode);
+	SubEmitterMode get_sub_emitter_mode() const;
+
+	void set_sub_emitter_frequency(float p_frequency);
+	float get_sub_emitter_frequency() const;
+
+	void set_sub_emitter_amount_at_end(int p_amount);
+	int get_sub_emitter_amount_at_end() const;
+
+	void set_sub_emitter_keep_velocity(bool p_enable);
+	bool get_sub_emitter_keep_velocity() const;
+
 	RID get_shader_rid() const;
 
 	virtual Shader::Mode get_shader_mode() const override;
@@ -307,5 +326,6 @@ public:
 VARIANT_ENUM_CAST(ParticlesMaterial::Parameter)
 VARIANT_ENUM_CAST(ParticlesMaterial::Flags)
 VARIANT_ENUM_CAST(ParticlesMaterial::EmissionShape)
+VARIANT_ENUM_CAST(ParticlesMaterial::SubEmitterMode)
 
 #endif // PARTICLES_MATERIAL_H

+ 2 - 0
servers/rendering/rasterizer.h

@@ -661,6 +661,8 @@ public:
 	virtual void particles_set_fixed_fps(RID p_particles, int p_fps) = 0;
 	virtual void particles_set_fractional_delta(RID p_particles, bool p_enable) = 0;
 	virtual void particles_restart(RID p_particles) = 0;
+	virtual void particles_emit(RID p_particles, const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) = 0;
+	virtual void particles_set_subemitter(RID p_particles, RID p_subemitter_particles) = 0;
 
 	virtual bool particles_is_inactive(RID p_particles) const = 0;
 

+ 199 - 36
servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp

@@ -3119,24 +3119,40 @@ bool RasterizerStorageRD::particles_get_emitting(RID p_particles) {
 	return particles->emitting;
 }
 
+void RasterizerStorageRD::_particles_free_data(Particles *particles) {
+	if (!particles->particle_buffer.is_valid()) {
+		return;
+	}
+	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->emission_buffer != nullptr) {
+		particles->emission_buffer = nullptr;
+		particles->emission_buffer_data.clear();
+		RD::get_singleton()->free(particles->emission_storage_buffer);
+		particles->emission_storage_buffer = RID();
+	}
+}
+
 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->amount == p_amount) {
+		return;
+	}
 
-	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();
+	_particles_free_data(particles);
 
-		if (particles->particles_sort_buffer.is_valid()) {
-			RD::get_singleton()->free(particles->particles_sort_buffer);
-			particles->particles_sort_buffer = RID();
-		}
-	}
+	particles->amount = p_amount;
 
 	if (particles->amount > 0) {
 		particles->particle_buffer = RD::get_singleton()->storage_buffer_create(sizeof(ParticleData) * p_amount);
@@ -3144,27 +3160,6 @@ void RasterizerStorageRD::particles_set_amount(RID p_particles, int p_amount) {
 		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;
 
@@ -3290,6 +3285,79 @@ void RasterizerStorageRD::particles_restart(RID p_particles) {
 	particles->restart_request = true;
 }
 
+void RasterizerStorageRD::_particles_allocate_emission_buffer(Particles *particles) {
+	ERR_FAIL_COND(particles->emission_buffer != nullptr);
+
+	particles->emission_buffer_data.resize(sizeof(ParticleEmissionBuffer::Data) * particles->amount + sizeof(uint32_t) * 4);
+	zeromem(particles->emission_buffer_data.ptrw(), particles->emission_buffer_data.size());
+	particles->emission_buffer = (ParticleEmissionBuffer *)particles->emission_buffer_data.ptrw();
+	particles->emission_buffer->particle_max = particles->amount;
+
+	particles->emission_storage_buffer = RD::get_singleton()->storage_buffer_create(particles->emission_buffer_data.size(), particles->emission_buffer_data);
+
+	if (RD::get_singleton()->uniform_set_is_valid(particles->particles_material_uniform_set)) {
+		//will need to be re-created
+		RD::get_singleton()->free(particles->particles_material_uniform_set);
+		particles->particles_material_uniform_set = RID();
+	}
+}
+
+void RasterizerStorageRD::particles_set_subemitter(RID p_particles, RID p_subemitter_particles) {
+	Particles *particles = particles_owner.getornull(p_particles);
+	ERR_FAIL_COND(!particles);
+	ERR_FAIL_COND(p_particles == p_subemitter_particles);
+
+	particles->sub_emitter = p_subemitter_particles;
+
+	if (RD::get_singleton()->uniform_set_is_valid(particles->particles_material_uniform_set)) {
+		RD::get_singleton()->free(particles->particles_material_uniform_set);
+		particles->particles_material_uniform_set = RID(); //clear and force to re create sub emitting
+	}
+}
+
+void RasterizerStorageRD::particles_emit(RID p_particles, const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
+	Particles *particles = particles_owner.getornull(p_particles);
+	ERR_FAIL_COND(!particles);
+	ERR_FAIL_COND(particles->amount == 0);
+
+	if (particles->emitting) {
+		particles->clear = true;
+		particles->emitting = false;
+	}
+
+	if (particles->emission_buffer == nullptr) {
+		_particles_allocate_emission_buffer(particles);
+	}
+
+	if (particles->inactive) {
+		//in case it was inactive, make active again
+		particles->inactive = false;
+		particles->inactive_time = 0;
+	}
+
+	int32_t idx = particles->emission_buffer->particle_count;
+	if (idx < particles->emission_buffer->particle_max) {
+		store_transform(p_transform, particles->emission_buffer->data[idx].xform);
+
+		particles->emission_buffer->data[idx].velocity[0] = p_velocity.x;
+		particles->emission_buffer->data[idx].velocity[1] = p_velocity.y;
+		particles->emission_buffer->data[idx].velocity[2] = p_velocity.z;
+
+		particles->emission_buffer->data[idx].custom[0] = p_custom.r;
+		particles->emission_buffer->data[idx].custom[1] = p_custom.g;
+		particles->emission_buffer->data[idx].custom[2] = p_custom.b;
+		particles->emission_buffer->data[idx].custom[3] = p_custom.a;
+
+		particles->emission_buffer->data[idx].color[0] = p_color.r;
+		particles->emission_buffer->data[idx].color[1] = p_color.g;
+		particles->emission_buffer->data[idx].color[2] = p_color.b;
+		particles->emission_buffer->data[idx].color[3] = p_color.a;
+
+		particles->emission_buffer->data[idx].flags = p_emit_flags;
+		particles->emission_buffer->particle_count++;
+	}
+}
+
 void RasterizerStorageRD::particles_request_process(RID p_particles) {
 	Particles *particles = particles_owner.getornull(p_particles);
 	ERR_FAIL_COND(!particles);
@@ -3375,6 +3443,54 @@ RID RasterizerStorageRD::particles_get_draw_pass_mesh(RID p_particles, int p_pas
 }
 
 void RasterizerStorageRD::_particles_process(Particles *p_particles, float p_delta) {
+	if (p_particles->particles_material_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(p_particles->particles_material_uniform_set)) {
+		Vector<RD::Uniform> uniforms;
+
+		{
+			RD::Uniform u;
+			u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.binding = 0;
+			u.ids.push_back(p_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(p_particles->particle_buffer);
+			uniforms.push_back(u);
+		}
+
+		{
+			RD::Uniform u;
+			u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.binding = 2;
+			if (p_particles->emission_storage_buffer.is_valid()) {
+				u.ids.push_back(p_particles->emission_storage_buffer);
+			} else {
+				u.ids.push_back(default_rd_storage_buffer);
+			}
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.binding = 3;
+			Particles *sub_emitter = particles_owner.getornull(p_particles->sub_emitter);
+			if (sub_emitter) {
+				if (sub_emitter->emission_buffer == nullptr) { //no emission buffer, allocate emission buffer
+					_particles_allocate_emission_buffer(sub_emitter);
+				}
+				u.ids.push_back(sub_emitter->emission_storage_buffer);
+			} else {
+				u.ids.push_back(default_rd_storage_buffer);
+			}
+			uniforms.push_back(u);
+		}
+
+		p_particles->particles_material_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.default_shader_rd, 1);
+	}
+
 	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;
@@ -3416,6 +3532,36 @@ void RasterizerStorageRD::_particles_process(Particles *p_particles, float p_del
 	push_constant.lifetime = p_particles->lifetime;
 	push_constant.trail_size = 1;
 	push_constant.use_fractional_delta = p_particles->fractional_delta;
+	push_constant.sub_emitter_mode = !p_particles->emitting && p_particles->emission_buffer && (p_particles->emission_buffer->particle_count > 0 || p_particles->force_sub_emit);
+
+	p_particles->force_sub_emit = false; //reset
+
+	Particles *sub_emitter = particles_owner.getornull(p_particles->sub_emitter);
+
+	if (sub_emitter && sub_emitter->emission_storage_buffer.is_valid()) {
+		//	print_line("updating subemitter buffer");
+		int32_t zero[4] = { 0, sub_emitter->amount, 0, 0 };
+		RD::get_singleton()->buffer_update(sub_emitter->emission_storage_buffer, 0, sizeof(uint32_t) * 4, zero, true);
+		push_constant.can_emit = true;
+
+		if (sub_emitter->emitting) {
+			sub_emitter->emitting = false;
+			sub_emitter->clear = true; //will need to clear if it was emitting, sorry
+		}
+		//make sure the sub emitter processes particles too
+		sub_emitter->inactive = false;
+		sub_emitter->inactive_time = 0;
+
+		sub_emitter->force_sub_emit = true;
+
+	} else {
+		push_constant.can_emit = false;
+	}
+
+	if (p_particles->emission_buffer && p_particles->emission_buffer->particle_count) {
+		RD::get_singleton()->buffer_update(p_particles->emission_storage_buffer, 0, sizeof(uint32_t) * 4 + sizeof(ParticleEmissionBuffer::Data) * p_particles->emission_buffer->particle_count, p_particles->emission_buffer, true);
+		p_particles->emission_buffer->particle_count = 0;
+	}
 
 	p_particles->clear = false;
 
@@ -3617,9 +3763,6 @@ void RasterizerStorageRD::update_particles() {
 			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
 	}
 }
@@ -6519,6 +6662,11 @@ bool RasterizerStorageRD::free(RID p_rid) {
 		light->instance_dependency.instance_notify_deleted(p_rid);
 		light_owner.free(p_rid);
 
+	} else if (particles_owner.owns(p_rid)) {
+		Particles *particles = particles_owner.getornull(p_rid);
+		_particles_free_data(particles);
+		particles->instance_dependency.instance_notify_deleted(p_rid);
+		particles_owner.free(p_rid);
 	} else if (render_target_owner.owns(p_rid)) {
 		RenderTarget *rt = render_target_owner.getornull(p_rid);
 
@@ -7013,6 +7161,17 @@ RasterizerStorageRD::RasterizerStorageRD() {
 		//actions.renames["GRAVITY"] = "current_gravity";
 		actions.renames["EMISSION_TRANSFORM"] = "FRAME.emission_transform";
 		actions.renames["RANDOM_SEED"] = "FRAME.random_seed";
+		actions.renames["FLAG_EMIT_POSITION"] = "EMISSION_FLAG_HAS_POSITION";
+		actions.renames["FLAG_EMIT_ROT_SCALE"] = "EMISSION_FLAG_HAS_ROTATION_SCALE";
+		actions.renames["FLAG_EMIT_VELOCITY"] = "EMISSION_FLAG_HAS_VELOCITY";
+		actions.renames["FLAG_EMIT_COLOR"] = "EMISSION_FLAG_HAS_COLOR";
+		actions.renames["FLAG_EMIT_CUSTOM"] = "EMISSION_FLAG_HAS_CUSTOM";
+		actions.renames["RESTART_POSITION"] = "restart_position";
+		actions.renames["RESTART_ROT_SCALE"] = "restart_rotation_scale";
+		actions.renames["RESTART_VELOCITY"] = "restart_velocity";
+		actions.renames["RESTART_COLOR"] = "restart_color";
+		actions.renames["RESTART_CUSTOM"] = "restart_custom";
+		actions.renames["emit_particle"] = "emit_particle";
 
 		actions.render_mode_defines["disable_force"] = "#define DISABLE_FORCE\n";
 		actions.render_mode_defines["disable_velocity"] = "#define DISABLE_VELOCITY\n";
@@ -7075,6 +7234,8 @@ RasterizerStorageRD::RasterizerStorageRD() {
 		particles_shader.base_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.default_shader_rd, 0);
 	}
 
+	default_rd_storage_buffer = RD::get_singleton()->storage_buffer_create(sizeof(uint32_t) * 4);
+
 	{
 		Vector<String> copy_modes;
 		copy_modes.push_back("\n#define MODE_FILL_INSTANCES\n");
@@ -7115,6 +7276,8 @@ RasterizerStorageRD::~RasterizerStorageRD() {
 	}
 	giprobe_sdf_shader.version_free(giprobe_sdf_shader_version);
 
+	RD::get_singleton()->free(default_rd_storage_buffer);
+
 	if (decal_atlas.textures.size()) {
 		ERR_PRINT("Decal Atlas: " + itos(decal_atlas.textures.size()) + " textures were not removed from the atlas.");
 	}

+ 37 - 1
servers/rendering/rasterizer_rd/rasterizer_storage_rd.h

@@ -249,6 +249,7 @@ private:
 
 	RID default_rd_textures[DEFAULT_RD_TEXTURE_MAX];
 	RID default_rd_samplers[RS::CANVAS_ITEM_TEXTURE_FILTER_MAX][RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX];
+	RID default_rd_storage_buffer;
 
 	/* DECAL ATLAS */
 
@@ -482,6 +483,25 @@ private:
 		float emission_transform[16];
 	};
 
+	struct ParticleEmissionBufferData {
+	};
+
+	struct ParticleEmissionBuffer {
+		struct Data {
+			float xform[16];
+			float velocity[3];
+			uint32_t flags;
+			float color[4];
+			float custom[4];
+		};
+
+		int32_t particle_count;
+		int32_t particle_max;
+		uint32_t pad1;
+		uint32_t pad2;
+		Data data[1]; //its 2020 and empty arrays are still non standard in C++
+	};
+
 	struct Particles {
 		bool inactive;
 		float inactive_time;
@@ -515,6 +535,8 @@ private:
 		bool dirty = false;
 		Particles *update_list = nullptr;
 
+		RID sub_emitter;
+
 		float phase;
 		float prev_phase;
 		uint64_t prev_ticks;
@@ -530,8 +552,15 @@ private:
 
 		bool clear;
 
+		bool force_sub_emit = false;
+
 		Transform emission_transform;
 
+		Vector<uint8_t> emission_buffer_data;
+
+		ParticleEmissionBuffer *emission_buffer = nullptr;
+		RID emission_storage_buffer;
+
 		Particles() :
 				inactive(true),
 				inactive_time(0.0),
@@ -562,6 +591,8 @@ private:
 	};
 
 	void _particles_process(Particles *p_particles, float p_delta);
+	void _particles_allocate_emission_buffer(Particles *particles);
+	void _particles_free_data(Particles *particles);
 
 	struct ParticlesShader {
 		struct PushConstant {
@@ -569,8 +600,11 @@ private:
 			uint32_t clear;
 			uint32_t total_particles;
 			uint32_t trail_size;
+
 			uint32_t use_fractional_delta;
-			uint32_t pad[3];
+			uint32_t sub_emitter_mode;
+			uint32_t can_emit;
+			uint32_t pad;
 		};
 
 		ParticlesShaderRD shader;
@@ -1650,6 +1684,8 @@ public:
 	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_emit(RID p_particles, const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags);
+	void particles_set_subemitter(RID p_particles, RID p_subemitter_particles);
 
 	void particles_set_draw_order(RID p_particles, RS::ParticlesDrawOrder p_order);
 

+ 185 - 53
servers/rendering/rasterizer_rd/shaders/particles.glsl

@@ -66,6 +66,38 @@ layout(set = 1, binding = 1, std430) restrict buffer Particles {
 }
 particles;
 
+#define EMISSION_FLAG_HAS_POSITION 1
+#define EMISSION_FLAG_HAS_ROTATION_SCALE 2
+#define EMISSION_FLAG_HAS_VELOCITY 4
+#define EMISSION_FLAG_HAS_COLOR 8
+#define EMISSION_FLAG_HAS_CUSTOM 16
+
+struct ParticleEmission {
+	mat4 xform;
+	vec3 velocity;
+	uint flags;
+	vec4 color;
+	vec4 custom;
+};
+
+layout(set = 1, binding = 2, std430) restrict volatile coherent buffer SourceEmission {
+	int particle_count;
+	uint pad0;
+	uint pad1;
+	uint pad2;
+	ParticleEmission data[];
+}
+src_particles;
+
+layout(set = 1, binding = 3, std430) restrict volatile coherent buffer DestEmission {
+	int particle_count;
+	int particle_max;
+	uint pad1;
+	uint pad2;
+	ParticleEmission data[];
+}
+dst_particles;
+
 /* SET 2: MATERIAL */
 
 #ifdef USE_MATERIAL_UNIFORMS
@@ -82,7 +114,9 @@ layout(push_constant, binding = 0, std430) uniform Params {
 	uint total_particles;
 	uint trail_size;
 	bool use_fractional_delta;
-	uint pad[3];
+	bool sub_emitter_mode;
+	bool can_emit;
+	uint pad;
 }
 params;
 
@@ -93,6 +127,51 @@ uint hash(uint x) {
 	return x;
 }
 
+bool emit_particle(mat4 p_xform, vec3 p_velocity, vec4 p_color, vec4 p_custom, uint p_flags) {
+	if (!params.can_emit) {
+		return false;
+	}
+
+	bool valid = false;
+
+	int dst_index = atomicAdd(dst_particles.particle_count, 1);
+
+	if (dst_index >= dst_particles.particle_max) {
+		atomicAdd(dst_particles.particle_count, -1);
+		return false;
+	}
+	/*
+	valid = true;
+
+	int attempts = 256; // never trust compute
+	while(attempts-- > 0) {
+	    dst_index = dst_particles.particle_count;
+	    if (dst_index == dst_particles.particle_max) {
+		return false; //cant emit anymore
+	    }
+
+	    if (atomicCompSwap(dst_particles.particle_count, dst_index, dst_index +1 ) != dst_index) {
+		continue;
+	    }
+	    valid=true;
+	    break;
+	}
+
+	barrier();
+
+	if (!valid) {
+		return false; //gave up (attempts exhausted)
+	}
+*/
+	dst_particles.data[dst_index].xform = p_xform;
+	dst_particles.data[dst_index].velocity = p_velocity;
+	dst_particles.data[dst_index].color = p_color;
+	dst_particles.data[dst_index].custom = p_custom;
+	dst_particles.data[dst_index].flags = p_flags;
+
+	return true;
+}
+
 /* clang-format off */
 
 COMPUTE_SHADER_GLOBALS
@@ -118,76 +197,129 @@ void main() {
 
 	float mass = 1.0;
 
-	float restart_phase = float(index) / float(params.total_particles);
+	bool restart = false;
 
-	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);
+	bool restart_position = false;
+	bool restart_rotation_scale = false;
+	bool restart_velocity = false;
+	bool restart_color = false;
+	bool restart_custom = false;
+
+	if (params.clear) {
+		PARTICLE.color = vec4(1.0);
+		PARTICLE.custom = vec4(0.0);
+		PARTICLE.velocity = vec3(0.0);
+		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));
 	}
 
-	restart_phase *= (1.0 - FRAME.explosiveness);
+	if (params.sub_emitter_mode) {
+		if (!PARTICLE.is_active) {
+			int src_index = atomicAdd(src_particles.particle_count, -1) - 1;
 
-	bool restart = false;
+			if (src_index >= 0) {
+				PARTICLE.is_active = true;
+				restart = true;
 
-	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 (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_POSITION)) {
+					PARTICLE.xform[3] = src_particles.data[src_index].xform[3];
+				} else {
+					PARTICLE.xform[3] = vec4(0, 0, 0, 1);
+					restart_position = true;
+				}
+				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_ROTATION_SCALE)) {
+					PARTICLE.xform[0] = src_particles.data[src_index].xform[0];
+					PARTICLE.xform[1] = src_particles.data[src_index].xform[1];
+					PARTICLE.xform[2] = src_particles.data[src_index].xform[2];
+				} else {
+					PARTICLE.xform[0] = vec4(1, 0, 0, 0);
+					PARTICLE.xform[1] = vec4(0, 1, 0, 0);
+					PARTICLE.xform[2] = vec4(0, 0, 1, 0);
+					restart_rotation_scale = true;
+				}
+				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_VELOCITY)) {
+					PARTICLE.velocity = src_particles.data[src_index].velocity;
+				} else {
+					PARTICLE.velocity = vec3(0);
+					restart_velocity = true;
+				}
+				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_COLOR)) {
+					PARTICLE.color = src_particles.data[src_index].color;
+				} else {
+					PARTICLE.color = vec4(1);
+					restart_color = true;
+				}
 
-		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;
+				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_CUSTOM)) {
+					PARTICLE.custom = src_particles.data[src_index].custom;
+				} else {
+					PARTICLE.custom = vec4(0);
+					restart_custom = true;
+				}
 			}
 		}
 
-	} 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 (FRAME.emitting) {
+		float restart_phase = float(index) / float(params.total_particles);
 
-		} else if (restart_phase < FRAME.system_phase) {
-			restart = true;
-			if (params.use_fractional_delta) {
-				local_delta = (FRAME.system_phase - restart_phase) * params.lifetime;
+		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);
 		}
-	}
 
-	uint current_cycle = FRAME.cycle;
+		restart_phase *= (1.0 - FRAME.explosiveness);
 
-	if (FRAME.system_phase < restart_phase) {
-		current_cycle -= uint(1);
-	}
+		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
 
-	uint particle_number = current_cycle * uint(params.total_particles) + particle;
+			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;
+				}
+			}
 
-	if (restart) {
-		PARTICLE.is_active = FRAME.emitting;
-	}
+		} 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;
+				}
 
-#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;
+			} 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;
+			restart_position = true;
+			restart_rotation_scale = true;
+			restart_velocity = true;
+			restart_color = true;
+			restart_custom = true;
 		}
-		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) {

+ 2 - 0
servers/rendering/rendering_server_raster.h

@@ -442,6 +442,8 @@ public:
 	BIND1R(bool, particles_is_inactive, RID)
 	BIND1(particles_request_process, RID)
 	BIND1(particles_restart, RID)
+	BIND6(particles_emit, RID, const Transform &, const Vector3 &, const Color &, const Color &, uint32_t)
+	BIND2(particles_set_subemitter, RID, RID)
 
 	BIND2(particles_set_draw_order, RID, RS::ParticlesDrawOrder)
 

+ 3 - 0
servers/rendering/rendering_server_wrap_mt.h

@@ -360,11 +360,14 @@ public:
 	FUNC1(particles_request_process, RID)
 	FUNC1(particles_restart, RID)
 
+	FUNC6(particles_emit, RID, const Transform &, const Vector3 &, const Color &, const Color &, uint32_t)
+
 	FUNC2(particles_set_draw_order, RID, RS::ParticlesDrawOrder)
 
 	FUNC2(particles_set_draw_passes, RID, int)
 	FUNC3(particles_set_draw_pass_mesh, RID, int, RID)
 	FUNC2(particles_set_emission_transform, RID, const Transform &)
+	FUNC2(particles_set_subemitter, RID, RID)
 
 	FUNC1R(AABB, particles_get_current_aabb, RID)
 

+ 109 - 71
servers/rendering/shader_language.cpp

@@ -920,13 +920,13 @@ void ShaderLanguage::clear() {
 	}
 }
 
-bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_reassign, const Map<StringName, BuiltInInfo> &p_builtin_types, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name) {
-	if (p_builtin_types.has(p_identifier)) {
+bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name) {
+	if (p_function_info.built_ins.has(p_identifier)) {
 		if (r_data_type) {
-			*r_data_type = p_builtin_types[p_identifier].type;
+			*r_data_type = p_function_info.built_ins[p_identifier].type;
 		}
 		if (r_is_const) {
-			*r_is_const = p_builtin_types[p_identifier].constant;
+			*r_is_const = p_function_info.built_ins[p_identifier].constant;
 		}
 		if (r_type) {
 			*r_type = IDENTIFIER_BUILTIN_VAR;
@@ -935,6 +935,20 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea
 		return true;
 	}
 
+	if (p_function_info.stage_functions.has(p_identifier)) {
+		if (r_data_type) {
+			*r_data_type = p_function_info.stage_functions[p_identifier].return_type;
+		}
+		if (r_is_const) {
+			*r_is_const = true;
+		}
+		if (r_type) {
+			*r_type = IDENTIFIER_FUNCTION;
+		}
+
+		return true;
+	}
+
 	FunctionNode *function = nullptr;
 
 	while (p_block) {
@@ -2152,7 +2166,7 @@ const ShaderLanguage::BuiltinFuncOutArgs ShaderLanguage::builtin_func_out_args[]
 	{ nullptr, 0 }
 };
 
-bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, OperatorNode *p_func, DataType *r_ret_type, StringName *r_ret_type_str) {
+bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionInfo &p_function_info, OperatorNode *p_func, DataType *r_ret_type, StringName *r_ret_type_str) {
 	ERR_FAIL_COND_V(p_func->op != OP_CALL && p_func->op != OP_CONSTRUCT, false);
 
 	Vector<DataType> args;
@@ -2169,6 +2183,30 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const Map<Strin
 
 	int argcount = args.size();
 
+	if (p_function_info.stage_functions.has(name)) {
+		//stage based function
+		const StageFunctionInfo &sf = p_function_info.stage_functions[name];
+		if (argcount != sf.arguments.size()) {
+			_set_error(vformat("Invalid number of arguments when calling stage function '%s', which expects %d arguments.", String(name), sf.arguments.size()));
+			return false;
+		}
+		//validate arguments
+		for (int i = 0; i < argcount; i++) {
+			if (args[i] != sf.arguments[i].type) {
+				_set_error(vformat("Invalid argument type when calling stage function '%s', type expected is '%s'.", String(name), String(get_datatype_name(sf.arguments[i].type))));
+				return false;
+			}
+		}
+
+		if (r_ret_type) {
+			*r_ret_type = sf.return_type;
+		}
+		if (r_ret_type_str) {
+			*r_ret_type_str = "";
+		}
+		return true;
+	}
+
 	bool failed_builtin = false;
 	bool unsupported_builtin = false;
 	int builtin_idx = 0;
@@ -2241,8 +2279,8 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const Map<Strin
 										if (shader->uniforms.has(varname)) {
 											fail = true;
 										} else {
-											if (p_builtin_types.has(varname)) {
-												BuiltInInfo info = p_builtin_types[varname];
+											if (p_function_info.built_ins.has(varname)) {
+												BuiltInInfo info = p_function_info.built_ins[varname];
 												if (info.constant) {
 													fail = true;
 												}
@@ -2278,7 +2316,7 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const Map<Strin
 								const BlockNode *b = p_block;
 								bool valid = false;
 								while (b) {
-									if (b->variables.has(var_name) || p_builtin_types.has(var_name)) {
+									if (b->variables.has(var_name) || p_function_info.built_ins.has(var_name)) {
 										valid = true;
 										break;
 									}
@@ -2492,7 +2530,7 @@ bool ShaderLanguage::_compare_datatypes_in_nodes(Node *a, Node *b) const {
 	return true;
 }
 
-bool ShaderLanguage::_parse_function_arguments(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, OperatorNode *p_func, int *r_complete_arg) {
+bool ShaderLanguage::_parse_function_arguments(BlockNode *p_block, const FunctionInfo &p_function_info, OperatorNode *p_func, int *r_complete_arg) {
 	TkPos pos = _get_tkpos();
 	Token tk = _get_token();
 
@@ -2514,7 +2552,7 @@ bool ShaderLanguage::_parse_function_arguments(BlockNode *p_block, const Map<Str
 			}
 		}
 
-		Node *arg = _parse_and_reduce_expression(p_block, p_builtin_types);
+		Node *arg = _parse_and_reduce_expression(p_block, p_function_info);
 
 		if (!arg) {
 			return false;
@@ -3053,16 +3091,16 @@ bool ShaderLanguage::_is_operator_assign(Operator p_op) const {
 	return false;
 }
 
-bool ShaderLanguage::_validate_assign(Node *p_node, const Map<StringName, BuiltInInfo> &p_builtin_types, String *r_message) {
+bool ShaderLanguage::_validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message) {
 	if (p_node->type == Node::TYPE_OPERATOR) {
 		OperatorNode *op = static_cast<OperatorNode *>(p_node);
 
 		if (op->op == OP_INDEX) {
-			return _validate_assign(op->arguments[0], p_builtin_types, r_message);
+			return _validate_assign(op->arguments[0], p_function_info, r_message);
 
 		} else if (_is_operator_assign(op->op)) {
 			//chained assignment
-			return _validate_assign(op->arguments[1], p_builtin_types, r_message);
+			return _validate_assign(op->arguments[1], p_function_info, r_message);
 
 		} else if (op->op == OP_CALL) {
 			if (r_message) {
@@ -3081,7 +3119,7 @@ bool ShaderLanguage::_validate_assign(Node *p_node, const Map<StringName, BuiltI
 			return false;
 		}
 
-		return _validate_assign(member->owner, p_builtin_types, r_message);
+		return _validate_assign(member->owner, p_function_info, r_message);
 
 	} else if (p_node->type == Node::TYPE_VARIABLE) {
 		VariableNode *var = static_cast<VariableNode *>(p_node);
@@ -3107,7 +3145,7 @@ bool ShaderLanguage::_validate_assign(Node *p_node, const Map<StringName, BuiltI
 			return false;
 		}
 
-		if (!(p_builtin_types.has(var->name) && p_builtin_types[var->name].constant)) {
+		if (!(p_function_info.built_ins.has(var->name) && p_function_info.built_ins[var->name].constant)) {
 			return true;
 		}
 	} else if (p_node->type == Node::TYPE_ARRAY) {
@@ -3204,7 +3242,7 @@ bool ShaderLanguage::_propagate_function_call_sampler_builtin_reference(StringNa
 	ERR_FAIL_V(false); //bug? function not found
 }
 
-ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types) {
+ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info) {
 	Vector<Expression> expression;
 
 	//Vector<TokenType> operators;
@@ -3220,7 +3258,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 		if (tk.type == TK_PARENTHESIS_OPEN) {
 			//handle subexpression
 
-			expr = _parse_and_reduce_expression(p_block, p_builtin_types);
+			expr = _parse_and_reduce_expression(p_block, p_function_info);
 			if (!expr) {
 				return nullptr;
 			}
@@ -3293,7 +3331,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 
 			int carg = -1;
 
-			bool ok = _parse_function_arguments(p_block, p_builtin_types, func, &carg);
+			bool ok = _parse_function_arguments(p_block, p_function_info, func, &carg);
 
 			if (carg >= 0) {
 				completion_type = COMPLETION_CALL_ARGUMENTS;
@@ -3307,7 +3345,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 				return nullptr;
 			}
 
-			if (!_validate_function_call(p_block, p_builtin_types, func, &func->return_cache, &func->struct_name)) {
+			if (!_validate_function_call(p_block, p_function_info, func, &func->return_cache, &func->struct_name)) {
 				_set_error("No matching constructor found for: '" + String(funcname->name) + "'");
 				return nullptr;
 			}
@@ -3383,7 +3421,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 									} else {
 										_set_tkpos(pos2);
 
-										Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
+										Node *n = _parse_and_reduce_expression(p_block, p_function_info);
 										if (!n || n->type != Node::TYPE_CONSTANT || n->get_datatype() != TYPE_INT) {
 											_set_error("Expected single integer constant > 0");
 											return nullptr;
@@ -3444,7 +3482,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 
 							if (tk.type == TK_PARENTHESIS_OPEN || auto_size) { // initialization
 								while (true) {
-									Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
+									Node *n = _parse_and_reduce_expression(p_block, p_function_info);
 									if (!n) {
 										return nullptr;
 									}
@@ -3484,7 +3522,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 
 							nexpr = an;
 						} else {
-							nexpr = _parse_and_reduce_expression(p_block, p_builtin_types);
+							nexpr = _parse_and_reduce_expression(p_block, p_function_info);
 							if (!nexpr) {
 								return nullptr;
 							}
@@ -3526,7 +3564,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 
 					int carg = -1;
 
-					bool ok = _parse_function_arguments(p_block, p_builtin_types, func, &carg);
+					bool ok = _parse_function_arguments(p_block, p_function_info, func, &carg);
 
 					// Check if block has a variable with the same name as function to prevent shader crash.
 					ShaderLanguage::BlockNode *bnode = p_block;
@@ -3568,7 +3606,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 						return nullptr;
 					}
 
-					if (!_validate_function_call(p_block, p_builtin_types, func, &func->return_cache, &func->struct_name)) {
+					if (!_validate_function_call(p_block, p_function_info, func, &func->return_cache, &func->struct_name)) {
 						_set_error("No matching function found for: '" + String(funcname->name) + "'");
 						return nullptr;
 					}
@@ -3620,8 +3658,8 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 												} else if (shader->uniforms.has(varname)) {
 													error = true;
 												} else {
-													if (p_builtin_types.has(varname)) {
-														BuiltInInfo info = p_builtin_types[varname];
+													if (p_function_info.built_ins.has(varname)) {
+														BuiltInInfo info = p_function_info.built_ins[varname];
 														if (info.constant) {
 															error = true;
 														}
@@ -3653,7 +3691,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 											if (!_propagate_function_call_sampler_uniform_settings(name, i, u->filter, u->repeat)) {
 												return nullptr;
 											}
-										} else if (p_builtin_types.has(varname)) {
+										} else if (p_function_info.built_ins.has(varname)) {
 											//a built-in
 											if (!_propagate_function_call_sampler_builtin_reference(name, i, varname)) {
 												return nullptr;
@@ -3708,7 +3746,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 						return nullptr;
 					}
 				} else {
-					if (!_find_identifier(p_block, false, p_builtin_types, identifier, &data_type, &ident_type, &is_const, &array_size, &struct_name)) {
+					if (!_find_identifier(p_block, false, p_function_info, identifier, &data_type, &ident_type, &is_const, &array_size, &struct_name)) {
 						_set_error("Unknown identifier in expression: " + String(identifier));
 						return nullptr;
 					}
@@ -3733,7 +3771,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 					if (tk.type == TK_PERIOD) {
 						completion_class = TAG_ARRAY;
 						p_block->block_tag = SubClassTag::TAG_ARRAY;
-						call_expression = _parse_and_reduce_expression(p_block, p_builtin_types);
+						call_expression = _parse_and_reduce_expression(p_block, p_function_info);
 						p_block->block_tag = SubClassTag::TAG_GLOBAL;
 						if (!call_expression) {
 							return nullptr;
@@ -3741,7 +3779,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 						data_type = call_expression->get_datatype();
 					} else { // indexing
 
-						index_expression = _parse_and_reduce_expression(p_block, p_builtin_types);
+						index_expression = _parse_and_reduce_expression(p_block, p_function_info);
 						if (!index_expression) {
 							return nullptr;
 						}
@@ -4121,7 +4159,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 						_set_error("Nested array length() is not yet implemented");
 						return nullptr;
 					} else if (tk.type == TK_BRACKET_OPEN) {
-						Node *index_expression = _parse_and_reduce_expression(p_block, p_builtin_types);
+						Node *index_expression = _parse_and_reduce_expression(p_block, p_function_info);
 						if (!index_expression) {
 							return nullptr;
 						}
@@ -4170,7 +4208,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 
 	*/
 			} else if (tk.type == TK_BRACKET_OPEN) {
-				Node *index = _parse_and_reduce_expression(p_block, p_builtin_types);
+				Node *index = _parse_and_reduce_expression(p_block, p_function_info);
 				if (!index) {
 					return nullptr;
 				}
@@ -4312,7 +4350,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 					return nullptr;
 				}
 
-				if (!_validate_assign(expr, p_builtin_types)) {
+				if (!_validate_assign(expr, p_function_info)) {
 					_set_error("Invalid use of increment/decrement operator in constant expression.");
 					return nullptr;
 				}
@@ -4610,7 +4648,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 			for (int i = expr_pos - 1; i >= next_op; i--) {
 				OperatorNode *op = alloc_node<OperatorNode>();
 				op->op = expression[i].op;
-				if ((op->op == OP_INCREMENT || op->op == OP_DECREMENT) && !_validate_assign(expression[i + 1].node, p_builtin_types)) {
+				if ((op->op == OP_INCREMENT || op->op == OP_DECREMENT) && !_validate_assign(expression[i + 1].node, p_function_info)) {
 					_set_error("Can't use increment/decrement operator in constant expression.");
 					return nullptr;
 				}
@@ -4684,7 +4722,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 
 			if (_is_operator_assign(op->op)) {
 				String assign_message;
-				if (!_validate_assign(expression[next_op - 1].node, p_builtin_types, &assign_message)) {
+				if (!_validate_assign(expression[next_op - 1].node, p_function_info, &assign_message)) {
 					_set_error(assign_message);
 					return nullptr;
 				}
@@ -4838,8 +4876,8 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha
 	return p_node;
 }
 
-ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types) {
-	ShaderLanguage::Node *expr = _parse_expression(p_block, p_builtin_types);
+ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info) {
+	ShaderLanguage::Node *expr = _parse_expression(p_block, p_function_info);
 	if (!expr) { //errored
 		return nullptr;
 	}
@@ -4849,7 +4887,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_
 	return expr;
 }
 
-Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, bool p_just_one, bool p_can_break, bool p_can_continue) {
+Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_just_one, bool p_can_break, bool p_can_continue) {
 	while (true) {
 		TkPos pos = _get_tkpos();
 
@@ -4933,7 +4971,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 
 				StringName name = tk.text;
 				ShaderLanguage::IdentifierType itype;
-				if (_find_identifier(p_block, true, p_builtin_types, name, (ShaderLanguage::DataType *)nullptr, &itype)) {
+				if (_find_identifier(p_block, true, p_function_info, name, (ShaderLanguage::DataType *)nullptr, &itype)) {
 					if (itype != IDENTIFIER_FUNCTION) {
 						_set_error("Redefinition of '" + String(name) + "'");
 						return ERR_PARSE_ERROR;
@@ -5052,7 +5090,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 								} else {
 									_set_tkpos(pos2);
 
-									Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
+									Node *n = _parse_and_reduce_expression(p_block, p_function_info);
 									if (!n || n->type != Node::TYPE_CONSTANT || n->get_datatype() != TYPE_INT) {
 										_set_error("Expected single integer constant > 0");
 										return ERR_PARSE_ERROR;
@@ -5133,7 +5171,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 
 						if (tk.type == TK_PARENTHESIS_OPEN || curly) { // initialization
 							while (true) {
-								Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
+								Node *n = _parse_and_reduce_expression(p_block, p_function_info);
 								if (!n) {
 									return ERR_PARSE_ERROR;
 								}
@@ -5205,7 +5243,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 					decl.initializer = nullptr;
 
 					//variable created with assignment! must parse an expression
-					Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
+					Node *n = _parse_and_reduce_expression(p_block, p_function_info);
 					if (!n) {
 						return ERR_PARSE_ERROR;
 					}
@@ -5265,7 +5303,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			//a sub block, just because..
 			BlockNode *block = alloc_node<BlockNode>();
 			block->parent_block = p_block;
-			if (_parse_block(block, p_builtin_types, false, p_can_break, p_can_continue) != OK) {
+			if (_parse_block(block, p_function_info, false, p_can_break, p_can_continue) != OK) {
 				return ERR_PARSE_ERROR;
 			}
 			p_block->statements.push_back(block);
@@ -5279,7 +5317,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 
 			ControlFlowNode *cf = alloc_node<ControlFlowNode>();
 			cf->flow_op = FLOW_OP_IF;
-			Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
+			Node *n = _parse_and_reduce_expression(p_block, p_function_info);
 			if (!n) {
 				return ERR_PARSE_ERROR;
 			}
@@ -5301,7 +5339,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			cf->blocks.push_back(block);
 			p_block->statements.push_back(cf);
 
-			Error err = _parse_block(block, p_builtin_types, true, p_can_break, p_can_continue);
+			Error err = _parse_block(block, p_function_info, true, p_can_break, p_can_continue);
 			if (err) {
 				return err;
 			}
@@ -5312,7 +5350,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 				block = alloc_node<BlockNode>();
 				block->parent_block = p_block;
 				cf->blocks.push_back(block);
-				err = _parse_block(block, p_builtin_types, true, p_can_break, p_can_continue);
+				err = _parse_block(block, p_function_info, true, p_can_break, p_can_continue);
 
 			} else {
 				_set_tkpos(pos); //rollback
@@ -5331,7 +5369,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			}
 			ControlFlowNode *cf = alloc_node<ControlFlowNode>();
 			cf->flow_op = FLOW_OP_SWITCH;
-			Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
+			Node *n = _parse_and_reduce_expression(p_block, p_function_info);
 			if (!n) {
 				return ERR_PARSE_ERROR;
 			}
@@ -5359,7 +5397,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			int prev_type = TK_CF_CASE;
 			while (true) { // Go-through multiple cases.
 
-				if (_parse_block(switch_block, p_builtin_types, true, true, false) != OK) {
+				if (_parse_block(switch_block, p_function_info, true, true, false) != OK) {
 					return ERR_PARSE_ERROR;
 				}
 				pos = _get_tkpos();
@@ -5460,7 +5498,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			cf->blocks.push_back(case_block);
 			p_block->statements.push_back(cf);
 
-			Error err = _parse_block(case_block, p_builtin_types, false, true, false);
+			Error err = _parse_block(case_block, p_function_info, false, true, false);
 			if (err) {
 				return err;
 			}
@@ -5494,7 +5532,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			cf->blocks.push_back(default_block);
 			p_block->statements.push_back(cf);
 
-			Error err = _parse_block(default_block, p_builtin_types, false, true, false);
+			Error err = _parse_block(default_block, p_function_info, false, true, false);
 			if (err) {
 				return err;
 			}
@@ -5511,7 +5549,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 				do_block = alloc_node<BlockNode>();
 				do_block->parent_block = p_block;
 
-				Error err = _parse_block(do_block, p_builtin_types, true, true, true);
+				Error err = _parse_block(do_block, p_function_info, true, true, true);
 				if (err) {
 					return err;
 				}
@@ -5535,7 +5573,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			} else {
 				cf->flow_op = FLOW_OP_WHILE;
 			}
-			Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
+			Node *n = _parse_and_reduce_expression(p_block, p_function_info);
 			if (!n) {
 				return ERR_PARSE_ERROR;
 			}
@@ -5552,7 +5590,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 				cf->blocks.push_back(block);
 				p_block->statements.push_back(cf);
 
-				Error err = _parse_block(block, p_builtin_types, true, true, true);
+				Error err = _parse_block(block, p_function_info, true, true, true);
 				if (err) {
 					return err;
 				}
@@ -5583,11 +5621,11 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			init_block->parent_block = p_block;
 			init_block->single_statement = true;
 			cf->blocks.push_back(init_block);
-			if (_parse_block(init_block, p_builtin_types, true, false, false) != OK) {
+			if (_parse_block(init_block, p_function_info, true, false, false) != OK) {
 				return ERR_PARSE_ERROR;
 			}
 
-			Node *n = _parse_and_reduce_expression(init_block, p_builtin_types);
+			Node *n = _parse_and_reduce_expression(init_block, p_function_info);
 			if (!n) {
 				return ERR_PARSE_ERROR;
 			}
@@ -5605,7 +5643,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 
 			cf->expressions.push_back(n);
 
-			n = _parse_and_reduce_expression(init_block, p_builtin_types);
+			n = _parse_and_reduce_expression(init_block, p_function_info);
 			if (!n) {
 				return ERR_PARSE_ERROR;
 			}
@@ -5623,7 +5661,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 			cf->blocks.push_back(block);
 			p_block->statements.push_back(cf);
 
-			Error err = _parse_block(block, p_builtin_types, true, true, true);
+			Error err = _parse_block(block, p_function_info, true, true, true);
 			if (err) {
 				return err;
 			}
@@ -5659,7 +5697,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 				}
 			} else {
 				_set_tkpos(pos); //rollback, wants expression
-				Node *expr = _parse_and_reduce_expression(p_block, p_builtin_types);
+				Node *expr = _parse_and_reduce_expression(p_block, p_function_info);
 				if (!expr) {
 					return ERR_PARSE_ERROR;
 				}
@@ -5761,7 +5799,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui
 		} else {
 			//nothing else, so expression
 			_set_tkpos(pos); //rollback
-			Node *expr = _parse_and_reduce_expression(p_block, p_builtin_types);
+			Node *expr = _parse_and_reduce_expression(p_block, p_function_info);
 			if (!expr) {
 				return ERR_PARSE_ERROR;
 			}
@@ -6102,7 +6140,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 
 				name = tk.text;
 
-				if (_find_identifier(nullptr, false, Map<StringName, BuiltInInfo>(), name)) {
+				if (_find_identifier(nullptr, false, FunctionInfo(), name)) {
 					_set_error("Redefinition of '" + String(name) + "'");
 					return ERR_PARSE_ERROR;
 				}
@@ -6351,7 +6389,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 					//reset scope for next uniform
 
 					if (tk.type == TK_OP_ASSIGN) {
-						Node *expr = _parse_and_reduce_expression(nullptr, Map<StringName, BuiltInInfo>());
+						Node *expr = _parse_and_reduce_expression(nullptr, FunctionInfo());
 						if (!expr) {
 							return ERR_PARSE_ERROR;
 						}
@@ -6476,7 +6514,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 					return ERR_PARSE_ERROR;
 				}
 
-				if (_find_identifier(nullptr, false, Map<StringName, BuiltInInfo>(), name)) {
+				if (_find_identifier(nullptr, false, FunctionInfo(), name)) {
 					_set_error("Redefinition of '" + String(name) + "'");
 					return ERR_PARSE_ERROR;
 				}
@@ -6589,7 +6627,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 										} else {
 											_set_tkpos(pos2);
 
-											Node *n = _parse_and_reduce_expression(NULL, Map<StringName, BuiltInInfo>());
+											Node *n = _parse_and_reduce_expression(NULL, FunctionInfo());
 											if (!n || n->type != Node::TYPE_CONSTANT || n->get_datatype() != TYPE_INT) {
 												_set_error("Expected single integer constant > 0");
 												return ERR_PARSE_ERROR;
@@ -6670,7 +6708,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 
 								if (tk.type == TK_PARENTHESIS_OPEN || curly) { // initialization
 									while (true) {
-										Node *n = _parse_and_reduce_expression(NULL, Map<StringName, BuiltInInfo>());
+										Node *n = _parse_and_reduce_expression(NULL, FunctionInfo());
 										if (!n) {
 											return ERR_PARSE_ERROR;
 										}
@@ -6725,7 +6763,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 								constant.initializer = static_cast<ConstantNode *>(expr);
 							} else {
 								//variable created with assignment! must parse an expression
-								Node *expr = _parse_and_reduce_expression(NULL, Map<StringName, BuiltInInfo>());
+								Node *expr = _parse_and_reduce_expression(NULL, FunctionInfo());
 								if (!expr)
 									return ERR_PARSE_ERROR;
 								if (expr->type == Node::TYPE_OPERATOR && ((OperatorNode *)expr)->op == OP_CALL) {
@@ -6762,7 +6800,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 							}
 
 							name = tk.text;
-							if (_find_identifier(nullptr, false, Map<StringName, BuiltInInfo>(), name)) {
+							if (_find_identifier(nullptr, false, FunctionInfo(), name)) {
 								_set_error("Redefinition of '" + String(name) + "'");
 								return ERR_PARSE_ERROR;
 							}
@@ -6785,14 +6823,14 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 					break;
 				}
 
-				Map<StringName, BuiltInInfo> builtin_types;
+				FunctionInfo builtins;
 				if (p_functions.has(name)) {
-					builtin_types = p_functions[name].built_ins;
+					builtins = p_functions[name];
 				}
 
 				if (p_functions.has("global")) { // Adds global variables: 'TIME'
 					for (Map<StringName, BuiltInInfo>::Element *E = p_functions["global"].built_ins.front(); E; E = E->next()) {
-						builtin_types.insert(E->key(), E->value());
+						builtins.built_ins.insert(E->key(), E->value());
 					}
 				}
 
@@ -6915,7 +6953,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 					pname = tk.text;
 
 					ShaderLanguage::IdentifierType itype;
-					if (_find_identifier(func_node->body, false, builtin_types, pname, (ShaderLanguage::DataType *)nullptr, &itype)) {
+					if (_find_identifier(func_node->body, false, builtins, pname, (ShaderLanguage::DataType *)nullptr, &itype)) {
 						if (itype != IDENTIFIER_FUNCTION) {
 							_set_error("Redefinition of '" + String(pname) + "'");
 							return ERR_PARSE_ERROR;
@@ -6977,7 +7015,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 
 				current_function = name;
 
-				Error err = _parse_block(func_node->body, builtin_types);
+				Error err = _parse_block(func_node->body, builtins);
 				if (err) {
 					return err;
 				}

+ 24 - 7
servers/rendering/shader_language.h

@@ -730,8 +730,25 @@ public:
 				constant(p_constant) {}
 	};
 
+	struct StageFunctionInfo {
+		struct Argument {
+			StringName name;
+			DataType type;
+
+			Argument(const StringName &p_name = StringName(), DataType p_type = TYPE_VOID) {
+				name = p_name;
+				type = p_type;
+			}
+		};
+
+		Vector<Argument> arguments;
+		DataType return_type = TYPE_VOID;
+	};
+
 	struct FunctionInfo {
 		Map<StringName, BuiltInInfo> built_ins;
+		Map<StringName, StageFunctionInfo> stage_functions;
+
 		bool can_discard;
 	};
 	static bool has_builtin(const Map<StringName, ShaderLanguage::FunctionInfo> &p_functions, const StringName &p_name);
@@ -802,9 +819,9 @@ private:
 		IDENTIFIER_CONSTANT,
 	};
 
-	bool _find_identifier(const BlockNode *p_block, bool p_allow_reassign, const Map<StringName, BuiltInInfo> &p_builtin_types, const StringName &p_identifier, DataType *r_data_type = nullptr, IdentifierType *r_type = nullptr, bool *r_is_const = nullptr, int *r_array_size = nullptr, StringName *r_struct_name = nullptr);
+	bool _find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type = nullptr, IdentifierType *r_type = nullptr, bool *r_is_const = nullptr, int *r_array_size = nullptr, StringName *r_struct_name = nullptr);
 	bool _is_operator_assign(Operator p_op) const;
-	bool _validate_assign(Node *p_node, const Map<StringName, BuiltInInfo> &p_builtin_types, String *r_message = nullptr);
+	bool _validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message = nullptr);
 	bool _validate_operator(OperatorNode *p_op, DataType *r_ret_type = nullptr);
 
 	struct BuiltinFuncDef {
@@ -837,16 +854,16 @@ private:
 	Error _validate_datatype(DataType p_type);
 	bool _compare_datatypes_in_nodes(Node *a, Node *b) const;
 
-	bool _validate_function_call(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, OperatorNode *p_func, DataType *r_ret_type, StringName *r_ret_type_str);
-	bool _parse_function_arguments(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, OperatorNode *p_func, int *r_complete_arg = nullptr);
+	bool _validate_function_call(BlockNode *p_block, const FunctionInfo &p_function_info, OperatorNode *p_func, DataType *r_ret_type, StringName *r_ret_type_str);
+	bool _parse_function_arguments(BlockNode *p_block, const FunctionInfo &p_function_info, OperatorNode *p_func, int *r_complete_arg = nullptr);
 	bool _propagate_function_call_sampler_uniform_settings(StringName p_name, int p_argument, TextureFilter p_filter, TextureRepeat p_repeat);
 	bool _propagate_function_call_sampler_builtin_reference(StringName p_name, int p_argument, const StringName &p_builtin);
 
-	Node *_parse_expression(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types);
+	Node *_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info);
 	ShaderLanguage::Node *_reduce_expression(BlockNode *p_block, ShaderLanguage::Node *p_node);
 
-	Node *_parse_and_reduce_expression(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types);
-	Error _parse_block(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, bool p_just_one = false, bool p_can_break = false, bool p_can_continue = false);
+	Node *_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info);
+	Error _parse_block(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_just_one = false, bool p_can_break = false, bool p_can_continue = false);
 	String _get_shader_type_list(const Set<String> &p_shader_types) const;
 	String _get_qualifier_str(ArgumentQualifier p_qualifier) const;
 

+ 21 - 0
servers/rendering/shader_types.cpp

@@ -283,8 +283,29 @@ ShaderTypes::ShaderTypes() {
 	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"].built_ins["FLAG_EMIT_POSITION"] = constt(ShaderLanguage::TYPE_UINT);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["FLAG_EMIT_ROT_SCALE"] = constt(ShaderLanguage::TYPE_UINT);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["FLAG_EMIT_VELOCITY"] = constt(ShaderLanguage::TYPE_UINT);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["FLAG_EMIT_COLOR"] = constt(ShaderLanguage::TYPE_UINT);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["FLAG_EMIT_CUSTOM"] = constt(ShaderLanguage::TYPE_UINT);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART_POSITION"] = constt(ShaderLanguage::TYPE_BOOL);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART_ROT_SCALE"] = constt(ShaderLanguage::TYPE_BOOL);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART_VELOCITY"] = constt(ShaderLanguage::TYPE_BOOL);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART_COLOR"] = constt(ShaderLanguage::TYPE_BOOL);
+	shader_modes[RS::SHADER_PARTICLES].functions["compute"].built_ins["RESTART_CUSTOM"] = constt(ShaderLanguage::TYPE_BOOL);
 	shader_modes[RS::SHADER_PARTICLES].functions["compute"].can_discard = false;
 
+	{
+		ShaderLanguage::StageFunctionInfo emit_vertex_func;
+		emit_vertex_func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("xform", ShaderLanguage::TYPE_MAT4));
+		emit_vertex_func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("velocity", ShaderLanguage::TYPE_VEC3));
+		emit_vertex_func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("color", ShaderLanguage::TYPE_VEC4));
+		emit_vertex_func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("custom", ShaderLanguage::TYPE_VEC4));
+		emit_vertex_func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("flags", ShaderLanguage::TYPE_UINT));
+		emit_vertex_func.return_type = ShaderLanguage::TYPE_BOOL; //whether it could emit
+		shader_modes[RS::SHADER_PARTICLES].functions["compute"].stage_functions["emit_particle"] = emit_vertex_func;
+	}
+
 	shader_modes[RS::SHADER_PARTICLES].modes.push_back("disable_force");
 	shader_modes[RS::SHADER_PARTICLES].modes.push_back("disable_velocity");
 	shader_modes[RS::SHADER_PARTICLES].modes.push_back("keep_data");

+ 13 - 1
servers/rendering_server.h

@@ -563,7 +563,7 @@ public:
 
 	virtual RID particles_create() = 0;
 
-	virtual void particles_set_emitting(RID p_particles, bool p_emitting) = 0;
+	virtual void particles_set_emitting(RID p_particles, bool p_enable) = 0;
 	virtual bool particles_get_emitting(RID p_particles) = 0;
 	virtual void particles_set_amount(RID p_particles, int p_amount) = 0;
 	virtual void particles_set_lifetime(RID p_particles, float p_lifetime) = 0;
@@ -581,6 +581,18 @@ public:
 	virtual void particles_request_process(RID p_particles) = 0;
 	virtual void particles_restart(RID p_particles) = 0;
 
+	virtual void particles_set_subemitter(RID p_particles, RID p_subemitter_particles) = 0;
+
+	enum ParticlesEmitFlags {
+		PARTICLES_EMIT_FLAG_POSITION = 1,
+		PARTICLES_EMIT_FLAG_ROTATION_SCALE = 2,
+		PARTICLES_EMIT_FLAG_VELOCITY = 4,
+		PARTICLES_EMIT_FLAG_COLOR = 8,
+		PARTICLES_EMIT_FLAG_CUSTOM = 16
+	};
+
+	virtual void particles_emit(RID p_particles, const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) = 0;
+
 	enum ParticlesDrawOrder {
 		PARTICLES_DRAW_ORDER_INDEX,
 		PARTICLES_DRAW_ORDER_LIFETIME,