Browse Source

Merge pull request #68426 from clayjohn/GLES3-particles

Add GPUParticles to the OpenGL3 renderer.
Rémi Verschelde 2 years ago
parent
commit
5f78f24b08
31 changed files with 2841 additions and 252 deletions
  1. 15 3
      drivers/gles3/effects/copy_effects.cpp
  2. 104 33
      drivers/gles3/rasterizer_canvas_gles3.cpp
  3. 2 2
      drivers/gles3/rasterizer_canvas_gles3.h
  4. 171 38
      drivers/gles3/rasterizer_scene_gles3.cpp
  5. 0 10
      drivers/gles3/rasterizer_scene_gles3.h
  6. 18 4
      drivers/gles3/shader_gles3.cpp
  7. 13 5
      drivers/gles3/shader_gles3.h
  8. 2 0
      drivers/gles3/shaders/SCsub
  9. 2 2
      drivers/gles3/shaders/cubemap_filter.glsl
  10. 501 0
      drivers/gles3/shaders/particles.glsl
  11. 122 0
      drivers/gles3/shaders/particles_copy.glsl
  12. 8 6
      drivers/gles3/shaders/scene.glsl
  13. 241 28
      drivers/gles3/storage/material_storage.cpp
  14. 62 0
      drivers/gles3/storage/material_storage.h
  15. 4 1
      drivers/gles3/storage/mesh_storage.cpp
  16. 3 0
      drivers/gles3/storage/mesh_storage.h
  17. 1171 40
      drivers/gles3/storage/particles_storage.cpp
  18. 313 2
      drivers/gles3/storage/particles_storage.h
  19. 16 4
      drivers/gles3/storage/texture_storage.cpp
  20. 18 43
      drivers/gles3/storage/utilities.cpp
  21. 44 5
      gles3_builders.py
  22. 0 4
      scene/2d/gpu_particles_2d.cpp
  23. 1 1
      scene/2d/gpu_particles_2d.h
  24. 0 4
      scene/3d/gpu_particles_3d.cpp
  25. 1 1
      scene/3d/gpu_particles_3d.h
  26. 0 3
      servers/rendering/dummy/storage/particles_storage.h
  27. 1 0
      servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
  28. 4 8
      servers/rendering/renderer_rd/storage_rd/particles_storage.h
  29. 0 3
      servers/rendering/storage/particles_storage.h
  30. 3 2
      tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl
  31. 1 0
      tests/python_build/fixtures/gles3/vertex_fragment_expected_parts.json

+ 15 - 3
drivers/gles3/effects/copy_effects.cpp

@@ -115,13 +115,21 @@ CopyEffects::~CopyEffects() {
 }
 
 void CopyEffects::copy_to_rect(const Rect2 &p_rect) {
-	copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION);
+	bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION);
+	if (!success) {
+		return;
+	}
+
 	copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION);
 	draw_screen_quad();
 }
 
 void CopyEffects::copy_screen() {
-	copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_DEFAULT);
+	bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_DEFAULT);
+	if (!success) {
+		return;
+	}
+
 	draw_screen_triangle();
 }
 
@@ -151,7 +159,11 @@ void CopyEffects::bilinear_blur(GLuint p_source_texture, int p_mipmap_count, con
 }
 
 void CopyEffects::set_color(const Color &p_color, const Rect2i &p_region) {
-	copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR);
+	bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR);
+	if (!success) {
+		return;
+	}
+
 	copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_region.position.x, p_region.position.y, p_region.size.x, p_region.size.y, copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR);
 	copy.shader.version_set_uniform(CopyShaderGLES3::COLOR_IN, p_color, copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR);
 	draw_screen_quad();

+ 104 - 33
drivers/gles3/rasterizer_canvas_gles3.cpp

@@ -41,6 +41,7 @@
 #include "storage/config.h"
 #include "storage/material_storage.h"
 #include "storage/mesh_storage.h"
+#include "storage/particles_storage.h"
 #include "storage/texture_storage.h"
 
 void RasterizerCanvasGLES3::_update_transform_2d_to_mat4(const Transform2D &p_transform, float *p_mat4) {
@@ -578,7 +579,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
 
 		GLES3::CanvasShaderData::BlendMode blend_mode = shader_data_cache ? shader_data_cache->blend_mode : GLES3::CanvasShaderData::BLEND_MODE_MIX;
 
-		_record_item_commands(ci, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken);
+		_record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken);
 	}
 
 	if (index == 0) {
@@ -623,7 +624,10 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
 		uint64_t specialization = 0;
 		specialization |= uint64_t(state.canvas_instance_batches[i].lights_disabled);
 		specialization |= uint64_t(!GLES3::Config::get_singleton()->float_texture_supported) << 1;
-		_bind_material(material_data, variant, specialization);
+		bool success = _bind_material(material_data, variant, specialization);
+		if (!success) {
+			continue;
+		}
 
 		GLES3::CanvasShaderData::BlendMode blend_mode = state.canvas_instance_batches[i].blend_mode;
 
@@ -707,7 +711,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
 	r_last_index += index;
 }
 
-void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken) {
+void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken) {
 	RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? state.default_filter : p_item->texture_filter;
 
 	if (texture_filter != state.canvas_instance_batches[state.current_batch_index].filter) {
@@ -1064,15 +1068,45 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, const Tran
 					state.canvas_instance_batches[state.current_batch_index].tex = m->texture;
 					_update_transform_2d_to_mat2x3(base_transform * draw_transform * m->transform, state.instance_data_array[r_index].world);
 					modulate = m->modulate;
+
 				} else if (c->type == Item::Command::TYPE_MULTIMESH) {
 					const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(c);
 					state.canvas_instance_batches[state.current_batch_index].tex = mm->texture;
-					uint32_t instance_count = GLES3::MeshStorage::get_singleton()->multimesh_get_instances_to_draw(mm->multimesh);
-					if (instance_count > 1) {
-						state.canvas_instance_batches[state.current_batch_index].shader_variant = CanvasShaderGLES3::MODE_INSTANCED;
-					}
+					state.canvas_instance_batches[state.current_batch_index].shader_variant = CanvasShaderGLES3::MODE_INSTANCED;
+
 				} else if (c->type == Item::Command::TYPE_PARTICLES) {
-					WARN_PRINT_ONCE("Particles not supported yet, sorry :(");
+					GLES3::ParticlesStorage *particles_storage = GLES3::ParticlesStorage::get_singleton();
+					GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
+
+					const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(c);
+					RID particles = pt->particles;
+					state.canvas_instance_batches[state.current_batch_index].tex = pt->texture;
+					state.canvas_instance_batches[state.current_batch_index].shader_variant = CanvasShaderGLES3::MODE_INSTANCED;
+					bool local_coords = particles_storage->particles_is_using_local_coords(particles);
+
+					if (particles_storage->particles_has_collision(particles) && texture_storage->render_target_is_sdf_enabled(p_render_target)) {
+						// Pass collision information.
+						Transform2D xform;
+						if (local_coords) {
+							xform = p_item->final_transform;
+						} else {
+							xform = p_canvas_transform_inverse;
+						}
+
+						GLuint sdf_texture = texture_storage->render_target_get_sdf_texture(p_render_target);
+
+						Rect2 to_screen;
+						{
+							Rect2 sdf_rect = texture_storage->render_target_get_sdf_rect(p_render_target);
+
+							to_screen.size = Vector2(1.0 / sdf_rect.size.width, 1.0 / sdf_rect.size.height);
+							to_screen.position = -sdf_rect.position * to_screen.size;
+						}
+
+						particles_storage->particles_set_canvas_sdf_collision(pt->particles, true, xform, to_screen, sdf_texture);
+					} else {
+						particles_storage->particles_set_canvas_sdf_collision(pt->particles, false, Transform2D(), Rect2(), 0);
+					}
 				}
 
 				state.canvas_instance_batches[state.current_batch_index].command = c;
@@ -1209,20 +1243,21 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
 		case Item::Command::TYPE_MULTIMESH:
 		case Item::Command::TYPE_PARTICLES: {
 			GLES3::MeshStorage *mesh_storage = GLES3::MeshStorage::get_singleton();
+			GLES3::ParticlesStorage *particles_storage = GLES3::ParticlesStorage::get_singleton();
 			RID mesh;
 			RID mesh_instance;
-			RID texture;
 			uint32_t instance_count = 1;
-			GLuint multimesh_buffer = 0;
-			uint32_t multimesh_stride = 0;
-			uint32_t multimesh_color_offset = 0;
-			bool multimesh_uses_color = false;
-			bool multimesh_uses_custom_data = false;
+			GLuint instance_buffer = 0;
+			uint32_t instance_stride = 0;
+			uint32_t instance_color_offset = 0;
+			bool instance_uses_color = false;
+			bool instance_uses_custom_data = false;
 
 			if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_MESH) {
 				const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(state.canvas_instance_batches[p_index].command);
 				mesh = m->mesh;
 				mesh_instance = m->mesh_instance;
+
 			} else if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_MULTIMESH) {
 				const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(state.canvas_instance_batches[p_index].command);
 				RID multimesh = mm->multimesh;
@@ -1238,13 +1273,41 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
 					break;
 				}
 
-				multimesh_buffer = mesh_storage->multimesh_get_gl_buffer(multimesh);
-				multimesh_stride = mesh_storage->multimesh_get_stride(multimesh);
-				multimesh_color_offset = mesh_storage->multimesh_get_color_offset(multimesh);
-				multimesh_uses_color = mesh_storage->multimesh_uses_colors(multimesh);
-				multimesh_uses_custom_data = mesh_storage->multimesh_uses_custom_data(multimesh);
+				instance_buffer = mesh_storage->multimesh_get_gl_buffer(multimesh);
+				instance_stride = mesh_storage->multimesh_get_stride(multimesh);
+				instance_color_offset = mesh_storage->multimesh_get_color_offset(multimesh);
+				instance_uses_color = mesh_storage->multimesh_uses_colors(multimesh);
+				instance_uses_custom_data = mesh_storage->multimesh_uses_custom_data(multimesh);
+
 			} else if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_PARTICLES) {
-				// Do nothing for now.
+				const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(state.canvas_instance_batches[p_index].command);
+				RID particles = pt->particles;
+				mesh = particles_storage->particles_get_draw_pass_mesh(particles, 0);
+
+				ERR_BREAK(particles_storage->particles_get_mode(particles) != RS::PARTICLES_MODE_2D);
+				particles_storage->particles_request_process(particles);
+
+				if (particles_storage->particles_is_inactive(particles)) {
+					break;
+				}
+
+				RenderingServerDefault::redraw_request(); // Active particles means redraw request.
+
+				int dpc = particles_storage->particles_get_draw_passes(particles);
+				if (dpc == 0) {
+					break; // Nothing to draw.
+				}
+
+				instance_count = particles_storage->particles_get_amount(particles);
+				instance_buffer = particles_storage->particles_get_gl_buffer(particles);
+				instance_stride = 12; // 8 bytes for instance transform and 4 bytes for packed color and custom.
+				instance_color_offset = 8; // 8 bytes for instance transform.
+				instance_uses_color = true;
+				instance_uses_custom_data = true;
+			}
+
+			if (instance_buffer == 0) {
+				break;
 			}
 
 			ERR_FAIL_COND(mesh.is_null());
@@ -1277,17 +1340,17 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
 
 				if (instance_count > 1) {
 					// Bind instance buffers.
-					glBindBuffer(GL_ARRAY_BUFFER, multimesh_buffer);
+					glBindBuffer(GL_ARRAY_BUFFER, instance_buffer);
 					glEnableVertexAttribArray(1);
-					glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, multimesh_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(0));
+					glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, instance_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(0));
 					glVertexAttribDivisor(1, 1);
 					glEnableVertexAttribArray(2);
-					glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, multimesh_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(4 * 4));
+					glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, instance_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(4 * 4));
 					glVertexAttribDivisor(2, 1);
 
-					if (multimesh_uses_color || multimesh_uses_custom_data) {
+					if (instance_uses_color || instance_uses_custom_data) {
 						glEnableVertexAttribArray(5);
-						glVertexAttribIPointer(5, 4, GL_UNSIGNED_INT, multimesh_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(multimesh_color_offset * sizeof(float)));
+						glVertexAttribIPointer(5, 4, GL_UNSIGNED_INT, instance_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(instance_color_offset * sizeof(float)));
 						glVertexAttribDivisor(5, 1);
 					}
 				}
@@ -1361,17 +1424,17 @@ void RasterizerCanvasGLES3::_new_batch(bool &r_batch_broken, uint32_t &r_index)
 	_align_instance_data_buffer(r_index);
 }
 
-void RasterizerCanvasGLES3::_bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization) {
+bool RasterizerCanvasGLES3::_bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization) {
 	if (p_material_data) {
 		if (p_material_data->shader_data->version.is_valid() && p_material_data->shader_data->valid) {
 			// Bind uniform buffer and textures
 			p_material_data->bind_uniforms();
-			GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(p_material_data->shader_data->version, p_variant, p_specialization);
+			return GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(p_material_data->shader_data->version, p_variant, p_specialization);
 		} else {
-			GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, p_variant, p_specialization);
+			return GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, p_variant, p_specialization);
 		}
 	} else {
-		GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, p_variant, p_specialization);
+		return GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, p_variant, p_specialization);
 	}
 }
 
@@ -1435,7 +1498,10 @@ void RasterizerCanvasGLES3::light_update_shadow(RID p_rid, int p_shadow_index, c
 	RS::CanvasOccluderPolygonCullMode cull_mode = RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED;
 
 	CanvasOcclusionShaderGLES3::ShaderVariant variant = config->float_texture_supported ? CanvasOcclusionShaderGLES3::MODE_SHADOW : CanvasOcclusionShaderGLES3::MODE_SHADOW_RGBA;
-	shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant);
+	bool success = shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant);
+	if (!success) {
+		return;
+	}
 
 	for (int i = 0; i < 4; i++) {
 		glViewport((state.shadow_texture_size / 4) * i, p_shadow_index * 2, (state.shadow_texture_size / 4), 2);
@@ -1553,7 +1619,10 @@ void RasterizerCanvasGLES3::light_update_directional_shadow(RID p_rid, int p_sha
 	RS::CanvasOccluderPolygonCullMode cull_mode = RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED;
 
 	CanvasOcclusionShaderGLES3::ShaderVariant variant = config->float_texture_supported ? CanvasOcclusionShaderGLES3::MODE_SHADOW : CanvasOcclusionShaderGLES3::MODE_SHADOW_RGBA;
-	shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant);
+	bool success = shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant);
+	if (!success) {
+		return;
+	}
 
 	Projection projection;
 	projection.set_orthogonal(-half_size, half_size, -0.5, 0.5, 0.0, distance);
@@ -1685,7 +1754,10 @@ void RasterizerCanvasGLES3::render_sdf(RID p_render_target, LightOccluderInstanc
 	glClear(GL_COLOR_BUFFER_BIT);
 
 	CanvasOcclusionShaderGLES3::ShaderVariant variant = CanvasOcclusionShaderGLES3::MODE_SDF;
-	shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant);
+	bool success = shadow_render.shader.version_bind_shader(shadow_render.shader_version, variant);
+	if (!success) {
+		return;
+	}
 
 	shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::PROJECTION, Projection(), shadow_render.shader_version, variant);
 	shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::DIRECTION, 0.0, 0.0, shadow_render.shader_version, variant);
@@ -2555,7 +2627,6 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() {
 
 	GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.initialize(global_defines);
 	data.canvas_shader_default_version = GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_create();
-	GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, CanvasShaderGLES3::MODE_QUAD);
 
 	shadow_render.shader.initialize();
 	shadow_render.shader_version = shadow_render.shader.version_create();

+ 2 - 2
drivers/gles3/rasterizer_canvas_gles3.h

@@ -352,9 +352,9 @@ public:
 
 	void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) override;
 	void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, uint32_t &r_last_index, bool p_to_backbuffer = false);
-	void _record_item_commands(const Item *p_item, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch);
+	void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch);
 	void _render_batch(Light *p_lights, uint32_t p_index);
-	void _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization);
+	bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization);
 	void _new_batch(bool &r_batch_broken, uint32_t &r_index);
 	void _add_to_batch(uint32_t &r_index, bool &r_batch_broken);
 	void _allocate_instance_data_buffer();

+ 171 - 38
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -35,6 +35,7 @@
 #include "servers/rendering/rendering_server_globals.h"
 #include "storage/config.h"
 #include "storage/mesh_storage.h"
+#include "storage/particles_storage.h"
 #include "storage/texture_storage.h"
 
 #ifdef GLES3_ENABLED
@@ -50,6 +51,9 @@ RenderGeometryInstance *RasterizerSceneGLES3::geometry_instance_create(RID p_bas
 
 	ginstance->data->base = p_base;
 	ginstance->data->base_type = type;
+	ginstance->data->dependency_tracker.userdata = ginstance;
+	ginstance->data->dependency_tracker.changed_callback = _geometry_instance_dependency_changed;
+	ginstance->data->dependency_tracker.deleted_callback = _geometry_instance_dependency_deleted;
 
 	ginstance->_mark_dirty();
 
@@ -314,6 +318,8 @@ void RasterizerSceneGLES3::_geometry_instance_add_surface(GeometryInstanceGLES3
 
 void RasterizerSceneGLES3::_geometry_instance_update(RenderGeometryInstance *p_geometry_instance) {
 	GLES3::MeshStorage *mesh_storage = GLES3::MeshStorage::get_singleton();
+	GLES3::ParticlesStorage *particles_storage = GLES3::ParticlesStorage::get_singleton();
+
 	GeometryInstanceGLES3 *ginstance = static_cast<GeometryInstanceGLES3 *>(p_geometry_instance);
 
 	if (ginstance->data->dirty_dependencies) {
@@ -361,6 +367,26 @@ void RasterizerSceneGLES3::_geometry_instance_update(RenderGeometryInstance *p_g
 
 		} break;
 		case RS::INSTANCE_PARTICLES: {
+			int draw_passes = particles_storage->particles_get_draw_passes(ginstance->data->base);
+
+			for (int j = 0; j < draw_passes; j++) {
+				RID mesh = particles_storage->particles_get_draw_pass_mesh(ginstance->data->base, j);
+				if (!mesh.is_valid()) {
+					continue;
+				}
+
+				const RID *materials = nullptr;
+				uint32_t surface_count;
+
+				materials = mesh_storage->mesh_get_surface_count_and_materials(mesh, surface_count);
+				if (materials) {
+					for (uint32_t k = 0; k < surface_count; k++) {
+						_geometry_instance_add_surface(ginstance, k, materials[k], mesh);
+					}
+				}
+			}
+
+			ginstance->instance_count = particles_storage->particles_get_amount(ginstance->data->base);
 		} break;
 
 		default: {
@@ -382,9 +408,17 @@ void RasterizerSceneGLES3::_geometry_instance_update(RenderGeometryInstance *p_g
 			ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA;
 		}
 
-		//ginstance->transforms_uniform_set = mesh_storage->multimesh_get_3d_uniform_set(ginstance->data->base, scene_globals.default_shader_rd, TRANSFORMS_UNIFORM_SET);
-
 	} else if (ginstance->data->base_type == RS::INSTANCE_PARTICLES) {
+		ginstance->base_flags |= INSTANCE_DATA_FLAG_PARTICLES;
+		ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH;
+
+		ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR;
+		ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA;
+
+		if (!particles_storage->particles_is_using_local_coords(ginstance->data->base)) {
+			store_transform = false;
+		}
+
 	} else if (ginstance->data->base_type == RS::INSTANCE_MESH) {
 	}
 
@@ -751,12 +785,16 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection,
 	sky_transform.invert();
 	sky_transform = p_transform.basis * sky_transform;
 
-	GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_bind_shader(shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
-	GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::ORIENTATION, sky_transform, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
-	GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, camera.columns[2][0], camera.columns[0][0], camera.columns[2][1], camera.columns[1][1], shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
-	GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::POSITION, p_transform.origin, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
-	GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::TIME, time, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
-	GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
+	bool success = material_storage->shaders.sky_shader.version_bind_shader(shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
+	if (!success) {
+		return;
+	}
+
+	material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::ORIENTATION, sky_transform, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
+	material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, camera.columns[2][0], camera.columns[0][0], camera.columns[2][1], camera.columns[1][1], shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
+	material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::POSITION, p_transform.origin, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
+	material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::TIME, time, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
+	material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND);
 
 	glBindVertexArray(sky_globals.screen_triangle_array);
 	glDrawArrays(GL_TRIANGLES, 0, 3);
@@ -850,12 +888,15 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p
 		correction.columns[1][1] = -1.0;
 		cm = correction * cm;
 
-		GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_bind_shader(shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
+		bool success = material_storage->shaders.sky_shader.version_bind_shader(shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
+		if (!success) {
+			return;
+		}
 
-		GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::POSITION, p_transform.origin, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
-		GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::TIME, time, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
-		GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, cm.columns[2][0], cm.columns[0][0], cm.columns[2][1], cm.columns[1][1], shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
-		GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
+		material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::POSITION, p_transform.origin, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
+		material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::TIME, time, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
+		material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, cm.columns[2][0], cm.columns[0][0], cm.columns[2][1], cm.columns[1][1], shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
+		material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
 
 		glBindVertexArray(sky_globals.screen_triangle_array);
 
@@ -864,7 +905,7 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p
 
 		for (int i = 0; i < 6; i++) {
 			Basis local_view = Basis::looking_at(view_normals[i], view_up[i]);
-			GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::ORIENTATION, local_view, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
+			material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::ORIENTATION, local_view, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP);
 			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, sky->raw_radiance, 0);
 			glDrawArrays(GL_TRIANGLES, 0, 3);
 		}
@@ -945,7 +986,10 @@ void RasterizerSceneGLES3::_filter_sky_radiance(Sky *p_sky, int p_base_layer) {
 	glViewport(0, 0, size, size);
 	glBindVertexArray(sky_globals.screen_triangle_array);
 
-	material_storage->shaders.cubemap_filter_shader.version_bind_shader(scene_globals.cubemap_filter_shader_version, mode);
+	bool success = material_storage->shaders.cubemap_filter_shader.version_bind_shader(scene_globals.cubemap_filter_shader_version, mode);
+	if (!success) {
+		return;
+	}
 
 	if (p_base_layer > 0) {
 		const uint32_t sample_counts[4] = { 1, sky_globals.ggx_samples / 4, sky_globals.ggx_samples / 2, sky_globals.ggx_samples };
@@ -1851,8 +1895,11 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 		glColorMask(0, 0, 0, 0);
 		glClearDepth(1.0f);
 		glClear(GL_DEPTH_BUFFER_BIT);
+		uint32_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL |
+				SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI |
+				SceneShaderGLES3::DISABLE_LIGHT_SPOT;
 
-		RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, 0, use_wireframe);
+		RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, spec_constant, use_wireframe);
 		_render_list_template<PASS_MODE_DEPTH>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_OPAQUE].elements.size());
 
 		glColorMask(1, 1, 1, 1);
@@ -1894,11 +1941,11 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 	{
 		// Specialization Constants that apply for entire rendering pass.
 		if (render_data.directional_light_count == 0) {
-			spec_constant_base_flags |= 1 << SPEC_CONSTANT_DISABLE_DIRECTIONAL_LIGHTS;
+			spec_constant_base_flags |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL;
 		}
 
 		if (render_data.environment.is_null() || (render_data.environment.is_valid() && !environment_get_fog_enabled(render_data.environment))) {
-			spec_constant_base_flags |= 1 << SPEC_CONSTANT_DISABLE_FOG;
+			spec_constant_base_flags |= SceneShaderGLES3::DISABLE_FOG;
 		}
 	}
 	// Render Opaque Objects.
@@ -1947,6 +1994,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 template <PassMode p_pass_mode>
 void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, const RenderDataGLES3 *p_render_data, uint32_t p_from_element, uint32_t p_to_element, bool p_alpha_pass) {
 	GLES3::MeshStorage *mesh_storage = GLES3::MeshStorage::get_singleton();
+	GLES3::ParticlesStorage *particles_storage = GLES3::ParticlesStorage::get_singleton();
 	GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
 	GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
 	GLES3::Config *config = GLES3::Config::get_singleton();
@@ -1960,11 +2008,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 	SceneShaderGLES3::ShaderVariant prev_variant = SceneShaderGLES3::ShaderVariant::MODE_COLOR;
 	SceneShaderGLES3::ShaderVariant shader_variant = SceneShaderGLES3::MODE_COLOR; // Assigned to silence wrong -Wmaybe-initialized
 
-	// @todo Get this from p_params->spec_constant_base_flags instead of hardcoding it.
-	uint32_t base_spec_constants = 0;
+	uint32_t base_spec_constants = p_params->spec_constant_base_flags;
 
 	if (p_render_data->view_count > 1) {
-		base_spec_constants |= 1 << SPEC_CONSTANT_USE_MULTIVIEW;
+		base_spec_constants |= SceneShaderGLES3::USE_MULTIVIEW;
 	}
 
 	switch (p_pass_mode) {
@@ -1987,7 +2034,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 			Sky *sky = sky_owner.get_or_null(environment_get_sky(p_render_data->environment));
 			if (sky && sky->radiance != 0) {
 				texture_to_bind = sky->radiance;
-				// base_spec_constant |= USE_RADIANCE_MAP;
+				base_spec_constants |= SceneShaderGLES3::USE_RADIANCE_MAP;
 			}
 			glBindTexture(GL_TEXTURE_CUBE_MAP, texture_to_bind);
 		}
@@ -2157,7 +2204,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 		}
 
 		bool use_index_buffer = index_array_gl != 0;
-		if (prev_index_array_gl != index_array_gl) {
+		if (prev_index_array_gl != index_array_gl || prev_vertex_array_gl != vertex_array_gl) {
 			if (index_array_gl != 0) {
 				// Bind index each time so we can use LODs
 				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_array_gl);
@@ -2177,11 +2224,16 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 
 		SceneShaderGLES3::ShaderVariant instance_variant = shader_variant;
 		if (inst->instance_count > 0) {
+			// Will need to use instancing to draw (either MultiMesh or Particles).
 			instance_variant = SceneShaderGLES3::ShaderVariant(1 + int(shader_variant));
 		}
 
 		if (prev_shader != shader || prev_variant != instance_variant) {
-			material_storage->shaders.scene_shader.version_bind_shader(shader->version, instance_variant, base_spec_constants);
+			bool success = material_storage->shaders.scene_shader.version_bind_shader(shader->version, instance_variant, base_spec_constants);
+			if (!success) {
+				continue;
+			}
+
 			float opaque_prepass_threshold = 0.0;
 			if constexpr (p_pass_mode == PASS_MODE_DEPTH) {
 				opaque_prepass_threshold = 0.99;
@@ -2213,25 +2265,42 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 
 		material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, world_transform, shader->version, instance_variant, base_spec_constants);
 		if (inst->instance_count > 0) {
-			// Using MultiMesh.
+			// Using MultiMesh or Particles.
 			// Bind instance buffers.
 
-			GLuint multimesh_buffer = mesh_storage->multimesh_get_gl_buffer(inst->data->base);
-			glBindBuffer(GL_ARRAY_BUFFER, multimesh_buffer);
-			uint32_t multimesh_stride = mesh_storage->multimesh_get_stride(inst->data->base);
+			GLuint instance_buffer = 0;
+			uint32_t stride = 0;
+			if (inst->flags_cache & INSTANCE_DATA_FLAG_PARTICLES) {
+				instance_buffer = particles_storage->particles_get_gl_buffer(inst->data->base);
+				stride = 16; // 12 bytes for instance transform and 4 bytes for packed color and custom.
+			} else {
+				instance_buffer = mesh_storage->multimesh_get_gl_buffer(inst->data->base);
+				stride = mesh_storage->multimesh_get_stride(inst->data->base);
+			}
+
+			if (instance_buffer == 0) {
+				// Instance buffer not initialized yet. Skip rendering for now.
+				continue;
+			}
+
+			glBindBuffer(GL_ARRAY_BUFFER, instance_buffer);
+
 			glEnableVertexAttribArray(12);
-			glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, multimesh_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(0));
+			glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(0));
 			glVertexAttribDivisor(12, 1);
 			glEnableVertexAttribArray(13);
-			glVertexAttribPointer(13, 4, GL_FLOAT, GL_FALSE, multimesh_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(4 * 4));
+			glVertexAttribPointer(13, 4, GL_FLOAT, GL_FALSE, stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4));
 			glVertexAttribDivisor(13, 1);
-			glEnableVertexAttribArray(14);
-			glVertexAttribPointer(14, 4, GL_FLOAT, GL_FALSE, multimesh_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(4 * 8));
-			glVertexAttribDivisor(14, 1);
+			if (!(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D)) {
+				glEnableVertexAttribArray(14);
+				glVertexAttribPointer(14, 4, GL_FLOAT, GL_FALSE, stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(sizeof(float) * 8));
+				glVertexAttribDivisor(14, 1);
+			}
 
-			if (mesh_storage->multimesh_uses_colors(inst->data->base) || mesh_storage->multimesh_uses_custom_data(inst->data->base)) {
+			if ((inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR) || (inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA)) {
+				uint32_t color_custom_offset = inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D ? 8 : 12;
 				glEnableVertexAttribArray(15);
-				glVertexAttribIPointer(15, 4, GL_UNSIGNED_INT, multimesh_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(mesh_storage->multimesh_get_color_offset(inst->data->base) * sizeof(float)));
+				glVertexAttribIPointer(15, 4, GL_UNSIGNED_INT, stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(color_custom_offset * sizeof(float)));
 				glVertexAttribDivisor(15, 1);
 			}
 			if (use_index_buffer) {
@@ -2265,6 +2334,72 @@ void RasterizerSceneGLES3::render_material(const Transform3D &p_cam_transform, c
 }
 
 void RasterizerSceneGLES3::render_particle_collider_heightfield(RID p_collider, const Transform3D &p_transform, const PagedArray<RenderGeometryInstance *> &p_instances) {
+	GLES3::ParticlesStorage *particles_storage = GLES3::ParticlesStorage::get_singleton();
+
+	ERR_FAIL_COND(!particles_storage->particles_collision_is_heightfield(p_collider));
+	Vector3 extents = particles_storage->particles_collision_get_extents(p_collider) * p_transform.basis.get_scale();
+	Projection cm;
+	cm.set_orthogonal(-extents.x, extents.x, -extents.z, extents.z, 0, extents.y * 2.0);
+
+	Vector3 cam_pos = p_transform.origin;
+	cam_pos.y += extents.y;
+
+	Transform3D cam_xform;
+	cam_xform.set_look_at(cam_pos, cam_pos - p_transform.basis.get_column(Vector3::AXIS_Y), -p_transform.basis.get_column(Vector3::AXIS_Z).normalized());
+
+	GLuint fb = particles_storage->particles_collision_get_heightfield_framebuffer(p_collider);
+	Size2i fb_size = particles_storage->particles_collision_get_heightfield_size(p_collider);
+
+	RENDER_TIMESTAMP("Setup GPUParticlesCollisionHeightField3D");
+
+	RenderDataGLES3 render_data;
+
+	render_data.cam_projection = cm;
+	render_data.cam_transform = cam_xform;
+	render_data.view_projection[0] = cm;
+	render_data.inv_cam_transform = render_data.cam_transform.affine_inverse();
+	render_data.cam_orthogonal = true;
+	render_data.z_near = 0.0;
+	render_data.z_far = cm.get_z_far();
+
+	render_data.instances = &p_instances;
+
+	_setup_environment(&render_data, true, Vector2(fb_size), true, Color(), false);
+
+	PassMode pass_mode = PASS_MODE_SHADOW;
+
+	_fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
+	render_list[RENDER_LIST_SECONDARY].sort_by_key();
+
+	RENDER_TIMESTAMP("Render Collider Heightfield");
+
+	glBindFramebuffer(GL_FRAMEBUFFER, fb);
+	glViewport(0, 0, fb_size.width, fb_size.height);
+
+	GLuint global_buffer = GLES3::MaterialStorage::get_singleton()->global_shader_parameters_get_uniform_buffer();
+
+	glBindBufferBase(GL_UNIFORM_BUFFER, SCENE_GLOBALS_UNIFORM_LOCATION, global_buffer);
+	glBindBuffer(GL_UNIFORM_BUFFER, 0);
+
+	glDisable(GL_BLEND);
+	glDepthMask(GL_TRUE);
+	glEnable(GL_DEPTH_TEST);
+	glDepthFunc(GL_LESS);
+	glDisable(GL_SCISSOR_TEST);
+	glCullFace(GL_BACK);
+	glEnable(GL_CULL_FACE);
+	scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK;
+
+	glColorMask(0, 0, 0, 0);
+	glClearDepth(1.0f);
+	glClear(GL_DEPTH_BUFFER_BIT);
+
+	RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), false, 31, false);
+
+	_render_list_template<PASS_MODE_SHADOW>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_SECONDARY].elements.size());
+
+	glColorMask(1, 1, 1, 1);
+	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 }
 
 void RasterizerSceneGLES3::set_time(double p_time, double p_step) {
@@ -2415,7 +2550,7 @@ RasterizerSceneGLES3::RasterizerSceneGLES3() {
 		global_defines += "#define MAX_GLOBAL_SHADER_UNIFORMS 256\n"; // TODO: this is arbitrary for now
 		global_defines += "\n#define MAX_LIGHT_DATA_STRUCTS " + itos(config->max_renderable_lights) + "\n";
 		global_defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n";
-		global_defines += "\n#define MAX_FORWARD_LIGHTS " + itos(config->max_lights_per_object) + "\n";
+		global_defines += "\n#define MAX_FORWARD_LIGHTS uint(" + itos(config->max_lights_per_object) + ")\n";
 		material_storage->shaders.scene_shader.initialize(global_defines);
 		scene_globals.shader_default_version = material_storage->shaders.scene_shader.version_create();
 		material_storage->shaders.scene_shader.version_bind_shader(scene_globals.shader_default_version, SceneShaderGLES3::MODE_COLOR);
@@ -2455,7 +2590,6 @@ void fragment() {
 		global_defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(sky_globals.max_directional_lights) + "\n";
 		material_storage->shaders.sky_shader.initialize(global_defines);
 		sky_globals.shader_default_version = material_storage->shaders.sky_shader.version_create();
-		material_storage->shaders.sky_shader.version_bind_shader(sky_globals.shader_default_version, SkyShaderGLES3::MODE_BACKGROUND);
 	}
 
 	{
@@ -2463,7 +2597,6 @@ void fragment() {
 		global_defines += "\n#define MAX_SAMPLE_COUNT " + itos(sky_globals.ggx_samples) + "\n";
 		material_storage->shaders.cubemap_filter_shader.initialize(global_defines);
 		scene_globals.cubemap_filter_shader_version = material_storage->shaders.cubemap_filter_shader.version_create();
-		material_storage->shaders.cubemap_filter_shader.version_bind_shader(scene_globals.cubemap_filter_shader_version, CubemapFilterShaderGLES3::MODE_DEFAULT);
 	}
 
 	{

+ 0 - 10
drivers/gles3/rasterizer_scene_gles3.h

@@ -85,16 +85,6 @@ enum SkyUniformLocation {
 	SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION,
 };
 
-enum {
-	SPEC_CONSTANT_DISABLE_LIGHTMAP = 0,
-	SPEC_CONSTANT_DISABLE_DIRECTIONAL_LIGHTS = 1,
-	SPEC_CONSTANT_DISABLE_OMNI_LIGHTS = 2,
-	SPEC_CONSTANT_DISABLE_SPOT_LIGHTS = 3,
-	SPEC_CONSTANT_DISABLE_FOG = 4,
-	SPEC_CONSTANT_USE_RADIANCE_MAP = 5,
-	SPEC_CONSTANT_USE_MULTIVIEW = 6,
-};
-
 struct RenderDataGLES3 {
 	Ref<RenderSceneBuffersGLES3> render_buffers;
 	bool transparent_bg = false;

+ 18 - 4
drivers/gles3/shader_gles3.cpp

@@ -92,7 +92,7 @@ void ShaderGLES3::_add_stage(const char *p_code, StageType p_stage_type) {
 	}
 }
 
-void ShaderGLES3::_setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_name, int p_uniform_count, const char **p_uniform_names, int p_ubo_count, const UBOPair *p_ubos, int p_texture_count, const TexUnitPair *p_tex_units, int p_specialization_count, const Specialization *p_specializations, int p_variant_count, const char **p_variants) {
+void ShaderGLES3::_setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_name, int p_uniform_count, const char **p_uniform_names, int p_ubo_count, const UBOPair *p_ubos, int p_feedback_count, const Feedback *p_feedback, int p_texture_count, const TexUnitPair *p_tex_units, int p_specialization_count, const Specialization *p_specializations, int p_variant_count, const char **p_variants) {
 	name = p_name;
 
 	if (p_vertex_code) {
@@ -118,6 +118,8 @@ void ShaderGLES3::_setup(const char *p_vertex_code, const char *p_fragment_code,
 	}
 	variant_defines = p_variants;
 	variant_count = p_variant_count;
+	feedbacks = p_feedback;
+	feedback_count = p_feedback_count;
 
 	StringBuilder tohash;
 	/*
@@ -339,9 +341,21 @@ void ShaderGLES3::_compile_specialization(Version::Specialization &spec, uint32_
 	glAttachShader(spec.id, spec.frag_id);
 	glAttachShader(spec.id, spec.vert_id);
 
-	//for (int i = 0; i < attribute_pair_count; i++) {
-	//	glBindAttribLocation(v.id, attribute_pairs[i].index, attribute_pairs[i].name);
-	//}
+	// If feedback exists, set it up.
+
+	if (feedback_count) {
+		Vector<const char *> feedback;
+		for (int i = 0; i < feedback_count; i++) {
+			if (feedbacks[i].specialization == 0 || (feedbacks[i].specialization & p_specialization)) {
+				// Specialization for this feedback is enabled
+				feedback.push_back(feedbacks[i].name);
+			}
+		}
+
+		if (feedback.size()) {
+			glTransformFeedbackVaryings(spec.id, feedback.size(), feedback.ptr(), GL_INTERLEAVED_ATTRIBS);
+		}
+	}
 
 	glLinkProgram(spec.id);
 

+ 13 - 5
drivers/gles3/shader_gles3.h

@@ -70,6 +70,11 @@ protected:
 		bool default_value = false;
 	};
 
+	struct Feedback {
+		const char *name;
+		uint64_t specialization;
+	};
+
 private:
 	//versions
 	CharString general_defines;
@@ -165,6 +170,8 @@ private:
 	int uniform_count = 0;
 	const UBOPair *ubo_pairs = nullptr;
 	int ubo_count = 0;
+	const Feedback *feedbacks;
+	int feedback_count = 0;
 	const TexUnitPair *texunit_pairs = nullptr;
 	int texunit_pair_count = 0;
 	int specialization_count = 0;
@@ -178,13 +185,13 @@ private:
 
 protected:
 	ShaderGLES3();
-	void _setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_name, int p_uniform_count, const char **p_uniform_names, int p_ubo_count, const UBOPair *p_ubos, int p_texture_count, const TexUnitPair *p_tex_units, int p_specialization_count, const Specialization *p_specializations, int p_variant_count, const char **p_variants);
+	void _setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_name, int p_uniform_count, const char **p_uniform_names, int p_ubo_count, const UBOPair *p_ubos, int p_feedback_count, const Feedback *p_feedback, int p_texture_count, const TexUnitPair *p_tex_units, int p_specialization_count, const Specialization *p_specializations, int p_variant_count, const char **p_variants);
 
-	_FORCE_INLINE_ void _version_bind_shader(RID p_version, int p_variant, uint64_t p_specialization) {
-		ERR_FAIL_INDEX(p_variant, variant_count);
+	_FORCE_INLINE_ bool _version_bind_shader(RID p_version, int p_variant, uint64_t p_specialization) {
+		ERR_FAIL_INDEX_V(p_variant, variant_count, false);
 
 		Version *version = version_owner.get_or_null(p_version);
-		ERR_FAIL_COND(!version);
+		ERR_FAIL_COND_V(!version, false);
 
 		if (version->variants.size() == 0) {
 			_initialize_version(version); //may lack initialization
@@ -210,11 +217,12 @@ protected:
 
 		if (!spec || !spec->ok) {
 			WARN_PRINT_ONCE("shader failed to compile, unable to bind shader.");
-			return;
+			return false;
 		}
 
 		glUseProgram(spec->id);
 		current_shader = spec;
+		return true;
 	}
 
 	_FORCE_INLINE_ int _version_get_uniform(int p_which, RID p_version, int p_variant, uint64_t p_specialization) {

+ 2 - 0
drivers/gles3/shaders/SCsub

@@ -19,3 +19,5 @@ if "GLES3_GLSL" in env["BUILDERS"]:
     env.GLES3_GLSL("cubemap_filter.glsl")
     env.GLES3_GLSL("canvas_occlusion.glsl")
     env.GLES3_GLSL("canvas_sdf.glsl")
+    env.GLES3_GLSL("particles.glsl")
+    env.GLES3_GLSL("particles_copy.glsl")

+ 2 - 2
drivers/gles3/shaders/cubemap_filter.glsl

@@ -31,7 +31,7 @@ uniform samplerCube source_cube; //texunit:0
 uniform int face_id;
 
 #ifndef MODE_DIRECT_WRITE
-uniform int sample_count;
+uniform uint sample_count;
 uniform vec4 sample_directions_mip[MAX_SAMPLE_COUNT];
 uniform float weight;
 #endif
@@ -105,7 +105,7 @@ void main() {
 	T[1] = cross(N, T[0]);
 	T[2] = N;
 
-	for (int sample_num = 0; sample_num < sample_count; sample_num++) {
+	for (uint sample_num = 0u; sample_num < sample_count; sample_num++) {
 		vec4 sample_direction_mip = sample_directions_mip[sample_num];
 		vec3 L = T * sample_direction_mip.xyz;
 		vec3 val = textureLod(source_cube, L, sample_direction_mip.w).rgb;

+ 501 - 0
drivers/gles3/shaders/particles.glsl

@@ -0,0 +1,501 @@
+/* clang-format off */
+#[modes]
+
+mode_default =
+
+#[specializations]
+
+MODE_3D = false
+USERDATA1_USED = false
+USERDATA2_USED = false
+USERDATA3_USED = false
+USERDATA4_USED = false
+USERDATA5_USED = false
+USERDATA6_USED = false
+
+#[vertex]
+
+#define SDF_MAX_LENGTH 16384.0
+
+layout(std140) uniform GlobalShaderUniformData { //ubo:1
+	vec4 global_shader_uniforms[MAX_GLOBAL_SHADER_UNIFORMS];
+};
+
+// This needs to be outside clang-format so the ubo comment is in the right place
+#ifdef MATERIAL_UNIFORMS_USED
+layout(std140) uniform MaterialUniforms{ //ubo:2
+
+#MATERIAL_UNIFORMS
+
+};
+#endif
+
+/* clang-format on */
+
+#define MAX_ATTRACTORS 32
+
+#define ATTRACTOR_TYPE_SPHERE uint(0)
+#define ATTRACTOR_TYPE_BOX uint(1)
+#define ATTRACTOR_TYPE_VECTOR_FIELD uint(2)
+
+struct Attractor {
+	mat4 transform;
+	vec4 extents; // Extents or radius. w-channel is padding.
+
+	uint type;
+	float strength;
+	float attenuation;
+	float directionality;
+};
+
+#define MAX_COLLIDERS 32
+
+#define COLLIDER_TYPE_SPHERE uint(0)
+#define COLLIDER_TYPE_BOX uint(1)
+#define COLLIDER_TYPE_SDF uint(2)
+#define COLLIDER_TYPE_HEIGHT_FIELD uint(3)
+#define COLLIDER_TYPE_2D_SDF uint(4)
+
+struct Collider {
+	mat4 transform;
+	vec4 extents; // Extents or radius. w-channel is padding.
+
+	uint type;
+	float scale;
+	float pad0;
+	float pad1;
+};
+
+layout(std140) uniform FrameData { //ubo:0
+	bool emitting;
+	uint cycle;
+	float system_phase;
+	float prev_system_phase;
+
+	float explosiveness;
+	float randomness;
+	float time;
+	float delta;
+
+	float particle_size;
+	float pad0;
+	float pad1;
+	float pad2;
+
+	uint random_seed;
+	uint attractor_count;
+	uint collider_count;
+	uint frame;
+
+	mat4 emission_transform;
+
+	Attractor attractors[MAX_ATTRACTORS];
+	Collider colliders[MAX_COLLIDERS];
+};
+
+#define PARTICLE_FLAG_ACTIVE uint(1)
+#define PARTICLE_FLAG_STARTED uint(2)
+#define PARTICLE_FLAG_TRAILED uint(4)
+#define PARTICLE_FRAME_MASK uint(0xFFFF)
+#define PARTICLE_FRAME_SHIFT uint(16)
+
+// ParticleData
+layout(location = 0) in highp vec4 color;
+layout(location = 1) in highp vec4 velocity_flags;
+layout(location = 2) in highp vec4 custom;
+layout(location = 3) in highp vec4 xform_1;
+layout(location = 4) in highp vec4 xform_2;
+#ifdef MODE_3D
+layout(location = 5) in highp vec4 xform_3;
+#endif
+#ifdef USERDATA1_USED
+layout(location = 6) in highp vec4 userdata1;
+#endif
+#ifdef USERDATA2_USED
+layout(location = 7) in highp vec4 userdata2;
+#endif
+#ifdef USERDATA3_USED
+layout(location = 8) in highp vec4 userdata3;
+#endif
+#ifdef USERDATA4_USED
+layout(location = 9) in highp vec4 userdata4;
+#endif
+#ifdef USERDATA5_USED
+layout(location = 10) in highp vec4 userdata5;
+#endif
+#ifdef USERDATA6_USED
+layout(location = 11) in highp vec4 userdata6;
+#endif
+
+out highp vec4 out_color; //tfb:
+out highp vec4 out_velocity_flags; //tfb:
+out highp vec4 out_custom; //tfb:
+out highp vec4 out_xform_1; //tfb:
+out highp vec4 out_xform_2; //tfb:
+#ifdef MODE_3D
+out highp vec4 out_xform_3; //tfb:MODE_3D
+#endif
+#ifdef USERDATA1_USED
+out highp vec4 out_userdata1; //tfb:USERDATA1_USED
+#endif
+#ifdef USERDATA2_USED
+out highp vec4 out_userdata2; //tfb:USERDATA2_USED
+#endif
+#ifdef USERDATA3_USED
+out highp vec4 out_userdata3; //tfb:USERDATA3_USED
+#endif
+#ifdef USERDATA4_USED
+out highp vec4 out_userdata4; //tfb:USERDATA4_USED
+#endif
+#ifdef USERDATA5_USED
+out highp vec4 out_userdata5; //tfb:USERDATA5_USED
+#endif
+#ifdef USERDATA6_USED
+out highp vec4 out_userdata6; //tfb:USERDATA6_USED
+#endif
+
+uniform sampler2D height_field_texture; //texunit:0
+
+uniform float lifetime;
+uniform bool clear;
+uniform uint total_particles;
+uniform bool use_fractional_delta;
+
+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;
+}
+
+vec3 safe_normalize(vec3 direction) {
+	const float EPSILON = 0.001;
+	if (length(direction) < EPSILON) {
+		return vec3(0.0);
+	}
+	return normalize(direction);
+}
+
+// Needed whenever 2D sdf texture is read from as it is packed in RGBA8.
+float vec4_to_float(vec4 p_vec) {
+	return dot(p_vec, vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0)) * 2.0 - 1.0;
+}
+
+#GLOBALS
+
+void main() {
+	bool apply_forces = true;
+	bool apply_velocity = true;
+	float local_delta = delta;
+
+	float mass = 1.0;
+
+	bool restart = false;
+
+	bool restart_position = false;
+	bool restart_rotation_scale = false;
+	bool restart_velocity = false;
+	bool restart_color = false;
+	bool restart_custom = false;
+
+	mat4 xform = mat4(1.0);
+	uint flags = 0u;
+
+	if (clear) {
+		out_color = vec4(1.0);
+		out_custom = vec4(0.0);
+		out_velocity_flags = vec4(0.0);
+	} else {
+		out_color = color;
+		out_velocity_flags = velocity_flags;
+		out_custom = custom;
+		xform[0] = xform_1;
+		xform[1] = xform_2;
+#ifdef MODE_3D
+		xform[2] = xform_3;
+#endif
+		xform = transpose(xform);
+		flags = floatBitsToUint(velocity_flags.w);
+	}
+
+	//clear started flag if set
+	flags &= ~PARTICLE_FLAG_STARTED;
+
+	bool collided = false;
+	vec3 collision_normal = vec3(0.0);
+	float collision_depth = 0.0;
+
+	vec3 attractor_force = vec3(0.0);
+
+#if !defined(DISABLE_VELOCITY)
+
+	if (bool(flags & PARTICLE_FLAG_ACTIVE)) {
+		xform[3].xyz += out_velocity_flags.xyz * local_delta;
+	}
+#endif
+	uint index = uint(gl_VertexID);
+	if (emitting) {
+		float restart_phase = float(index) / float(total_particles);
+
+		if (randomness > 0.0) {
+			uint seed = cycle;
+			if (restart_phase >= system_phase) {
+				seed -= uint(1);
+			}
+			seed *= uint(total_particles);
+			seed += index;
+			float random = float(hash(seed) % uint(65536)) / 65536.0;
+			restart_phase += randomness * random * 1.0 / float(total_particles);
+		}
+
+		restart_phase *= (1.0 - explosiveness);
+
+		if (system_phase > prev_system_phase) {
+			// restart_phase >= prev_system_phase is used so particles emit in the first frame they are processed
+
+			if (restart_phase >= prev_system_phase && restart_phase < system_phase) {
+				restart = true;
+				if (use_fractional_delta) {
+					local_delta = (system_phase - restart_phase) * lifetime;
+				}
+			}
+
+		} else if (delta > 0.0) {
+			if (restart_phase >= prev_system_phase) {
+				restart = true;
+				if (use_fractional_delta) {
+					local_delta = (1.0 - restart_phase + system_phase) * lifetime;
+				}
+
+			} else if (restart_phase < system_phase) {
+				restart = true;
+				if (use_fractional_delta) {
+					local_delta = (system_phase - restart_phase) * lifetime;
+				}
+			}
+		}
+
+		if (restart) {
+			flags = emitting ? (PARTICLE_FLAG_ACTIVE | PARTICLE_FLAG_STARTED | (cycle << PARTICLE_FRAME_SHIFT)) : 0u;
+			restart_position = true;
+			restart_rotation_scale = true;
+			restart_velocity = true;
+			restart_color = true;
+			restart_custom = true;
+		}
+	}
+
+	bool particle_active = bool(flags & PARTICLE_FLAG_ACTIVE);
+
+	uint particle_number = (flags >> PARTICLE_FRAME_SHIFT) * uint(total_particles) + index;
+
+	if (restart && particle_active) {
+#CODE : START
+	}
+
+	if (particle_active) {
+		for (uint i = 0u; i < attractor_count; i++) {
+			vec3 dir;
+			float amount;
+			vec3 rel_vec = xform[3].xyz - attractors[i].transform[3].xyz;
+			vec3 local_pos = rel_vec * mat3(attractors[i].transform);
+
+			switch (attractors[i].type) {
+				case ATTRACTOR_TYPE_SPHERE: {
+					dir = safe_normalize(rel_vec);
+					float d = length(local_pos) / attractors[i].extents.x;
+					if (d > 1.0) {
+						continue;
+					}
+					amount = max(0.0, 1.0 - d);
+				} break;
+				case ATTRACTOR_TYPE_BOX: {
+					dir = safe_normalize(rel_vec);
+
+					vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz);
+					float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z));
+					if (d > 1.0) {
+						continue;
+					}
+					amount = max(0.0, 1.0 - d);
+
+				} break;
+				case ATTRACTOR_TYPE_VECTOR_FIELD: {
+				} break;
+			}
+			amount = pow(amount, attractors[i].attenuation);
+			dir = safe_normalize(mix(dir, attractors[i].transform[2].xyz, attractors[i].directionality));
+			attractor_force -= amount * dir * attractors[i].strength;
+		}
+
+		float particle_size = particle_size;
+
+#ifdef USE_COLLISION_SCALE
+
+		particle_size *= dot(vec3(length(xform[0].xyz), length(xform[1].xyz), length(xform[2].xyz)), vec3(0.33333333333));
+
+#endif
+
+		if (collider_count == 1u && colliders[0].type == COLLIDER_TYPE_2D_SDF) {
+			//2D collision
+
+			vec2 pos = xform[3].xy;
+			vec4 to_sdf_x = colliders[0].transform[0];
+			vec4 to_sdf_y = colliders[0].transform[1];
+			vec2 sdf_pos = vec2(dot(vec4(pos, 0, 1), to_sdf_x), dot(vec4(pos, 0, 1), to_sdf_y));
+
+			vec4 sdf_to_screen = vec4(colliders[0].extents.xyz, colliders[0].scale);
+
+			vec2 uv_pos = sdf_pos * sdf_to_screen.xy + sdf_to_screen.zw;
+
+			if (all(greaterThan(uv_pos, vec2(0.0))) && all(lessThan(uv_pos, vec2(1.0)))) {
+				vec2 pos2 = pos + vec2(0, particle_size);
+				vec2 sdf_pos2 = vec2(dot(vec4(pos2, 0, 1), to_sdf_x), dot(vec4(pos2, 0, 1), to_sdf_y));
+				float sdf_particle_size = distance(sdf_pos, sdf_pos2);
+
+				float d = vec4_to_float(texture(height_field_texture, uv_pos)) * SDF_MAX_LENGTH;
+
+				d -= sdf_particle_size;
+
+				if (d < 0.0) {
+					const float EPSILON = 0.001;
+					vec2 n = normalize(vec2(
+							vec4_to_float(texture(height_field_texture, uv_pos + vec2(EPSILON, 0.0))) - vec4_to_float(texture(height_field_texture, uv_pos - vec2(EPSILON, 0.0))),
+							vec4_to_float(texture(height_field_texture, uv_pos + vec2(0.0, EPSILON))) - vec4_to_float(texture(height_field_texture, uv_pos - vec2(0.0, EPSILON)))));
+
+					collided = true;
+					sdf_pos2 = sdf_pos + n * d;
+					pos2 = vec2(dot(vec4(sdf_pos2, 0, 1), colliders[0].transform[2]), dot(vec4(sdf_pos2, 0, 1), colliders[0].transform[3]));
+
+					n = pos - pos2;
+
+					collision_normal = normalize(vec3(n, 0.0));
+					collision_depth = length(n);
+				}
+			}
+
+		} else {
+			for (uint i = 0u; i < collider_count; i++) {
+				vec3 normal;
+				float depth;
+				bool col = false;
+
+				vec3 rel_vec = xform[3].xyz - colliders[i].transform[3].xyz;
+				vec3 local_pos = rel_vec * mat3(colliders[i].transform);
+
+				switch (colliders[i].type) {
+					case COLLIDER_TYPE_SPHERE: {
+						float d = length(rel_vec) - (particle_size + colliders[i].extents.x);
+
+						if (d < 0.0) {
+							col = true;
+							depth = -d;
+							normal = normalize(rel_vec);
+						}
+
+					} break;
+					case COLLIDER_TYPE_BOX: {
+						vec3 abs_pos = abs(local_pos);
+						vec3 sgn_pos = sign(local_pos);
+
+						if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) {
+							//point outside box
+
+							vec3 closest = min(abs_pos, colliders[i].extents.xyz);
+							vec3 rel = abs_pos - closest;
+							depth = length(rel) - particle_size;
+							if (depth < 0.0) {
+								col = true;
+								normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos);
+								depth = -depth;
+							}
+						} else {
+							//point inside box
+							vec3 axis_len = colliders[i].extents.xyz - abs_pos;
+							// there has to be a faster way to do this?
+							if (all(lessThan(axis_len.xx, axis_len.yz))) {
+								normal = vec3(1, 0, 0);
+							} else if (all(lessThan(axis_len.yy, axis_len.xz))) {
+								normal = vec3(0, 1, 0);
+							} else {
+								normal = vec3(0, 0, 1);
+							}
+
+							col = true;
+							depth = dot(normal * axis_len, vec3(1)) + particle_size;
+							normal = mat3(colliders[i].transform) * (normal * sgn_pos);
+						}
+
+					} break;
+					case COLLIDER_TYPE_SDF: {
+					} break;
+					case COLLIDER_TYPE_HEIGHT_FIELD: {
+						vec3 local_pos_bottom = local_pos;
+						local_pos_bottom.y -= particle_size;
+
+						if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) {
+							continue;
+						}
+						const float DELTA = 1.0 / 8192.0;
+
+						vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5;
+
+						float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r;
+
+						if (y > uvw_pos.y) {
+							//inside heightfield
+
+							vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
+							vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
+							vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz;
+
+							normal = normalize(cross(pos1 - pos2, pos1 - pos3));
+							float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y;
+
+							col = true;
+							depth = dot(normal, pos1) - dot(normal, local_pos_bottom);
+						}
+
+					} break;
+				}
+
+				if (col) {
+					if (!collided) {
+						collided = true;
+						collision_normal = normal;
+						collision_depth = depth;
+					} else {
+						vec3 c = collision_normal * collision_depth;
+						c += normal * max(0.0, depth - dot(normal, c));
+						collision_normal = normalize(c);
+						collision_depth = length(c);
+					}
+				}
+			}
+		}
+	}
+
+	if (particle_active) {
+#CODE : PROCESS
+	}
+
+	flags &= ~PARTICLE_FLAG_ACTIVE;
+	if (particle_active) {
+		flags |= PARTICLE_FLAG_ACTIVE;
+	}
+
+	xform = transpose(xform);
+	out_xform_1 = xform[0];
+	out_xform_2 = xform[1];
+#ifdef MODE_3D
+	out_xform_3 = xform[2];
+#endif
+	out_velocity_flags.w = uintBitsToFloat(flags);
+}
+
+/* clang-format off */
+#[fragment]
+
+void main() {
+}
+/* clang-format on */

+ 122 - 0
drivers/gles3/shaders/particles_copy.glsl

@@ -0,0 +1,122 @@
+/* clang-format off */
+#[modes]
+
+mode_default =
+
+#[specializations]
+
+MODE_3D = false
+
+#[vertex]
+
+#include "stdlib_inc.glsl"
+
+// ParticleData
+layout(location = 0) in highp vec4 color;
+layout(location = 1) in highp vec4 velocity_flags;
+layout(location = 2) in highp vec4 custom;
+layout(location = 3) in highp vec4 xform_1;
+layout(location = 4) in highp vec4 xform_2;
+#ifdef MODE_3D
+layout(location = 5) in highp vec4 xform_3;
+#endif
+
+/* clang-format on */
+out highp vec4 out_xform_1; //tfb:
+out highp vec4 out_xform_2; //tfb:
+#ifdef MODE_3D
+out highp vec4 out_xform_3; //tfb:MODE_3D
+#endif
+flat out highp uvec4 instance_color_custom_data; //tfb:
+
+uniform lowp vec3 sort_direction;
+uniform highp float frame_remainder;
+
+uniform highp vec3 align_up;
+uniform highp uint align_mode;
+
+uniform highp mat4 inv_emission_transform;
+
+#define TRANSFORM_ALIGN_DISABLED uint(0)
+#define TRANSFORM_ALIGN_Z_BILLBOARD uint(1)
+#define TRANSFORM_ALIGN_Y_TO_VELOCITY uint(2)
+#define TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY uint(3)
+
+#define PARTICLE_FLAG_ACTIVE uint(1)
+
+void main() {
+	mat4 txform = mat4(vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0)); // zero scale, becomes invisible.
+	if (bool(floatBitsToUint(velocity_flags.w) & PARTICLE_FLAG_ACTIVE)) {
+#ifdef MODE_3D
+		txform = transpose(mat4(xform_1, xform_2, xform_3, vec4(0.0, 0.0, 0.0, 1.0)));
+#else
+		txform = transpose(mat4(xform_1, xform_2, vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)));
+#endif
+
+		switch (align_mode) {
+			case TRANSFORM_ALIGN_DISABLED: {
+			} break; //nothing
+			case TRANSFORM_ALIGN_Z_BILLBOARD: {
+				mat3 local = mat3(normalize(cross(align_up, sort_direction)), align_up, sort_direction);
+				local = local * mat3(txform);
+				txform[0].xyz = local[0];
+				txform[1].xyz = local[1];
+				txform[2].xyz = local[2];
+
+			} break;
+			case TRANSFORM_ALIGN_Y_TO_VELOCITY: {
+				vec3 v = velocity_flags.xyz;
+				float s = (length(txform[0]) + length(txform[1]) + length(txform[2])) / 3.0;
+				if (length(v) > 0.0) {
+					txform[1].xyz = normalize(v);
+				} else {
+					txform[1].xyz = normalize(txform[1].xyz);
+				}
+
+				txform[0].xyz = normalize(cross(txform[1].xyz, txform[2].xyz));
+				txform[2].xyz = vec3(0.0, 0.0, 1.0) * s;
+				txform[0].xyz *= s;
+				txform[1].xyz *= s;
+			} break;
+			case TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY: {
+				vec3 sv = velocity_flags.xyz - sort_direction * dot(sort_direction, velocity_flags.xyz); //screen velocity
+				float s = (length(txform[0]) + length(txform[1]) + length(txform[2])) / 3.0;
+
+				if (length(sv) == 0.0) {
+					sv = align_up;
+				}
+
+				sv = normalize(sv);
+
+				txform[0].xyz = normalize(cross(sv, sort_direction)) * s;
+				txform[1].xyz = sv * s;
+				txform[2].xyz = sort_direction * s;
+
+			} break;
+		}
+
+		txform[3].xyz += velocity_flags.xyz * frame_remainder;
+
+#ifndef MODE_3D
+		// In global mode, bring 2D particles to local coordinates
+		// as they will be drawn with the node position as origin.
+		txform = inv_emission_transform * txform;
+#endif
+
+		txform = transpose(txform);
+	}
+
+	instance_color_custom_data = uvec4(packHalf2x16(color.xy), packHalf2x16(color.zw), packHalf2x16(custom.xy), packHalf2x16(custom.zw));
+	out_xform_1 = txform[0];
+	out_xform_2 = txform[1];
+#ifdef MODE_3D
+	out_xform_3 = txform[2];
+#endif
+}
+
+/* clang-format off */
+#[fragment]
+
+void main() {
+}
+/* clang-format on */

+ 8 - 6
drivers/gles3/shaders/scene.glsl

@@ -197,7 +197,7 @@ out vec3 tangent_interp;
 out vec3 binormal_interp;
 #endif
 
-#if defined(MATERIAL_UNIFORMS_USED)
+#ifdef MATERIAL_UNIFORMS_USED
 
 /* clang-format off */
 layout(std140) uniform MaterialUniforms { // ubo:3
@@ -366,7 +366,9 @@ void main() {
 #endif
 #endif
 
+#ifndef MODE_RENDER_DEPTH
 #include "tonemap_inc.glsl"
+#endif
 #include "stdlib_inc.glsl"
 
 /* texture unit usage, N is max_texture_unity-N
@@ -428,7 +430,7 @@ layout(std140) uniform GlobalShaderUniformData { //ubo:1
 
 	/* Material Uniforms */
 
-#if defined(MATERIAL_UNIFORMS_USED)
+#ifdef MATERIAL_UNIFORMS_USED
 
 /* clang-format off */
 layout(std140) uniform MaterialUniforms { // ubo:3
@@ -535,7 +537,7 @@ layout(std140) uniform OmniLightData { // ubo:5
 	LightData omni_lights[MAX_LIGHT_DATA_STRUCTS];
 };
 uniform uint omni_light_indices[MAX_FORWARD_LIGHTS];
-uniform int omni_light_count;
+uniform uint omni_light_count;
 #endif
 
 #ifndef DISABLE_LIGHT_SPOT
@@ -545,7 +547,7 @@ layout(std140) uniform SpotLightData { // ubo:6
 	LightData spot_lights[MAX_LIGHT_DATA_STRUCTS];
 };
 uniform uint spot_light_indices[MAX_FORWARD_LIGHTS];
-uniform int spot_light_count;
+uniform uint spot_light_count;
 #endif
 
 #ifdef USE_ADDITIVE_LIGHTING
@@ -1188,7 +1190,7 @@ void main() {
 #endif //!DISABLE_LIGHT_DIRECTIONAL
 
 #ifndef DISABLE_LIGHT_OMNI
-	for (int i = 0; i < MAX_FORWARD_LIGHTS; i++) {
+	for (uint i = 0u; i < MAX_FORWARD_LIGHTS; i++) {
 		if (i >= omni_light_count) {
 			break;
 		}
@@ -1211,7 +1213,7 @@ void main() {
 #endif // !DISABLE_LIGHT_OMNI
 
 #ifndef DISABLE_LIGHT_SPOT
-	for (int i = 0; i < MAX_FORWARD_LIGHTS; i++) {
+	for (uint i = 0u; i < MAX_FORWARD_LIGHTS; i++) {
 		if (i >= spot_light_count) {
 			break;
 		}

+ 241 - 28
drivers/gles3/storage/material_storage.cpp

@@ -34,6 +34,7 @@
 
 #include "config.h"
 #include "material_storage.h"
+#include "particles_storage.h"
 #include "texture_storage.h"
 
 #include "drivers/gles3/rasterizer_canvas_gles3.h"
@@ -1342,13 +1343,13 @@ MaterialStorage::MaterialStorage() {
 
 	shader_data_request_func[RS::SHADER_SPATIAL] = _create_scene_shader_func;
 	shader_data_request_func[RS::SHADER_CANVAS_ITEM] = _create_canvas_shader_func;
-	shader_data_request_func[RS::SHADER_PARTICLES] = nullptr;
+	shader_data_request_func[RS::SHADER_PARTICLES] = _create_particles_shader_func;
 	shader_data_request_func[RS::SHADER_SKY] = _create_sky_shader_func;
 	shader_data_request_func[RS::SHADER_FOG] = nullptr;
 
 	material_data_request_func[RS::SHADER_SPATIAL] = _create_scene_material_func;
 	material_data_request_func[RS::SHADER_CANVAS_ITEM] = _create_canvas_material_func;
-	material_data_request_func[RS::SHADER_PARTICLES] = nullptr;
+	material_data_request_func[RS::SHADER_PARTICLES] = _create_particles_material_func;
 	material_data_request_func[RS::SHADER_SKY] = _create_sky_material_func;
 	material_data_request_func[RS::SHADER_FOG] = nullptr;
 
@@ -1613,32 +1614,32 @@ MaterialStorage::MaterialStorage() {
 
 	{
 		// Setup Particles compiler
-		/*
-ShaderCompiler::DefaultIdentifierActions actions;
 
-		actions.renames["COLOR"] = "PARTICLE.color";
-		actions.renames["VELOCITY"] = "PARTICLE.velocity";
+		ShaderCompiler::DefaultIdentifierActions actions;
+
+		actions.renames["COLOR"] = "out_color";
+		actions.renames["VELOCITY"] = "out_velocity_flags.xyz";
 		//actions.renames["MASS"] = "mass"; ?
 		actions.renames["ACTIVE"] = "particle_active";
 		actions.renames["RESTART"] = "restart";
-		actions.renames["CUSTOM"] = "PARTICLE.custom";
-		for (int i = 0; i < ParticlesShader::MAX_USERDATAS; i++) {
+		actions.renames["CUSTOM"] = "out_custom";
+		for (int i = 0; i < PARTICLES_MAX_USERDATAS; i++) {
 			String udname = "USERDATA" + itos(i + 1);
-			actions.renames[udname] = "PARTICLE.userdata" + itos(i + 1);
+			actions.renames[udname] = "out_userdata" + itos(i + 1);
 			actions.usage_defines[udname] = "#define USERDATA" + itos(i + 1) + "_USED\n";
 		}
-		actions.renames["TRANSFORM"] = "PARTICLE.xform";
-		actions.renames["TIME"] = "frame_history.data[0].time";
+		actions.renames["TRANSFORM"] = "xform";
+		actions.renames["TIME"] = "time";
 		actions.renames["PI"] = _MKSTR(Math_PI);
 		actions.renames["TAU"] = _MKSTR(Math_TAU);
 		actions.renames["E"] = _MKSTR(Math_E);
-		actions.renames["LIFETIME"] = "params.lifetime";
+		actions.renames["LIFETIME"] = "lifetime";
 		actions.renames["DELTA"] = "local_delta";
 		actions.renames["NUMBER"] = "particle_number";
 		actions.renames["INDEX"] = "index";
 		//actions.renames["GRAVITY"] = "current_gravity";
-		actions.renames["EMISSION_TRANSFORM"] = "FRAME.emission_transform";
-		actions.renames["RANDOM_SEED"] = "FRAME.random_seed";
+		actions.renames["EMISSION_TRANSFORM"] = "emission_transform";
+		actions.renames["RANDOM_SEED"] = "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";
@@ -1660,18 +1661,10 @@ ShaderCompiler::DefaultIdentifierActions actions;
 		actions.render_mode_defines["keep_data"] = "#define ENABLE_KEEP_DATA\n";
 		actions.render_mode_defines["collision_use_scale"] = "#define USE_COLLISION_SCALE\n";
 
-		actions.sampler_array_name = "material_samplers";
-		actions.base_texture_binding_index = 1;
-		actions.texture_layout_set = 3;
-		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_shader_uniforms.data";
 
-		particles_shader.compiler.initialize(actions);
-		*/
+		shaders.compiler_particles.initialize(actions);
 	}
 
 	{
@@ -2470,8 +2463,8 @@ void MaterialStorage::shader_set_code(RID p_shader, const String &p_code) {
 	RS::ShaderMode new_mode;
 	if (mode_string == "canvas_item") {
 		new_mode = RS::SHADER_CANVAS_ITEM;
-		//} else if (mode_string == "particles") {
-		//	new_mode = RS::SHADER_PARTICLES;
+	} else if (mode_string == "particles") {
+		new_mode = RS::SHADER_PARTICLES;
 	} else if (mode_string == "spatial") {
 		new_mode = RS::SHADER_SPATIAL;
 	} else if (mode_string == "sky") {
@@ -2542,6 +2535,9 @@ void MaterialStorage::shader_set_path_hint(RID p_shader, const String &p_path) {
 	ERR_FAIL_COND(!shader);
 
 	shader->path_hint = p_path;
+	if (shader->data) {
+		shader->data->set_path_hint(p_path);
+	}
 }
 
 String MaterialStorage::shader_get_code(RID p_shader) const {
@@ -2809,6 +2805,10 @@ void MaterialStorage::material_update_dependency(RID p_material, DependencyTrack
 
 /* Canvas Shader Data */
 
+void CanvasShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
 void CanvasShaderData::set_code(const String &p_code) {
 	// compile the shader
 
@@ -3007,7 +3007,7 @@ GLES3::ShaderData *GLES3::_create_canvas_shader_func() {
 }
 
 void CanvasMaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
-	return update_parameters_internal(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size);
+	update_parameters_internal(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size);
 }
 
 void CanvasMaterialData::bind_uniforms() {
@@ -3043,6 +3043,10 @@ GLES3::MaterialData *GLES3::_create_canvas_material_func(ShaderData *p_shader) {
 ////////////////////////////////////////////////////////////////////////////////
 // SKY SHADER
 
+void SkyShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
 void SkyShaderData::set_code(const String &p_code) {
 	//compile
 
@@ -3251,7 +3255,7 @@ GLES3::ShaderData *GLES3::_create_sky_shader_func() {
 
 void SkyMaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
 	uniform_set_updated = true;
-	return update_parameters_internal(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size);
+	update_parameters_internal(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size);
 }
 
 SkyMaterialData::~SkyMaterialData() {
@@ -3286,6 +3290,10 @@ void SkyMaterialData::bind_uniforms() {
 ////////////////////////////////////////////////////////////////////////////////
 // Scene SHADER
 
+void SceneShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
 void SceneShaderData::set_code(const String &p_code) {
 	//compile
 
@@ -3592,7 +3600,7 @@ void SceneMaterialData::set_next_pass(RID p_pass) {
 }
 
 void SceneMaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
-	return update_parameters_internal(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size);
+	update_parameters_internal(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size);
 }
 
 SceneMaterialData::~SceneMaterialData() {
@@ -3625,4 +3633,209 @@ void SceneMaterialData::bind_uniforms() {
 	}
 }
 
+/* Particles SHADER */
+
+void ParticlesShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
+void ParticlesShaderData::set_code(const String &p_code) {
+	//compile
+
+	code = p_code;
+	valid = false;
+	ubo_size = 0;
+	uniforms.clear();
+	uses_collision = false;
+
+	if (code.is_empty()) {
+		return; //just invalid, but no error
+	}
+
+	ShaderCompiler::GeneratedCode gen_code;
+	ShaderCompiler::IdentifierActions actions;
+	actions.entry_point_stages["start"] = ShaderCompiler::STAGE_VERTEX;
+	actions.entry_point_stages["process"] = ShaderCompiler::STAGE_VERTEX;
+
+	actions.usage_flag_pointers["COLLIDED"] = &uses_collision;
+
+	userdata_count = 0;
+	for (uint32_t i = 0; i < PARTICLES_MAX_USERDATAS; i++) {
+		userdatas_used[i] = false;
+		actions.usage_flag_pointers["USERDATA" + itos(i + 1)] = &userdatas_used[i];
+	}
+
+	actions.uniforms = &uniforms;
+
+	Error err = MaterialStorage::get_singleton()->shaders.compiler_particles.compile(RS::SHADER_PARTICLES, code, &actions, path, gen_code);
+	ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed.");
+
+	if (version.is_null()) {
+		version = MaterialStorage::get_singleton()->shaders.particles_process_shader.version_create();
+	}
+
+	for (uint32_t i = 0; i < PARTICLES_MAX_USERDATAS; i++) {
+		if (userdatas_used[i]) {
+			userdata_count++;
+		}
+	}
+
+	Vector<StringName> texture_uniform_names;
+	for (int i = 0; i < gen_code.texture_uniforms.size(); i++) {
+		texture_uniform_names.push_back(gen_code.texture_uniforms[i].name);
+	}
+
+	MaterialStorage::get_singleton()->shaders.particles_process_shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines, texture_uniform_names);
+	ERR_FAIL_COND(!MaterialStorage::get_singleton()->shaders.particles_process_shader.version_is_valid(version));
+
+	ubo_size = gen_code.uniform_total_size;
+	ubo_offsets = gen_code.uniform_offsets;
+	texture_uniforms = gen_code.texture_uniforms;
+
+	valid = true;
+}
+
+void ParticlesShaderData::set_default_texture_parameter(const StringName &p_name, RID p_texture, int p_index) {
+	if (!p_texture.is_valid()) {
+		if (default_texture_params.has(p_name) && default_texture_params[p_name].has(p_index)) {
+			default_texture_params[p_name].erase(p_index);
+
+			if (default_texture_params[p_name].is_empty()) {
+				default_texture_params.erase(p_name);
+			}
+		}
+	} else {
+		if (!default_texture_params.has(p_name)) {
+			default_texture_params[p_name] = HashMap<int, RID>();
+		}
+		default_texture_params[p_name][p_index] = p_texture;
+	}
+}
+
+void ParticlesShaderData::get_shader_uniform_list(List<PropertyInfo> *p_param_list) const {
+	HashMap<int, StringName> order;
+
+	for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) {
+		if (E.value.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_GLOBAL || E.value.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_INSTANCE) {
+			continue;
+		}
+
+		if (E.value.texture_order >= 0) {
+			order[E.value.texture_order + 100000] = E.key;
+		} else {
+			order[E.value.order] = E.key;
+		}
+	}
+
+	String last_group;
+	for (const KeyValue<int, StringName> &E : order) {
+		String group = uniforms[E.value].group;
+		if (!uniforms[E.value].subgroup.is_empty()) {
+			group += "::" + uniforms[E.value].subgroup;
+		}
+
+		if (group != last_group) {
+			PropertyInfo pi;
+			pi.usage = PROPERTY_USAGE_GROUP;
+			pi.name = group;
+			p_param_list->push_back(pi);
+
+			last_group = group;
+		}
+
+		PropertyInfo pi = ShaderLanguage::uniform_to_property_info(uniforms[E.value]);
+		pi.name = E.value;
+		p_param_list->push_back(pi);
+	}
+}
+
+void ParticlesShaderData::get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const {
+	for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) {
+		if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_INSTANCE) {
+			continue;
+		}
+
+		RendererMaterialStorage::InstanceShaderParam p;
+		p.info = ShaderLanguage::uniform_to_property_info(E.value);
+		p.info.name = E.key; //supply name
+		p.index = E.value.instance_index;
+		p.default_value = ShaderLanguage::constant_value_to_variant(E.value.default_value, E.value.type, E.value.array_size, E.value.hint);
+		p_param_list->push_back(p);
+	}
+}
+
+bool ParticlesShaderData::is_parameter_texture(const StringName &p_param) const {
+	if (!uniforms.has(p_param)) {
+		return false;
+	}
+
+	return uniforms[p_param].texture_order >= 0;
+}
+
+bool ParticlesShaderData::is_animated() const {
+	return false;
+}
+
+bool ParticlesShaderData::casts_shadows() const {
+	return false;
+}
+
+Variant 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.array_size, uniform.hint);
+	}
+	return Variant();
+}
+
+RS::ShaderNativeSourceCode ParticlesShaderData::get_native_source_code() const {
+	return MaterialStorage::get_singleton()->shaders.particles_process_shader.version_get_native_source_code(version);
+}
+
+ParticlesShaderData::~ParticlesShaderData() {
+	if (version.is_valid()) {
+		MaterialStorage::get_singleton()->shaders.particles_process_shader.version_free(version);
+	}
+}
+
+GLES3::ShaderData *GLES3::_create_particles_shader_func() {
+	ParticlesShaderData *shader_data = memnew(ParticlesShaderData);
+	return shader_data;
+}
+
+void ParticleProcessMaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
+	update_parameters_internal(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size);
+}
+
+ParticleProcessMaterialData::~ParticleProcessMaterialData() {
+}
+
+GLES3::MaterialData *GLES3::_create_particles_material_func(ShaderData *p_shader) {
+	ParticleProcessMaterialData *material_data = memnew(ParticleProcessMaterialData);
+	material_data->shader_data = static_cast<ParticlesShaderData *>(p_shader);
+	//update will happen later anyway so do nothing.
+	return material_data;
+}
+
+void ParticleProcessMaterialData::bind_uniforms() {
+	// Bind Material Uniforms
+	glBindBufferBase(GL_UNIFORM_BUFFER, GLES3::PARTICLES_MATERIAL_UNIFORM_LOCATION, uniform_buffer);
+
+	RID *textures = texture_cache.ptrw();
+	ShaderCompiler::GeneratedCode::Texture *texture_uniforms = shader_data->texture_uniforms.ptrw();
+	for (int ti = 0; ti < texture_cache.size(); ti++) {
+		Texture *texture = TextureStorage::get_singleton()->get_texture(textures[ti]);
+		glActiveTexture(GL_TEXTURE1 + ti); // Start at GL_TEXTURE1 becuase texture slot 0 is reserved for the heightmap texture.
+		glBindTexture(target_from_type[texture_uniforms[ti].type], texture->tex_id);
+
+		// Set sampler state here as the same texture can be used in multiple places with different flags
+		// Need to convert sampler state from ShaderLanguage::Texture* to RS::CanvasItemTexture*
+		RS::CanvasItemTextureFilter filter = RS::CanvasItemTextureFilter((int(texture_uniforms[ti].filter) + 1) % RS::CANVAS_ITEM_TEXTURE_FILTER_MAX);
+		RS::CanvasItemTextureRepeat repeat = RS::CanvasItemTextureRepeat((int(texture_uniforms[ti].repeat) + 1) % RS::CANVAS_ITEM_TEXTURE_REPEAT_MIRROR);
+		texture->gl_set_filter(filter);
+		texture->gl_set_repeat(repeat);
+	}
+}
+
 #endif // !GLES3_ENABLED

+ 62 - 0
drivers/gles3/storage/material_storage.h

@@ -44,6 +44,7 @@
 
 #include "../shaders/canvas.glsl.gen.h"
 #include "../shaders/cubemap_filter.glsl.gen.h"
+#include "../shaders/particles.glsl.gen.h"
 #include "../shaders/scene.glsl.gen.h"
 #include "../shaders/sky.glsl.gen.h"
 
@@ -53,6 +54,7 @@ namespace GLES3 {
 
 struct ShaderData {
 	virtual void set_code(const String &p_Code) = 0;
+	virtual void set_path_hint(const String &p_hint) = 0;
 	virtual void set_default_texture_parameter(const StringName &p_name, RID p_texture, int p_index) = 0;
 	virtual void get_shader_uniform_list(List<PropertyInfo> *p_param_list) const = 0;
 
@@ -165,6 +167,7 @@ struct CanvasShaderData : public ShaderData {
 	bool uses_time = false;
 
 	virtual void set_code(const String &p_Code);
+	virtual void set_path_hint(const String &p_hint);
 	virtual void set_default_texture_parameter(const StringName &p_name, RID p_texture, int p_index);
 	virtual void get_shader_uniform_list(List<PropertyInfo> *p_param_list) const;
 	virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;
@@ -216,6 +219,7 @@ struct SkyShaderData : public ShaderData {
 	bool uses_light;
 
 	virtual void set_code(const String &p_Code);
+	virtual void set_path_hint(const String &p_hint);
 	virtual void set_default_texture_parameter(const StringName &p_name, RID p_texture, int p_index);
 	virtual void get_shader_uniform_list(List<PropertyInfo> *p_param_list) const;
 	virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;
@@ -339,6 +343,7 @@ struct SceneShaderData : public ShaderData {
 	uint32_t index = 0;
 
 	virtual void set_code(const String &p_Code);
+	virtual void set_path_hint(const String &p_hint);
 	virtual void set_default_texture_parameter(const StringName &p_name, RID p_texture, int p_index);
 	virtual void get_shader_uniform_list(List<PropertyInfo> *p_param_list) const;
 	virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;
@@ -370,6 +375,62 @@ struct SceneMaterialData : public MaterialData {
 
 MaterialData *_create_scene_material_func(ShaderData *p_shader);
 
+/* Particle Shader */
+
+enum {
+	PARTICLES_MAX_USERDATAS = 6
+};
+
+struct ParticlesShaderData : public ShaderData {
+	bool valid = false;
+	RID version;
+	bool uses_collision = false;
+
+	HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms;
+	Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;
+
+	Vector<uint32_t> ubo_offsets;
+	uint32_t ubo_size = 0;
+
+	String path;
+	String code;
+	HashMap<StringName, HashMap<int, RID>> default_texture_params;
+
+	bool uses_time = false;
+
+	bool userdatas_used[PARTICLES_MAX_USERDATAS] = {};
+	uint32_t userdata_count = 0;
+
+	virtual void set_code(const String &p_Code);
+	virtual void set_path_hint(const String &p_hint);
+	virtual void set_default_texture_parameter(const StringName &p_name, RID p_texture, int p_index);
+	virtual void get_shader_uniform_list(List<PropertyInfo> *p_param_list) const;
+	virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;
+	virtual bool is_parameter_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;
+	virtual RS::ShaderNativeSourceCode get_native_source_code() const;
+
+	ParticlesShaderData() {}
+	virtual ~ParticlesShaderData();
+};
+
+ShaderData *_create_particles_shader_func();
+
+struct ParticleProcessMaterialData : public MaterialData {
+	ParticlesShaderData *shader_data = nullptr;
+	RID uniform_set;
+
+	virtual void set_render_priority(int p_priority) {}
+	virtual void set_next_pass(RID p_pass) {}
+	virtual void update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty);
+	virtual void bind_uniforms();
+	virtual ~ParticleProcessMaterialData();
+};
+
+MaterialData *_create_particles_material_func(ShaderData *p_shader);
+
 /* Global shader uniform structs */
 struct GlobalShaderUniforms {
 	enum {
@@ -506,6 +567,7 @@ public:
 		SkyShaderGLES3 sky_shader;
 		SceneShaderGLES3 scene_shader;
 		CubemapFilterShaderGLES3 cubemap_filter_shader;
+		ParticlesShaderGLES3 particles_process_shader;
 
 		ShaderCompiler compiler_canvas;
 		ShaderCompiler compiler_scene;

+ 4 - 1
drivers/gles3/storage/mesh_storage.cpp

@@ -87,8 +87,11 @@ void MeshStorage::mesh_set_blend_shape_count(RID p_mesh, int p_blend_shape_count
 	ERR_FAIL_COND(!mesh);
 
 	ERR_FAIL_COND(mesh->surface_count > 0); //surfaces already exist
-	WARN_PRINT_ONCE("blend shapes not supported by GLES3 renderer yet");
 	mesh->blend_shape_count = p_blend_shape_count;
+
+	if (p_blend_shape_count > 0) {
+		WARN_PRINT_ONCE("blend shapes not supported by GLES3 renderer yet");
+	}
 }
 
 bool MeshStorage::mesh_needs_instance(RID p_mesh, bool p_has_skeleton) {

+ 3 - 0
drivers/gles3/storage/mesh_storage.h

@@ -327,6 +327,7 @@ public:
 
 	_FORCE_INLINE_ uint32_t mesh_surface_get_lod(void *p_surface, float p_model_scale, float p_distance_threshold, float p_mesh_lod_threshold, uint32_t &r_index_count) const {
 		Mesh::Surface *s = reinterpret_cast<Mesh::Surface *>(p_surface);
+		ERR_FAIL_COND_V(!s, 0);
 
 		int32_t current_lod = -1;
 		r_index_count = s->index_count;
@@ -403,6 +404,8 @@ public:
 	virtual void mesh_instance_check_for_update(RID p_mesh_instance) override;
 	virtual void update_mesh_instances() override;
 
+	// TODO: considering hashing versions with multimesh buffer RID.
+	// Doing so would allow us to avoid specifying multimesh buffer pointers every frame and may improve performance.
 	_FORCE_INLINE_ void mesh_instance_surface_get_vertex_arrays_and_format(RID p_mesh_instance, uint32_t p_surface_index, uint32_t p_input_mask, GLuint &r_vertex_array_gl) {
 		MeshInstance *mi = mesh_instance_owner.get_or_null(p_mesh_instance);
 		ERR_FAIL_COND(!mi);

+ 1171 - 40
drivers/gles3/storage/particles_storage.cpp

@@ -31,6 +31,12 @@
 #ifdef GLES3_ENABLED
 
 #include "particles_storage.h"
+#include "material_storage.h"
+#include "mesh_storage.h"
+#include "texture_storage.h"
+#include "utilities.h"
+
+#include "servers/rendering/rendering_server_default.h"
 
 using namespace GLES3;
 
@@ -42,213 +48,1338 @@ ParticlesStorage *ParticlesStorage::get_singleton() {
 
 ParticlesStorage::ParticlesStorage() {
 	singleton = this;
+	GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
+
+	{
+		String global_defines;
+		global_defines += "#define MAX_GLOBAL_SHADER_UNIFORMS 256\n"; // TODO: this is arbitrary for now
+		material_storage->shaders.particles_process_shader.initialize(global_defines);
+	}
+	{
+		// default material and shader for particles shader
+		particles_shader.default_shader = material_storage->shader_allocate();
+		material_storage->shader_initialize(particles_shader.default_shader);
+		material_storage->shader_set_code(particles_shader.default_shader, R"(
+// Default particles shader.
+
+shader_type particles;
+
+void process() {
+	COLOR = vec4(1.0);
+}
+)");
+		particles_shader.default_material = material_storage->material_allocate();
+		material_storage->material_initialize(particles_shader.default_material);
+		material_storage->material_set_shader(particles_shader.default_material, particles_shader.default_shader);
+	}
+	{
+		particles_shader.copy_shader.initialize();
+		particles_shader.copy_shader_version = particles_shader.copy_shader.version_create();
+	}
 }
 
 ParticlesStorage::~ParticlesStorage() {
 	singleton = nullptr;
+	GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
+
+	material_storage->material_free(particles_shader.default_material);
+	material_storage->shader_free(particles_shader.default_shader);
+	particles_shader.copy_shader.version_free(particles_shader.copy_shader_version);
 }
 
 /* PARTICLES */
 
 RID ParticlesStorage::particles_allocate() {
-	return RID();
+	return particles_owner.allocate_rid();
 }
 
 void ParticlesStorage::particles_initialize(RID p_rid) {
+	particles_owner.initialize_rid(p_rid, Particles());
 }
 
 void ParticlesStorage::particles_free(RID p_rid) {
+	update_particles();
+	Particles *particles = particles_owner.get_or_null(p_rid);
+	particles->dependency.deleted_notify(p_rid);
+	_particles_free_data(particles);
+	particles_owner.free(p_rid);
 }
 
 void ParticlesStorage::particles_set_mode(RID p_particles, RS::ParticlesMode p_mode) {
-}
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	if (particles->mode == p_mode) {
+		return;
+	}
 
-void ParticlesStorage::particles_emit(RID p_particles, const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
+	_particles_free_data(particles);
+
+	particles->mode = p_mode;
 }
 
 void ParticlesStorage::particles_set_emitting(RID p_particles, bool p_emitting) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->emitting = p_emitting;
+}
+
+bool ParticlesStorage::particles_get_emitting(RID p_particles) {
+	ERR_FAIL_COND_V_MSG(RSG::threaded, false, "This function should never be used with threaded rendering, as it stalls the renderer.");
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND_V(!particles, false);
+
+	return particles->emitting;
+}
+
+void ParticlesStorage::_particles_free_data(Particles *particles) {
+	particles->userdata_count = 0;
+	particles->instance_buffer_size_cache = 0;
+	particles->instance_buffer_stride_cache = 0;
+	particles->num_attrib_arrays_cache = 0;
+	particles->process_buffer_stride_cache = 0;
+
+	if (particles->front_process_buffer != 0) {
+		glDeleteVertexArrays(1, &particles->front_vertex_array);
+		glDeleteBuffers(1, &particles->front_process_buffer);
+		glDeleteBuffers(1, &particles->front_instance_buffer);
+		particles->front_vertex_array = 0;
+		particles->front_process_buffer = 0;
+		particles->front_instance_buffer = 0;
+
+		glDeleteVertexArrays(1, &particles->back_vertex_array);
+		glDeleteBuffers(1, &particles->back_process_buffer);
+		glDeleteBuffers(1, &particles->back_instance_buffer);
+		particles->back_vertex_array = 0;
+		particles->back_process_buffer = 0;
+		particles->back_instance_buffer = 0;
+	}
+
+	if (particles->sort_buffer != 0) {
+		glDeleteBuffers(1, &particles->last_frame_buffer);
+		glDeleteBuffers(1, &particles->sort_buffer);
+		particles->last_frame_buffer = 0;
+		particles->sort_buffer = 0;
+		particles->sort_buffer_filled = false;
+		particles->last_frame_buffer_filled = false;
+	}
+
+	if (particles->frame_params_ubo != 0) {
+		glDeleteBuffers(1, &particles->frame_params_ubo);
+		particles->frame_params_ubo = 0;
+	}
 }
 
 void ParticlesStorage::particles_set_amount(RID p_particles, int p_amount) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	if (particles->amount == p_amount) {
+		return;
+	}
+
+	_particles_free_data(particles);
+
+	particles->amount = p_amount;
+
+	particles->prev_ticks = 0;
+	particles->phase = 0;
+	particles->prev_phase = 0;
+	particles->clear = true;
+
+	particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES);
 }
 
 void ParticlesStorage::particles_set_lifetime(RID p_particles, double p_lifetime) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->lifetime = p_lifetime;
 }
 
 void ParticlesStorage::particles_set_one_shot(RID p_particles, bool p_one_shot) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->one_shot = p_one_shot;
 }
 
 void ParticlesStorage::particles_set_pre_process_time(RID p_particles, double p_time) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->pre_process_time = p_time;
 }
-
 void ParticlesStorage::particles_set_explosiveness_ratio(RID p_particles, real_t p_ratio) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->explosiveness = p_ratio;
 }
-
 void ParticlesStorage::particles_set_randomness_ratio(RID p_particles, real_t p_ratio) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->randomness = p_ratio;
 }
 
 void ParticlesStorage::particles_set_custom_aabb(RID p_particles, const AABB &p_aabb) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->custom_aabb = p_aabb;
+	particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
 }
 
 void ParticlesStorage::particles_set_speed_scale(RID p_particles, double p_scale) {
-}
-
-void ParticlesStorage::particles_set_use_local_coordinates(RID p_particles, bool p_enable) {
-}
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
 
-void ParticlesStorage::particles_set_process_material(RID p_particles, RID p_material) {
+	particles->speed_scale = p_scale;
 }
+void ParticlesStorage::particles_set_use_local_coordinates(RID p_particles, bool p_enable) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
 
-RID ParticlesStorage::particles_get_process_material(RID p_particles) const {
-	return RID();
+	particles->use_local_coords = p_enable;
+	particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES);
 }
 
 void ParticlesStorage::particles_set_fixed_fps(RID p_particles, int p_fps) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->fixed_fps = p_fps;
+
+	_particles_free_data(particles);
+
+	particles->prev_ticks = 0;
+	particles->phase = 0;
+	particles->prev_phase = 0;
+	particles->clear = true;
+
+	particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES);
 }
 
 void ParticlesStorage::particles_set_interpolate(RID p_particles, bool p_enable) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->interpolate = p_enable;
 }
 
 void ParticlesStorage::particles_set_fractional_delta(RID p_particles, bool p_enable) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->fractional_delta = p_enable;
 }
 
-void ParticlesStorage::particles_set_subemitter(RID p_particles, RID p_subemitter_particles) {
+void ParticlesStorage::particles_set_trails(RID p_particles, bool p_enable, double p_length) {
+	if (p_enable) {
+		WARN_PRINT_ONCE("The OpenGL 3 renderer does not support particle trails");
+	}
 }
 
-void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p_axis, const Vector3 &p_up_axis) {
+void ParticlesStorage::particles_set_trail_bind_poses(RID p_particles, const Vector<Transform3D> &p_bind_poses) {
+	if (p_bind_poses.size() != 0) {
+		WARN_PRINT_ONCE("The OpenGL 3 renderer does not support particle trails");
+	}
 }
 
 void ParticlesStorage::particles_set_collision_base_size(RID p_particles, real_t p_size) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->collision_base_size = p_size;
 }
 
 void ParticlesStorage::particles_set_transform_align(RID p_particles, RS::ParticlesTransformAlign p_transform_align) {
-}
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
 
-void ParticlesStorage::particles_set_trails(RID p_particles, bool p_enable, double p_length) {
+	particles->transform_align = p_transform_align;
 }
 
-void ParticlesStorage::particles_set_trail_bind_poses(RID p_particles, const Vector<Transform3D> &p_bind_poses) {
+void ParticlesStorage::particles_set_process_material(RID p_particles, RID p_material) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->process_material = p_material;
+	particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES); //the instance buffer may have changed
 }
 
-void ParticlesStorage::particles_restart(RID p_particles) {
+RID ParticlesStorage::particles_get_process_material(RID p_particles) const {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND_V(!particles, RID());
+
+	return particles->process_material;
 }
 
 void ParticlesStorage::particles_set_draw_order(RID p_particles, RS::ParticlesDrawOrder p_order) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->draw_order = p_order;
 }
 
-void ParticlesStorage::particles_set_draw_passes(RID p_particles, int p_count) {
+void ParticlesStorage::particles_set_draw_passes(RID p_particles, int p_passes) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->draw_passes.resize(p_passes);
 }
 
 void ParticlesStorage::particles_set_draw_pass_mesh(RID p_particles, int p_pass, RID p_mesh) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	ERR_FAIL_INDEX(p_pass, particles->draw_passes.size());
+	particles->draw_passes.write[p_pass] = p_mesh;
+	particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES);
+}
+
+void ParticlesStorage::particles_restart(RID p_particles) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	particles->restart_request = true;
+}
+
+void ParticlesStorage::particles_set_subemitter(RID p_particles, RID p_subemitter_particles) {
+	if (p_subemitter_particles.is_valid()) {
+		WARN_PRINT_ONCE("The OpenGL 3 renderer does not support particle sub emitters");
+	}
+}
+
+void ParticlesStorage::particles_emit(RID p_particles, const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
+	WARN_PRINT_ONCE("The OpenGL 3 renderer does not support manually emitting particles");
 }
 
 void ParticlesStorage::particles_request_process(RID p_particles) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	if (!particles->dirty) {
+		particles->dirty = true;
+		particles->update_list = particle_update_list;
+		particle_update_list = particles;
+	}
 }
 
 AABB ParticlesStorage::particles_get_current_aabb(RID p_particles) {
-	return AABB();
+	if (RSG::threaded) {
+		WARN_PRINT_ONCE("Calling this function with threaded rendering enabled stalls the renderer, use with care.");
+	}
+
+	const Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND_V(!particles, AABB());
+
+	int total_amount = particles->amount;
+
+	// If available, read from the sort buffer which should be 2 frames out of date.
+	// This will help alleviate GPU stalls.
+	GLuint read_buffer = particles->sort_buffer_filled ? particles->sort_buffer : particles->back_instance_buffer;
+
+	Vector<uint8_t> buffer = Utilities::buffer_get_data(GL_ARRAY_BUFFER, read_buffer, total_amount * sizeof(ParticleInstanceData3D));
+	ERR_FAIL_COND_V(buffer.size() != (int)(total_amount * sizeof(ParticleInstanceData3D)), AABB());
+
+	Transform3D inv = particles->emission_transform.affine_inverse();
+
+	AABB aabb;
+	if (buffer.size()) {
+		bool first = true;
+
+		const uint8_t *data_ptr = (const uint8_t *)buffer.ptr();
+		uint32_t particle_data_size = sizeof(ParticleInstanceData3D) + sizeof(float) * particles->userdata_count;
+
+		for (int i = 0; i < total_amount; i++) {
+			const ParticleInstanceData3D &particle_data = *(const ParticleInstanceData3D *)&data_ptr[particle_data_size * i];
+			// If scale is 0.0, we assume the particle is inactive.
+			if (particle_data.xform[0] > 0.0) {
+				Vector3 pos = Vector3(particle_data.xform[3], particle_data.xform[7], particle_data.xform[11]);
+				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 = MeshStorage::get_singleton()->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 ParticlesStorage::particles_get_aabb(RID p_particles) const {
-	return AABB();
+	const Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND_V(!particles, AABB());
+
+	return particles->custom_aabb;
 }
 
 void ParticlesStorage::particles_set_emission_transform(RID p_particles, const Transform3D &p_transform) {
-}
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
 
-bool ParticlesStorage::particles_get_emitting(RID p_particles) {
-	return false;
+	particles->emission_transform = p_transform;
 }
 
 int ParticlesStorage::particles_get_draw_passes(RID p_particles) const {
-	return 0;
-}
+	const Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND_V(!particles, 0);
 
-RID ParticlesStorage::particles_get_draw_pass_mesh(RID p_particles, int p_pass) const {
-	return RID();
+	return particles->draw_passes.size();
 }
 
-void ParticlesStorage::particles_add_collision(RID p_particles, RID p_instance) {
+RID ParticlesStorage::particles_get_draw_pass_mesh(RID p_particles, int p_pass) const {
+	const Particles *particles = particles_owner.get_or_null(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 ParticlesStorage::particles_remove_collision(RID p_particles, RID p_instance) {
+void ParticlesStorage::particles_add_collision(RID p_particles, RID p_particles_collision_instance) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->collisions.insert(p_particles_collision_instance);
+}
+
+void ParticlesStorage::particles_remove_collision(RID p_particles, RID p_particles_collision_instance) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->collisions.erase(p_particles_collision_instance);
+}
+
+void ParticlesStorage::particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, GLuint p_texture) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+	particles->has_sdf_collision = p_enable;
+	particles->sdf_collision_transform = p_xform;
+	particles->sdf_collision_to_screen = p_to_screen;
+	particles->sdf_collision_texture = p_texture;
+}
+
+// Does one step of processing particles by reading from back_process_buffer and writing to front_process_buffer.
+void ParticlesStorage::_particles_process(Particles *p_particles, double p_delta) {
+	GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
+	GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
+
+	double new_phase = Math::fmod(p_particles->phase + (p_delta / p_particles->lifetime) * p_particles->speed_scale, 1.0);
+
+	//update current frame
+	ParticlesFrameParams 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 = RSG::rasterizer->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) {
+		GLES3::MaterialStorage::store_transform(Transform3D(), frame_params.emission_transform);
+	} else {
+		GLES3::MaterialStorage::store_transform(p_particles->emission_transform, frame_params.emission_transform);
+	}
+
+	frame_params.cycle = p_particles->cycle_number;
+	frame_params.frame = p_particles->frame_counter++;
+	frame_params.pad0 = 0;
+	frame_params.pad1 = 0;
+	frame_params.pad2 = 0;
+
+	{ //collision and attractors
+
+		frame_params.collider_count = 0;
+		frame_params.attractor_count = 0;
+		frame_params.particle_size = p_particles->collision_base_size;
+
+		GLuint collision_heightmap_texture = 0;
+
+		Transform3D to_particles;
+		if (p_particles->use_local_coords) {
+			to_particles = p_particles->emission_transform.affine_inverse();
+		}
+
+		if (p_particles->has_sdf_collision && p_particles->sdf_collision_texture != 0) {
+			//2D collision
+
+			Transform2D xform = p_particles->sdf_collision_transform; //will use dotproduct manually so invert beforehand
+			Transform2D revert = xform.affine_inverse();
+			frame_params.collider_count = 1;
+			frame_params.colliders[0].transform[0] = xform.columns[0][0];
+			frame_params.colliders[0].transform[1] = xform.columns[0][1];
+			frame_params.colliders[0].transform[2] = 0;
+			frame_params.colliders[0].transform[3] = xform.columns[2][0];
+
+			frame_params.colliders[0].transform[4] = xform.columns[1][0];
+			frame_params.colliders[0].transform[5] = xform.columns[1][1];
+			frame_params.colliders[0].transform[6] = 0;
+			frame_params.colliders[0].transform[7] = xform.columns[2][1];
+
+			frame_params.colliders[0].transform[8] = revert.columns[0][0];
+			frame_params.colliders[0].transform[9] = revert.columns[0][1];
+			frame_params.colliders[0].transform[10] = 0;
+			frame_params.colliders[0].transform[11] = revert.columns[2][0];
+
+			frame_params.colliders[0].transform[12] = revert.columns[1][0];
+			frame_params.colliders[0].transform[13] = revert.columns[1][1];
+			frame_params.colliders[0].transform[14] = 0;
+			frame_params.colliders[0].transform[15] = revert.columns[2][1];
+
+			frame_params.colliders[0].extents[0] = p_particles->sdf_collision_to_screen.size.x;
+			frame_params.colliders[0].extents[1] = p_particles->sdf_collision_to_screen.size.y;
+			frame_params.colliders[0].extents[2] = p_particles->sdf_collision_to_screen.position.x;
+			frame_params.colliders[0].scale = p_particles->sdf_collision_to_screen.position.y;
+			frame_params.colliders[0].type = ParticlesFrameParams::COLLISION_TYPE_2D_SDF;
+
+			collision_heightmap_texture = p_particles->sdf_collision_texture;
+		}
+
+		for (const RID &E : p_particles->collisions) {
+			ParticlesCollisionInstance *pci = particles_collision_instance_owner.get_or_null(E);
+			if (!pci || !pci->active) {
+				continue;
+			}
+			ParticlesCollision *pc = particles_collision_owner.get_or_null(pci->collision);
+			ERR_CONTINUE(!pc);
+
+			Transform3D to_collider = pci->transform;
+			if (p_particles->use_local_coords) {
+				to_collider = to_particles * to_collider;
+			}
+			Vector3 scale = to_collider.basis.get_scale();
+			to_collider.basis.orthonormalize();
+
+			if (pc->type <= RS::PARTICLES_COLLISION_TYPE_VECTOR_FIELD_ATTRACT) {
+				//attractor
+				if (frame_params.attractor_count >= ParticlesFrameParams::MAX_ATTRACTORS) {
+					continue;
+				}
+
+				ParticlesFrameParams::Attractor &attr = frame_params.attractors[frame_params.attractor_count];
+
+				GLES3::MaterialStorage::store_transform(to_collider, attr.transform);
+				attr.strength = pc->attractor_strength;
+				attr.attenuation = pc->attractor_attenuation;
+				attr.directionality = pc->attractor_directionality;
+
+				switch (pc->type) {
+					case RS::PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT: {
+						attr.type = ParticlesFrameParams::ATTRACTOR_TYPE_SPHERE;
+						float radius = pc->radius;
+						radius *= (scale.x + scale.y + scale.z) / 3.0;
+						attr.extents[0] = radius;
+						attr.extents[1] = radius;
+						attr.extents[2] = radius;
+					} break;
+					case RS::PARTICLES_COLLISION_TYPE_BOX_ATTRACT: {
+						attr.type = ParticlesFrameParams::ATTRACTOR_TYPE_BOX;
+						Vector3 extents = pc->extents * scale;
+						attr.extents[0] = extents.x;
+						attr.extents[1] = extents.y;
+						attr.extents[2] = extents.z;
+					} break;
+					case RS::PARTICLES_COLLISION_TYPE_VECTOR_FIELD_ATTRACT: {
+						WARN_PRINT_ONCE("Vector field particle attractors are not available in the OpenGL2 renderer.");
+					} break;
+					default: {
+					}
+				}
+
+				frame_params.attractor_count++;
+			} else {
+				//collider
+				if (frame_params.collider_count >= ParticlesFrameParams::MAX_COLLIDERS) {
+					continue;
+				}
+
+				ParticlesFrameParams::Collider &col = frame_params.colliders[frame_params.collider_count];
+
+				GLES3::MaterialStorage::store_transform(to_collider, col.transform);
+				switch (pc->type) {
+					case RS::PARTICLES_COLLISION_TYPE_SPHERE_COLLIDE: {
+						col.type = ParticlesFrameParams::COLLISION_TYPE_SPHERE;
+						float radius = pc->radius;
+						radius *= (scale.x + scale.y + scale.z) / 3.0;
+						col.extents[0] = radius;
+						col.extents[1] = radius;
+						col.extents[2] = radius;
+					} break;
+					case RS::PARTICLES_COLLISION_TYPE_BOX_COLLIDE: {
+						col.type = ParticlesFrameParams::COLLISION_TYPE_BOX;
+						Vector3 extents = pc->extents * scale;
+						col.extents[0] = extents.x;
+						col.extents[1] = extents.y;
+						col.extents[2] = extents.z;
+					} break;
+					case RS::PARTICLES_COLLISION_TYPE_SDF_COLLIDE: {
+						WARN_PRINT_ONCE("SDF Particle Colliders are not available in the OpenGL 3 renderer.");
+					} break;
+					case RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE: {
+						if (collision_heightmap_texture != 0) { //already taken
+							continue;
+						}
+
+						col.type = ParticlesFrameParams::COLLISION_TYPE_HEIGHT_FIELD;
+						Vector3 extents = pc->extents * scale;
+						col.extents[0] = extents.x;
+						col.extents[1] = extents.y;
+						col.extents[2] = extents.z;
+						collision_heightmap_texture = pc->heightfield_texture;
+					} break;
+					default: {
+					}
+				}
+
+				frame_params.collider_count++;
+			}
+		}
+
+		// Bind heightmap or SDF texture.
+		GLuint heightmap = collision_heightmap_texture;
+		if (heightmap == 0) {
+			GLES3::Texture *tex = texture_storage->get_texture(texture_storage->texture_gl_get_default(GLES3::DEFAULT_GL_TEXTURE_BLACK));
+			heightmap = tex->tex_id;
+		}
+		glActiveTexture(GL_TEXTURE0);
+		glBindTexture(GL_TEXTURE_2D, heightmap);
+	}
+
+	if (p_particles->frame_params_ubo == 0) {
+		glGenBuffers(1, &p_particles->frame_params_ubo);
+	}
+	// Update per-frame UBO.
+	glBindBufferBase(GL_UNIFORM_BUFFER, PARTICLES_FRAME_UNIFORM_LOCATION, p_particles->frame_params_ubo);
+	glBufferData(GL_UNIFORM_BUFFER, sizeof(ParticlesFrameParams), &frame_params, GL_STREAM_DRAW);
+
+	// Get shader and set shader uniforms;
+	ParticleProcessMaterialData *m = static_cast<ParticleProcessMaterialData *>(material_storage->material_get_data(p_particles->process_material, RS::SHADER_PARTICLES));
+	if (!m) {
+		m = static_cast<ParticleProcessMaterialData *>(material_storage->material_get_data(particles_shader.default_material, RS::SHADER_PARTICLES));
+	}
+
+	ERR_FAIL_COND(!m);
+
+	ParticlesShaderGLES3::ShaderVariant variant = ParticlesShaderGLES3::MODE_DEFAULT;
+
+	uint32_t specialization = 0;
+	for (uint32_t i = 0; i < p_particles->userdata_count; i++) {
+		specialization |= (1 << i);
+	}
+
+	if (p_particles->mode == RS::ParticlesMode::PARTICLES_MODE_3D) {
+		specialization |= ParticlesShaderGLES3::MODE_3D;
+	}
+
+	RID version = particles_shader.default_shader_version;
+	if (m->shader_data->version.is_valid() && m->shader_data->valid) {
+		// Bind material uniform buffer and textures.
+		m->bind_uniforms();
+		version = m->shader_data->version;
+	}
+
+	bool success = material_storage->shaders.particles_process_shader.version_bind_shader(version, variant, specialization);
+	if (!success) {
+		return;
+	}
+
+	material_storage->shaders.particles_process_shader.version_set_uniform(ParticlesShaderGLES3::LIFETIME, p_particles->lifetime, version, variant, specialization);
+	material_storage->shaders.particles_process_shader.version_set_uniform(ParticlesShaderGLES3::CLEAR, p_particles->clear, version, variant, specialization);
+	material_storage->shaders.particles_process_shader.version_set_uniform(ParticlesShaderGLES3::TOTAL_PARTICLES, uint32_t(p_particles->amount), version, variant, specialization);
+	material_storage->shaders.particles_process_shader.version_set_uniform(ParticlesShaderGLES3::USE_FRACTIONAL_DELTA, p_particles->fractional_delta, version, variant, specialization);
+
+	p_particles->clear = false;
+
+	p_particles->has_collision_cache = m->shader_data->uses_collision;
+
+	glBindVertexArray(p_particles->back_vertex_array);
+
+	glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, p_particles->front_process_buffer);
+
+	glBeginTransformFeedback(GL_POINTS);
+	glDrawArrays(GL_POINTS, 0, p_particles->amount);
+	glEndTransformFeedback();
+
+	glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
+	glBindVertexArray(0);
+
+	SWAP(p_particles->front_process_buffer, p_particles->back_process_buffer);
+	SWAP(p_particles->front_vertex_array, p_particles->back_vertex_array);
 }
 
-void ParticlesStorage::particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, RID p_texture) {
+void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p_axis, const Vector3 &p_up_axis) {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND(!particles);
+
+	if (particles->draw_order != RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY) {
+		return;
+	}
+
+	if (particles->front_process_buffer == 0) {
+		return; //particles have not processed yet
+	}
+
+	Vector3 axis = -p_axis; // cameras look to z negative
+
+	if (particles->use_local_coords) {
+		axis = particles->emission_transform.basis.xform_inv(axis).normalized();
+	}
+
+	// Sort will be done on CPU since we don't have compute shaders.
+	// If the sort_buffer has valid data
+	// Use a buffer that is 2 frames out of date to avoid stalls.
+	if (particles->draw_order == RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH && particles->sort_buffer_filled) {
+		glBindBuffer(GL_ARRAY_BUFFER, particles->sort_buffer);
+
+		ParticleInstanceData3D *particle_array;
+#ifndef __EMSCRIPTEN__
+		particle_array = static_cast<ParticleInstanceData3D *>(glMapBufferRange(GL_ARRAY_BUFFER, 0, particles->amount * sizeof(ParticleInstanceData3D), GL_MAP_READ_BIT | GL_MAP_WRITE_BIT));
+		ERR_FAIL_NULL(particle_array);
+#else
+		LocalVector<ParticleInstanceData3D> particle_vector;
+		particle_vector.resize(particles->amount);
+		particle_array = particle_vector.ptr();
+		glGetBufferSubData(GL_ARRAY_BUFFER, 0, particles->amount * sizeof(ParticleInstanceData3D), particle_array);
+#endif
+		SortArray<ParticleInstanceData3D, ParticlesViewSort> sorter;
+		sorter.compare.z_dir = axis;
+		sorter.sort(particle_array, particles->amount);
+
+#ifndef __EMSCRIPTEN__
+		glUnmapBuffer(GL_ARRAY_BUFFER);
+#else
+		glBufferSubData(GL_ARRAY_BUFFER, 0, particles->amount * sizeof(ParticleInstanceData3D), particle_vector.ptr());
+#endif
+	}
+
+	glEnable(GL_RASTERIZER_DISCARD);
+	_particles_update_instance_buffer(particles, axis, p_up_axis);
+	glDisable(GL_RASTERIZER_DISCARD);
+}
+
+void ParticlesStorage::_particles_update_buffers(Particles *particles) {
+	GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
+	uint32_t userdata_count = 0;
+
+	if (particles->process_material.is_valid()) {
+		GLES3::ParticleProcessMaterialData *material_data = static_cast<GLES3::ParticleProcessMaterialData *>(material_storage->material_get_data(particles->process_material, RS::SHADER_PARTICLES));
+		if (material_data && material_data->shader_data->version.is_valid() && material_data->shader_data->valid) {
+			userdata_count = material_data->shader_data->userdata_count;
+		}
+	}
+
+	if (userdata_count != particles->userdata_count) {
+		// Mismatch userdata, re-create buffers.
+		_particles_free_data(particles);
+	}
+
+	if (particles->amount > 0 && particles->front_process_buffer == 0) {
+		int total_amount = particles->amount;
+
+		particles->userdata_count = userdata_count;
+
+		uint32_t xform_size = particles->mode == RS::PARTICLES_MODE_2D ? 2 : 3;
+		particles->instance_buffer_stride_cache = sizeof(float) * 4 * (xform_size + 1);
+		particles->instance_buffer_size_cache = particles->instance_buffer_stride_cache * total_amount;
+		particles->num_attrib_arrays_cache = 5 + userdata_count + (xform_size - 2);
+		particles->process_buffer_stride_cache = sizeof(float) * 4 * particles->num_attrib_arrays_cache;
+
+		int process_data_amount = 4 * particles->num_attrib_arrays_cache * total_amount;
+		float *data = memnew_arr(float, process_data_amount);
+
+		for (int i = 0; i < process_data_amount; i++) {
+			data[i] = 0;
+		}
+
+		{
+			glGenVertexArrays(1, &particles->front_vertex_array);
+			glBindVertexArray(particles->front_vertex_array);
+			glGenBuffers(1, &particles->front_process_buffer);
+			glGenBuffers(1, &particles->front_instance_buffer);
+
+			glBindBuffer(GL_ARRAY_BUFFER, particles->front_process_buffer);
+			glBufferData(GL_ARRAY_BUFFER, particles->process_buffer_stride_cache * total_amount, data, GL_DYNAMIC_COPY);
+
+			for (uint32_t j = 0; j < particles->num_attrib_arrays_cache; j++) {
+				glEnableVertexAttribArray(j);
+				glVertexAttribPointer(j, 4, GL_FLOAT, GL_FALSE, particles->process_buffer_stride_cache, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * j));
+			}
+			glBindVertexArray(0);
+
+			glBindBuffer(GL_ARRAY_BUFFER, particles->front_instance_buffer);
+			glBufferData(GL_ARRAY_BUFFER, particles->instance_buffer_size_cache, nullptr, GL_DYNAMIC_COPY);
+		}
+
+		{
+			glGenVertexArrays(1, &particles->back_vertex_array);
+			glBindVertexArray(particles->back_vertex_array);
+			glGenBuffers(1, &particles->back_process_buffer);
+			glGenBuffers(1, &particles->back_instance_buffer);
+
+			glBindBuffer(GL_ARRAY_BUFFER, particles->back_process_buffer);
+			glBufferData(GL_ARRAY_BUFFER, particles->process_buffer_stride_cache * total_amount, data, GL_DYNAMIC_COPY);
+
+			for (uint32_t j = 0; j < particles->num_attrib_arrays_cache; j++) {
+				glEnableVertexAttribArray(j);
+				glVertexAttribPointer(j, 4, GL_FLOAT, GL_FALSE, particles->process_buffer_stride_cache, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * j));
+			}
+			glBindVertexArray(0);
+
+			glBindBuffer(GL_ARRAY_BUFFER, particles->back_instance_buffer);
+			glBufferData(GL_ARRAY_BUFFER, particles->instance_buffer_size_cache, nullptr, GL_DYNAMIC_COPY);
+		}
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+		memdelete_arr(data);
+	}
+}
+
+void ParticlesStorage::_particles_allocate_history_buffers(Particles *particles) {
+	if (particles->sort_buffer == 0) {
+		glGenBuffers(1, &particles->last_frame_buffer);
+		glBindBuffer(GL_ARRAY_BUFFER, particles->last_frame_buffer);
+		glBufferData(GL_ARRAY_BUFFER, particles->instance_buffer_size_cache, nullptr, GL_DYNAMIC_READ);
+
+		glGenBuffers(1, &particles->sort_buffer);
+		glBindBuffer(GL_ARRAY_BUFFER, particles->sort_buffer);
+		glBufferData(GL_ARRAY_BUFFER, particles->instance_buffer_size_cache, nullptr, GL_DYNAMIC_READ);
+		particles->sort_buffer_filled = false;
+		particles->last_frame_buffer_filled = false;
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+}
+void ParticlesStorage::_particles_update_instance_buffer(Particles *particles, const Vector3 &p_axis, const Vector3 &p_up_axis) {
+	ParticlesCopyShaderGLES3::ShaderVariant variant = ParticlesCopyShaderGLES3::MODE_DEFAULT;
+
+	uint64_t specialization = 0;
+	if (particles->mode == RS::ParticlesMode::PARTICLES_MODE_3D) {
+		specialization |= ParticlesCopyShaderGLES3::MODE_3D;
+	}
+
+	bool success = particles_shader.copy_shader.version_bind_shader(particles_shader.copy_shader_version, variant, specialization);
+	if (!success) {
+		return;
+	}
+
+	// Affect 2D only.
+	if (particles->use_local_coords) {
+		// In local mode, particle positions are calculated locally (relative to the node position)
+		// and they're also drawn locally.
+		// It works as expected, so we just pass an identity transform.
+		particles_shader.copy_shader.version_set_uniform(ParticlesCopyShaderGLES3::INV_EMISSION_TRANSFORM, Transform3D(), particles_shader.copy_shader_version, variant, specialization);
+	} else {
+		// In global mode, particle positions are calculated globally (relative to the canvas origin)
+		// but they're drawn locally.
+		// So, we need to pass the inverse of the emission transform to bring the
+		// particles to local coordinates before drawing.
+		Transform3D inv = particles->emission_transform.affine_inverse();
+		particles_shader.copy_shader.version_set_uniform(ParticlesCopyShaderGLES3::INV_EMISSION_TRANSFORM, inv, particles_shader.copy_shader_version, variant, specialization);
+	}
+
+	particles_shader.copy_shader.version_set_uniform(ParticlesCopyShaderGLES3::FRAME_REMAINDER, particles->interpolate ? particles->frame_remainder : 0.0, particles_shader.copy_shader_version, variant, specialization);
+	particles_shader.copy_shader.version_set_uniform(ParticlesCopyShaderGLES3::ALIGN_MODE, uint32_t(particles->transform_align), particles_shader.copy_shader_version, variant, specialization);
+	particles_shader.copy_shader.version_set_uniform(ParticlesCopyShaderGLES3::ALIGN_UP, p_up_axis, particles_shader.copy_shader_version, variant, specialization);
+	particles_shader.copy_shader.version_set_uniform(ParticlesCopyShaderGLES3::SORT_DIRECTION, p_axis, particles_shader.copy_shader_version, variant, specialization);
+
+	glBindVertexArray(particles->back_vertex_array);
+	glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, particles->front_instance_buffer, 0, particles->instance_buffer_size_cache);
+	glBeginTransformFeedback(GL_POINTS);
+
+	if (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME) {
+		uint32_t lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1);
+		uint32_t stride = particles->process_buffer_stride_cache;
+
+		glBindBuffer(GL_ARRAY_BUFFER, particles->back_process_buffer);
+
+		// Offset VBO so you render starting at the newest particle.
+		if (particles->amount - lifetime_split > 0) {
+			glEnableVertexAttribArray(0); // Color.
+			glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * lifetime_split + sizeof(float) * 4 * 0));
+			glEnableVertexAttribArray(1); // .xyz: velocity. .z: flags.
+			glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * lifetime_split + sizeof(float) * 4 * 1));
+			glEnableVertexAttribArray(2); // Custom.
+			glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * lifetime_split + sizeof(float) * 4 * 2));
+			glEnableVertexAttribArray(3); // Xform1.
+			glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * lifetime_split + sizeof(float) * 4 * 3));
+			glEnableVertexAttribArray(4); // Xform2.
+			glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * lifetime_split + sizeof(float) * 4 * 4));
+			if (particles->mode == RS::PARTICLES_MODE_3D) {
+				glEnableVertexAttribArray(5); // Xform3.
+				glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * lifetime_split + sizeof(float) * 4 * 5));
+			}
+
+			uint32_t to_draw = particles->amount - lifetime_split;
+			glDrawArrays(GL_POINTS, 0, to_draw);
+		}
+
+		// Then render from index 0 up intil the newest particle.
+		if (lifetime_split > 0) {
+			glEndTransformFeedback();
+			// Now output to the second portion of the instance buffer.
+			glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, particles->front_instance_buffer, particles->instance_buffer_stride_cache * (particles->amount - lifetime_split), particles->instance_buffer_stride_cache * (lifetime_split));
+			glBeginTransformFeedback(GL_POINTS);
+			// Reset back to normal.
+			for (uint32_t j = 0; j < particles->num_attrib_arrays_cache; j++) {
+				glEnableVertexAttribArray(j);
+				glVertexAttribPointer(j, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * j));
+			}
+
+			glDrawArrays(GL_POINTS, 0, lifetime_split);
+		}
+	} else {
+		glDrawArrays(GL_POINTS, 0, particles->amount);
+	}
+
+	glEndTransformFeedback();
+	glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0, 0, 0);
+	glBindVertexArray(0);
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
 }
 
 void ParticlesStorage::update_particles() {
+	glEnable(GL_RASTERIZER_DISCARD);
+
+	GLuint global_buffer = GLES3::MaterialStorage::get_singleton()->global_shader_parameters_get_uniform_buffer();
+
+	glBindBufferBase(GL_UNIFORM_BUFFER, PARTICLES_GLOBALS_UNIFORM_LOCATION, global_buffer);
+	glBindBuffer(GL_UNIFORM_BUFFER, 0);
+
+	while (particle_update_list) {
+		// Use transform feedback to process particles.
+
+		Particles *particles = particle_update_list;
+
+		particle_update_list = particles->update_list;
+		particles->update_list = nullptr;
+		particles->dirty = false;
+
+		_particles_update_buffers(particles);
+
+		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 * RSG::rasterizer->get_frame_delta_time();
+			if (particles->inactive_time > particles->lifetime * 1.2) {
+				particles->inactive = true;
+				continue;
+			}
+		}
+
+		// Copy the instance buffer that was last used into the last_frame buffer.
+		// sort_buffer should now be 2 frames out of date.
+		if (particles->draw_order == RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME) {
+			_particles_allocate_history_buffers(particles);
+			SWAP(particles->last_frame_buffer, particles->sort_buffer);
+
+			glBindBuffer(GL_COPY_READ_BUFFER, particles->back_instance_buffer);
+			glBindBuffer(GL_COPY_WRITE_BUFFER, particles->last_frame_buffer);
+			glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, particles->instance_buffer_size_cache);
+
+			// Last frame's last_frame turned into this frame's sort buffer.
+			particles->sort_buffer_filled = particles->last_frame_buffer_filled;
+			particles->sort_buffer_phase = particles->last_frame_phase;
+			particles->last_frame_buffer_filled = true;
+			particles->last_frame_phase = particles->phase;
+			glBindBuffer(GL_COPY_READ_BUFFER, 0);
+			glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
+		}
+
+		int fixed_fps = 0;
+		if (particles->fixed_fps > 0) {
+			fixed_fps = particles->fixed_fps;
+		}
+
+		bool zero_time_scale = Engine::get_singleton()->get_time_scale() <= 0.0;
+
+		if (particles->clear && particles->pre_process_time > 0.0) {
+			double frame_time;
+			if (fixed_fps > 0) {
+				frame_time = 1.0 / fixed_fps;
+			} else {
+				frame_time = 1.0 / 30.0;
+			}
+
+			double todo = particles->pre_process_time;
+
+			while (todo >= 0) {
+				_particles_process(particles, frame_time);
+				todo -= frame_time;
+			}
+		}
+
+		if (fixed_fps > 0) {
+			double frame_time;
+			double decr;
+			if (zero_time_scale) {
+				frame_time = 0.0;
+				decr = 1.0 / fixed_fps;
+			} else {
+				frame_time = 1.0 / fixed_fps;
+				decr = frame_time;
+			}
+			double delta = RSG::rasterizer->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;
+			}
+			double 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, RSG::rasterizer->get_frame_delta_time());
+			}
+		}
+
+		// Copy particles to instance buffer and pack Color/Custom.
+		// We don't have camera information here, so don't copy here if we need camera information for view depth or align mode.
+		if (particles->draw_order != RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY) {
+			_particles_update_instance_buffer(particles, Vector3(0.0, 0.0, 0.0), Vector3(0.0, 0.0, 0.0));
+
+			if (particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME && particles->sort_buffer_filled) {
+				if (particles->mode == RS::ParticlesMode::PARTICLES_MODE_2D) {
+					_particles_reverse_lifetime_sort<ParticleInstanceData2D>(particles);
+				} else {
+					_particles_reverse_lifetime_sort<ParticleInstanceData3D>(particles);
+				}
+			}
+		}
+
+		SWAP(particles->front_instance_buffer, particles->back_instance_buffer);
+
+		// At the end of update, the back_buffer contains the most up-to-date-information to read from.
+
+		particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
+	}
+
+	glDisable(GL_RASTERIZER_DISCARD);
+}
+
+template <typename ParticleInstanceData>
+void ParticlesStorage::_particles_reverse_lifetime_sort(Particles *particles) {
+	glBindBuffer(GL_ARRAY_BUFFER, particles->sort_buffer);
+
+	ParticleInstanceData *particle_array;
+	uint32_t buffer_size = particles->amount * sizeof(ParticleInstanceData);
+#ifndef __EMSCRIPTEN__
+	particle_array = static_cast<ParticleInstanceData *>(glMapBufferRange(GL_ARRAY_BUFFER, 0, buffer_size, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT));
+
+	ERR_FAIL_NULL(particle_array);
+#else
+	LocalVector<ParticleInstanceData> particle_vector;
+	particle_vector.resize(particles->amount);
+	particle_array = particle_vector.ptr();
+	glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, particle_array);
+#endif
+
+	uint32_t lifetime_split = MIN(particles->amount * particles->sort_buffer_phase, particles->amount - 1);
+
+	for (uint32_t i = 0; i < lifetime_split / 2; i++) {
+		SWAP(particle_array[i], particle_array[lifetime_split - i]);
+	}
+
+	for (uint32_t i = 0; i < (particles->amount - lifetime_split) / 2; i++) {
+		SWAP(particle_array[lifetime_split + i + 1], particle_array[particles->amount - 1 - i]);
+	}
+
+#ifndef __EMSCRIPTEN__
+	glUnmapBuffer(GL_ARRAY_BUFFER);
+#else
+	glBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, particle_vector.ptr());
+#endif
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+Dependency *ParticlesStorage::particles_get_dependency(RID p_particles) const {
+	Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_NULL_V(particles, nullptr);
+
+	return &particles->dependency;
 }
 
 bool ParticlesStorage::particles_is_inactive(RID p_particles) const {
-	return false;
+	ERR_FAIL_COND_V_MSG(RSG::threaded, false, "This function should never be used with threaded rendering, as it stalls the renderer.");
+	const Particles *particles = particles_owner.get_or_null(p_particles);
+	ERR_FAIL_COND_V(!particles, false);
+	return !particles->emitting && particles->inactive;
 }
 
-/* PARTICLES COLLISION */
+/* PARTICLES COLLISION API */
 
 RID ParticlesStorage::particles_collision_allocate() {
-	return RID();
+	return particles_collision_owner.allocate_rid();
 }
-
 void ParticlesStorage::particles_collision_initialize(RID p_rid) {
+	particles_collision_owner.initialize_rid(p_rid, ParticlesCollision());
 }
 
 void ParticlesStorage::particles_collision_free(RID p_rid) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_rid);
+
+	if (particles_collision->heightfield_texture != 0) {
+		glDeleteTextures(1, &particles_collision->heightfield_texture);
+		particles_collision->heightfield_texture = 0;
+		glDeleteFramebuffers(1, &particles_collision->heightfield_fb);
+		particles_collision->heightfield_fb = 0;
+	}
+	particles_collision->dependency.deleted_notify(p_rid);
+	particles_collision_owner.free(p_rid);
+}
+
+GLuint ParticlesStorage::particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND_V(!particles_collision, 0);
+	ERR_FAIL_COND_V(particles_collision->type != RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE, 0);
+
+	if (particles_collision->heightfield_texture == 0) {
+		//create
+		const int resolutions[RS::PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_MAX] = { 256, 512, 1024, 2048, 4096, 8192 };
+		Size2i size;
+		if (particles_collision->extents.x > particles_collision->extents.z) {
+			size.x = resolutions[particles_collision->heightfield_resolution];
+			size.y = int32_t(particles_collision->extents.z / particles_collision->extents.x * size.x);
+		} else {
+			size.y = resolutions[particles_collision->heightfield_resolution];
+			size.x = int32_t(particles_collision->extents.x / particles_collision->extents.z * size.y);
+		}
+
+		glGenTextures(1, &particles_collision->heightfield_texture);
+		glActiveTexture(GL_TEXTURE0);
+		glBindTexture(GL_TEXTURE_2D, particles_collision->heightfield_texture);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, size.x, size.y, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
+
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+
+		glGenFramebuffers(1, &particles_collision->heightfield_fb);
+		glBindFramebuffer(GL_FRAMEBUFFER, particles_collision->heightfield_fb);
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, particles_collision->heightfield_texture, 0);
+#ifdef DEBUG_ENABLED
+		GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+		if (status != GL_FRAMEBUFFER_COMPLETE) {
+			WARN_PRINT("Could create heightmap texture status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status));
+		}
+#endif
+		particles_collision->heightfield_fb_size = size;
+
+		glBindTexture(GL_TEXTURE_2D, 0);
+		glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	}
+
+	return particles_collision->heightfield_fb;
 }
 
 void ParticlesStorage::particles_collision_set_collision_type(RID p_particles_collision, RS::ParticlesCollisionType p_type) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+
+	if (p_type == particles_collision->type) {
+		return;
+	}
+
+	if (particles_collision->heightfield_texture != 0) {
+		glDeleteTextures(1, &particles_collision->heightfield_texture);
+		particles_collision->heightfield_texture = 0;
+		glDeleteFramebuffers(1, &particles_collision->heightfield_fb);
+		particles_collision->heightfield_fb = 0;
+	}
+
+	particles_collision->type = p_type;
+	particles_collision->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
 }
 
 void ParticlesStorage::particles_collision_set_cull_mask(RID p_particles_collision, uint32_t p_cull_mask) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+	particles_collision->cull_mask = p_cull_mask;
 }
 
 void ParticlesStorage::particles_collision_set_sphere_radius(RID p_particles_collision, real_t p_radius) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+
+	particles_collision->radius = p_radius;
+	particles_collision->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
 }
 
 void ParticlesStorage::particles_collision_set_box_extents(RID p_particles_collision, const Vector3 &p_extents) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+
+	particles_collision->extents = p_extents;
+	particles_collision->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
 }
 
 void ParticlesStorage::particles_collision_set_attractor_strength(RID p_particles_collision, real_t p_strength) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+
+	particles_collision->attractor_strength = p_strength;
 }
 
 void ParticlesStorage::particles_collision_set_attractor_directionality(RID p_particles_collision, real_t p_directionality) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+
+	particles_collision->attractor_directionality = p_directionality;
 }
 
 void ParticlesStorage::particles_collision_set_attractor_attenuation(RID p_particles_collision, real_t p_curve) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+
+	particles_collision->attractor_attenuation = p_curve;
 }
 
 void ParticlesStorage::particles_collision_set_field_texture(RID p_particles_collision, RID p_texture) {
+	WARN_PRINT_ONCE("The OpenGL 3 renderer does not support SDF collisions in 3D particle shaders");
 }
 
 void ParticlesStorage::particles_collision_height_field_update(RID p_particles_collision) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+	particles_collision->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
 }
 
 void ParticlesStorage::particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND(!particles_collision);
+	ERR_FAIL_INDEX(p_resolution, RS::PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_MAX);
+
+	if (particles_collision->heightfield_resolution == p_resolution) {
+		return;
+	}
+
+	particles_collision->heightfield_resolution = p_resolution;
+
+	if (particles_collision->heightfield_texture != 0) {
+		glDeleteTextures(1, &particles_collision->heightfield_texture);
+		particles_collision->heightfield_texture = 0;
+		glDeleteFramebuffers(1, &particles_collision->heightfield_fb);
+		particles_collision->heightfield_fb = 0;
+	}
 }
 
 AABB ParticlesStorage::particles_collision_get_aabb(RID p_particles_collision) const {
-	return AABB();
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND_V(!particles_collision, AABB());
+
+	switch (particles_collision->type) {
+		case RS::PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT:
+		case RS::PARTICLES_COLLISION_TYPE_SPHERE_COLLIDE: {
+			AABB aabb;
+			aabb.position = -Vector3(1, 1, 1) * particles_collision->radius;
+			aabb.size = Vector3(2, 2, 2) * particles_collision->radius;
+			return aabb;
+		}
+		default: {
+			AABB aabb;
+			aabb.position = -particles_collision->extents;
+			aabb.size = particles_collision->extents * 2;
+			return aabb;
+		}
+	}
+}
+
+Vector3 ParticlesStorage::particles_collision_get_extents(RID p_particles_collision) const {
+	const ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND_V(!particles_collision, Vector3());
+	return particles_collision->extents;
 }
 
 bool ParticlesStorage::particles_collision_is_heightfield(RID p_particles_collision) const {
-	return false;
+	const ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_COND_V(!particles_collision, false);
+	return particles_collision->type == RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE;
 }
 
-RID ParticlesStorage::particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const {
-	return RID();
+Dependency *ParticlesStorage::particles_collision_get_dependency(RID p_particles_collision) const {
+	ParticlesCollision *pc = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_NULL_V(pc, nullptr);
+
+	return &pc->dependency;
 }
 
+/* Particles collision instance */
+
 RID ParticlesStorage::particles_collision_instance_create(RID p_collision) {
-	return RID();
+	ParticlesCollisionInstance pci;
+	pci.collision = p_collision;
+	return particles_collision_instance_owner.make_rid(pci);
 }
 
 void ParticlesStorage::particles_collision_instance_free(RID p_rid) {
+	particles_collision_instance_owner.free(p_rid);
 }
 
 void ParticlesStorage::particles_collision_instance_set_transform(RID p_collision_instance, const Transform3D &p_transform) {
+	ParticlesCollisionInstance *pci = particles_collision_instance_owner.get_or_null(p_collision_instance);
+	ERR_FAIL_COND(!pci);
+	pci->transform = p_transform;
 }
 
 void ParticlesStorage::particles_collision_instance_set_active(RID p_collision_instance, bool p_active) {
+	ParticlesCollisionInstance *pci = particles_collision_instance_owner.get_or_null(p_collision_instance);
+	ERR_FAIL_COND(!pci);
+	pci->active = p_active;
 }
 
 #endif // GLES3_ENABLED

+ 313 - 2
drivers/gles3/storage/particles_storage.h

@@ -33,25 +33,283 @@
 
 #ifdef GLES3_ENABLED
 
+#include "../shaders/particles_copy.glsl.gen.h"
 #include "core/templates/local_vector.h"
 #include "core/templates/rid_owner.h"
 #include "core/templates/self_list.h"
 #include "servers/rendering/storage/particles_storage.h"
+#include "servers/rendering/storage/utilities.h"
+
+#include "platform_config.h"
+#ifndef OPENGL_INCLUDE_H
+#include <GLES3/gl3.h>
+#else
+#include OPENGL_INCLUDE_H
+#endif
 
 namespace GLES3 {
 
+enum ParticlesUniformLocation {
+	PARTICLES_FRAME_UNIFORM_LOCATION,
+	PARTICLES_GLOBALS_UNIFORM_LOCATION,
+	PARTICLES_MATERIAL_UNIFORM_LOCATION,
+};
+
 class ParticlesStorage : public RendererParticlesStorage {
 private:
 	static ParticlesStorage *singleton;
 
+	/* PARTICLES */
+
+	struct ParticleInstanceData3D {
+		float xform[12];
+		float color[2]; // Color and custom are packed together into one vec4;
+		float custom[2];
+	};
+
+	struct ParticleInstanceData2D {
+		float xform[8];
+		float color[2]; // Color and custom are packed together into one vec4;
+		float custom[2];
+	};
+
+	struct ParticlesViewSort {
+		Vector3 z_dir;
+		bool operator()(const ParticleInstanceData3D &p_a, const ParticleInstanceData3D &p_b) const {
+			return z_dir.dot(Vector3(p_a.xform[3], p_a.xform[7], p_a.xform[11])) < z_dir.dot(Vector3(p_b.xform[3], p_b.xform[7], p_b.xform[11]));
+		}
+	};
+
+	struct ParticlesFrameParams {
+		enum {
+			MAX_ATTRACTORS = 32,
+			MAX_COLLIDERS = 32,
+			MAX_3D_TEXTURES = 0 // GLES3 renderer doesn't support using 3D textures for flow field or collisions.
+		};
+
+		enum AttractorType {
+			ATTRACTOR_TYPE_SPHERE,
+			ATTRACTOR_TYPE_BOX,
+			ATTRACTOR_TYPE_VECTOR_FIELD,
+		};
+
+		struct Attractor {
+			float transform[16];
+			float extents[4]; // Extents or radius. w-channel is padding.
+
+			uint32_t type;
+			float strength;
+			float attenuation;
+			float directionality;
+		};
+
+		enum CollisionType {
+			COLLISION_TYPE_SPHERE,
+			COLLISION_TYPE_BOX,
+			COLLISION_TYPE_SDF,
+			COLLISION_TYPE_HEIGHT_FIELD,
+			COLLISION_TYPE_2D_SDF,
+
+		};
+
+		struct Collider {
+			float transform[16];
+			float extents[4]; // Extents or radius. w-channel is padding.
+
+			uint32_t type;
+			float scale;
+			float pad0;
+			float pad1;
+		};
+
+		uint32_t emitting;
+		uint32_t cycle;
+		float system_phase;
+		float prev_system_phase;
+
+		float explosiveness;
+		float randomness;
+		float time;
+		float delta;
+
+		float particle_size;
+		float pad0;
+		float pad1;
+		float pad2;
+
+		uint32_t random_seed;
+		uint32_t attractor_count;
+		uint32_t collider_count;
+		uint32_t frame;
+
+		float emission_transform[16];
+
+		Attractor attractors[MAX_ATTRACTORS];
+		Collider colliders[MAX_COLLIDERS];
+	};
+
+	struct Particles {
+		RS::ParticlesMode mode = RS::PARTICLES_MODE_3D;
+		bool inactive = true;
+		double inactive_time = 0.0;
+		bool emitting = false;
+		bool one_shot = false;
+		int amount = 0;
+		double lifetime = 1.0;
+		double pre_process_time = 0.0;
+		real_t explosiveness = 0.0;
+		real_t randomness = 0.0;
+		bool restart_request = false;
+		AABB custom_aabb = AABB(Vector3(-4, -4, -4), Vector3(8, 8, 8));
+		bool use_local_coords = false;
+		bool has_collision_cache = false;
+
+		bool has_sdf_collision = false;
+		Transform2D sdf_collision_transform;
+		Rect2 sdf_collision_to_screen;
+		GLuint sdf_collision_texture = 0;
+
+		RID process_material;
+		uint32_t frame_counter = 0;
+		RS::ParticlesTransformAlign transform_align = RS::PARTICLES_TRANSFORM_ALIGN_DISABLED;
+
+		RS::ParticlesDrawOrder draw_order = RS::PARTICLES_DRAW_ORDER_INDEX;
+
+		Vector<RID> draw_passes;
+
+		GLuint frame_params_ubo = 0;
+
+		// We may process particles multiple times each frame (if they have a fixed FPS higher than the game FPS).
+		// Unfortunately, this means we can't just use a round-robin system of 3 buffers.
+		// To ensure the sort buffer is accurate, we copy the last frame instance buffer just before processing.
+
+		// Transform Feedback buffer and VAO for rendering.
+		// Each frame we render to this one.
+		GLuint front_vertex_array = 0; // Binds process buffer. Used for processing.
+		GLuint front_process_buffer = 0; // Transform + color + custom data + userdata + velocity + flags. Only needed for processing.
+		GLuint front_instance_buffer = 0; // Transform + color + custom data. In packed format needed for rendering.
+
+		// VAO for transform feedback, contains last frame's data.
+		// Read from this one for particles process and then copy to last frame buffer.
+		GLuint back_vertex_array = 0; // Binds process buffer. Used for processing.
+		GLuint back_process_buffer = 0; // Transform + color + custom data + userdata + velocity + flags. Only needed for processing.
+		GLuint back_instance_buffer = 0; // Transform + color + custom data. In packed format needed for rendering.
+
+		uint32_t instance_buffer_size_cache = 0;
+		uint32_t instance_buffer_stride_cache = 0;
+		uint32_t num_attrib_arrays_cache = 0;
+		uint32_t process_buffer_stride_cache = 0;
+
+		// Only ever copied to, holds last frame's instance data, then swaps with sort_buffer.
+		GLuint last_frame_buffer = 0;
+		bool last_frame_buffer_filled = false;
+		float last_frame_phase = 0.0;
+
+		// The frame-before-last's instance buffer.
+		// Use this to copy data back for sorting or computing AABB.
+		GLuint sort_buffer = 0;
+		bool sort_buffer_filled = false;
+		float sort_buffer_phase = 0.0;
+
+		uint32_t userdata_count = 0;
+
+		bool dirty = false;
+		Particles *update_list = nullptr;
+
+		double phase = 0.0;
+		double prev_phase = 0.0;
+		uint64_t prev_ticks = 0;
+		uint32_t random_seed = 0;
+
+		uint32_t cycle_number = 0;
+
+		double speed_scale = 1.0;
+
+		int fixed_fps = 30;
+		bool interpolate = true;
+		bool fractional_delta = false;
+		double frame_remainder = 0;
+		real_t collision_base_size = 0.01;
+
+		bool clear = true;
+
+		Transform3D emission_transform;
+
+		HashSet<RID> collisions;
+
+		Dependency dependency;
+
+		double trail_length = 1.0;
+		bool trails_enabled = false;
+
+		Particles() {
+		}
+	};
+
+	void _particles_process(Particles *p_particles, double p_delta);
+	void _particles_free_data(Particles *particles);
+	void _particles_update_buffers(Particles *particles);
+	void _particles_allocate_history_buffers(Particles *particles);
+	void _particles_update_instance_buffer(Particles *particles, const Vector3 &p_axis, const Vector3 &p_up_axis);
+
+	template <typename T>
+	void _particles_reverse_lifetime_sort(Particles *particles);
+
+	struct ParticlesShader {
+		RID default_shader;
+		RID default_material;
+		RID default_shader_version;
+
+		ParticlesCopyShaderGLES3 copy_shader;
+		RID copy_shader_version;
+	} particles_shader;
+
+	Particles *particle_update_list = nullptr;
+
+	mutable RID_Owner<Particles, true> particles_owner;
+
+	/* Particles Collision */
+
+	struct ParticlesCollision {
+		RS::ParticlesCollisionType type = RS::PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT;
+		uint32_t cull_mask = 0xFFFFFFFF;
+		float radius = 1.0;
+		Vector3 extents = Vector3(1, 1, 1);
+		float attractor_strength = 1.0;
+		float attractor_attenuation = 1.0;
+		float attractor_directionality = 0.0;
+		GLuint field_texture = 0;
+		GLuint heightfield_texture = 0;
+		GLuint heightfield_fb = 0;
+		Size2i heightfield_fb_size;
+
+		RS::ParticlesCollisionHeightfieldResolution heightfield_resolution = RS::PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_1024;
+
+		Dependency dependency;
+	};
+
+	struct ParticlesCollisionInstance {
+		RID collision;
+		Transform3D transform;
+		bool active = false;
+	};
+
+	mutable RID_Owner<ParticlesCollision, true> particles_collision_owner;
+
+	mutable RID_Owner<ParticlesCollisionInstance> particles_collision_instance_owner;
+
 public:
 	static ParticlesStorage *get_singleton();
 
 	ParticlesStorage();
 	virtual ~ParticlesStorage();
 
+	bool free(RID p_rid);
+
 	/* PARTICLES */
 
+	bool owns_particles(RID p_rid) { return particles_owner.owns(p_rid); }
+
 	virtual RID particles_allocate() override;
 	virtual void particles_initialize(RID p_rid) override;
 	virtual void particles_free(RID p_rid) override;
@@ -102,12 +360,51 @@ public:
 	virtual void particles_add_collision(RID p_particles, RID p_instance) override;
 	virtual void particles_remove_collision(RID p_particles, RID p_instance) override;
 
-	virtual void particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, RID p_texture) override;
+	void particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, GLuint p_texture);
 
 	virtual void update_particles() override;
 	virtual bool particles_is_inactive(RID p_particles) const override;
 
+	_FORCE_INLINE_ RS::ParticlesMode particles_get_mode(RID p_particles) {
+		Particles *particles = particles_owner.get_or_null(p_particles);
+		ERR_FAIL_COND_V(!particles, RS::PARTICLES_MODE_2D);
+		return particles->mode;
+	}
+
+	_FORCE_INLINE_ uint32_t particles_get_amount(RID p_particles) {
+		Particles *particles = particles_owner.get_or_null(p_particles);
+		ERR_FAIL_COND_V(!particles, 0);
+
+		return particles->amount;
+	}
+
+	_FORCE_INLINE_ GLuint particles_get_gl_buffer(RID p_particles) {
+		Particles *particles = particles_owner.get_or_null(p_particles);
+
+		if ((particles->draw_order == RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME) && particles->sort_buffer_filled) {
+			return particles->sort_buffer;
+		}
+		return particles->back_instance_buffer;
+	}
+
+	_FORCE_INLINE_ bool particles_has_collision(RID p_particles) {
+		Particles *particles = particles_owner.get_or_null(p_particles);
+		ERR_FAIL_COND_V(!particles, 0);
+
+		return particles->has_collision_cache;
+	}
+
+	_FORCE_INLINE_ uint32_t particles_is_using_local_coords(RID p_particles) {
+		Particles *particles = particles_owner.get_or_null(p_particles);
+		ERR_FAIL_COND_V(!particles, false);
+
+		return particles->use_local_coords;
+	}
+
+	Dependency *particles_get_dependency(RID p_particles) const;
+
 	/* PARTICLES COLLISION */
+	bool owns_particles_collision(RID p_rid) { return particles_collision_owner.owns(p_rid); }
 
 	virtual RID particles_collision_allocate() override;
 	virtual void particles_collision_initialize(RID p_rid) override;
@@ -124,8 +421,22 @@ public:
 	virtual void particles_collision_height_field_update(RID p_particles_collision) override;
 	virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) override;
 	virtual AABB particles_collision_get_aabb(RID p_particles_collision) const override;
+	Vector3 particles_collision_get_extents(RID p_particles_collision) const;
 	virtual bool particles_collision_is_heightfield(RID p_particles_collision) const override;
-	virtual RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const override;
+	GLuint particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const;
+
+	_FORCE_INLINE_ Size2i particles_collision_get_heightfield_size(RID p_particles_collision) const {
+		ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+		ERR_FAIL_COND_V(!particles_collision, Size2i());
+		ERR_FAIL_COND_V(particles_collision->type != RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE, Size2i());
+
+		return particles_collision->heightfield_fb_size;
+	}
+
+	Dependency *particles_collision_get_dependency(RID p_particles) const;
+
+	/* PARTICLES COLLISION INSTANCE*/
+	bool owns_particles_collision_instance(RID p_rid) { return particles_collision_instance_owner.owns(p_rid); }
 
 	virtual RID particles_collision_instance_create(RID p_collision) override;
 	virtual void particles_collision_instance_free(RID p_rid) override;

+ 16 - 4
drivers/gles3/storage/texture_storage.cpp

@@ -647,7 +647,7 @@ void TextureStorage::texture_2d_initialize(RID p_texture, const Ref<Image> &p_im
 	texture.height = p_image->get_height();
 	texture.alloc_width = texture.width;
 	texture.alloc_height = texture.height;
-	texture.mipmaps = p_image->get_mipmap_count();
+	texture.mipmaps = p_image->get_mipmap_count() + 1;
 	texture.format = p_image->get_format();
 	texture.type = Texture::TYPE_2D;
 	texture.target = GL_TEXTURE_2D;
@@ -2215,7 +2215,11 @@ void TextureStorage::render_target_sdf_process(RID p_render_target) {
 
 	// Load
 	CanvasSdfShaderGLES3::ShaderVariant variant = shrink ? CanvasSdfShaderGLES3::MODE_LOAD_SHRINK : CanvasSdfShaderGLES3::MODE_LOAD;
-	sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant);
+	bool success = sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant);
+	if (!success) {
+		return;
+	}
+
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::BASE_SIZE, r.size, sdf_shader.shader_version, variant);
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SIZE, size, sdf_shader.shader_version, variant);
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::STRIDE, 0, sdf_shader.shader_version, variant);
@@ -2236,7 +2240,11 @@ void TextureStorage::render_target_sdf_process(RID p_render_target) {
 	int stride = nearest_power_of_2_templated(MAX(size.width, size.height) / 2);
 
 	variant = CanvasSdfShaderGLES3::MODE_PROCESS;
-	sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant);
+	success = sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant);
+	if (!success) {
+		return;
+	}
+
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::BASE_SIZE, r.size, sdf_shader.shader_version, variant);
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SIZE, size, sdf_shader.shader_version, variant);
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::STRIDE, stride, sdf_shader.shader_version, variant);
@@ -2260,7 +2268,11 @@ void TextureStorage::render_target_sdf_process(RID p_render_target) {
 
 	// Store
 	variant = shrink ? CanvasSdfShaderGLES3::MODE_STORE_SHRINK : CanvasSdfShaderGLES3::MODE_STORE;
-	sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant);
+	success = sdf_shader.shader.version_bind_shader(sdf_shader.shader_version, variant);
+	if (!success) {
+		return;
+	}
+
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::BASE_SIZE, r.size, sdf_shader.shader_version, variant);
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::SIZE, size, sdf_shader.shader_version, variant);
 	sdf_shader.shader.version_set_uniform(CanvasSdfShaderGLES3::STRIDE, stride, sdf_shader.shader_version, variant);

+ 18 - 43
drivers/gles3/storage/utilities.cpp

@@ -108,6 +108,10 @@ RS::InstanceType Utilities::get_base_type(RID p_rid) const {
 		return RS::INSTANCE_LIGHT;
 	} else if (GLES3::LightStorage::get_singleton()->owns_lightmap(p_rid)) {
 		return RS::INSTANCE_LIGHTMAP;
+	} else if (GLES3::ParticlesStorage::get_singleton()->owns_particles(p_rid)) {
+		return RS::INSTANCE_PARTICLES;
+	} else if (GLES3::ParticlesStorage::get_singleton()->owns_particles_collision(p_rid)) {
+		return RS::INSTANCE_PARTICLES_COLLISION;
 	}
 	return RS::INSTANCE_NONE;
 }
@@ -143,53 +147,18 @@ bool Utilities::free(RID p_rid) {
 	} else if (GLES3::LightStorage::get_singleton()->owns_lightmap(p_rid)) {
 		GLES3::LightStorage::get_singleton()->lightmap_free(p_rid);
 		return true;
-	} else {
-		return false;
-	}
-	/*
-	else if (reflection_probe_owner.owns(p_rid)) {
-		// delete the texture
-		ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_rid);
-		reflection_probe->instance_remove_deps();
-
-		reflection_probe_owner.free(p_rid);
-		memdelete(reflection_probe);
-
-		return true;
-	} else if (lightmap_capture_data_owner.owns(p_rid)) {
-		// delete the texture
-		LightmapCapture *lightmap_capture = lightmap_capture_data_owner.get_or_null(p_rid);
-		lightmap_capture->instance_remove_deps();
-
-		lightmap_capture_data_owner.free(p_rid);
-		memdelete(lightmap_capture);
+	} else if (GLES3::ParticlesStorage::get_singleton()->owns_particles(p_rid)) {
+		GLES3::ParticlesStorage::get_singleton()->particles_free(p_rid);
 		return true;
-
-	} else if (canvas_occluder_owner.owns(p_rid)) {
-		CanvasOccluder *co = canvas_occluder_owner.get_or_null(p_rid);
-		if (co->index_id) {
-			glDeleteBuffers(1, &co->index_id);
-		}
-		if (co->vertex_id) {
-			glDeleteBuffers(1, &co->vertex_id);
-		}
-
-		canvas_occluder_owner.free(p_rid);
-		memdelete(co);
-
+	} else if (GLES3::ParticlesStorage::get_singleton()->owns_particles_collision(p_rid)) {
+		GLES3::ParticlesStorage::get_singleton()->particles_collision_free(p_rid);
 		return true;
-
-	} else if (canvas_light_shadow_owner.owns(p_rid)) {
-		CanvasLightShadow *cls = canvas_light_shadow_owner.get_or_null(p_rid);
-		glDeleteFramebuffers(1, &cls->fbo);
-		glDeleteRenderbuffers(1, &cls->depth);
-		glDeleteTextures(1, &cls->distance);
-		canvas_light_shadow_owner.free(p_rid);
-		memdelete(cls);
-
+	} else if (GLES3::ParticlesStorage::get_singleton()->owns_particles_collision_instance(p_rid)) {
+		GLES3::ParticlesStorage::get_singleton()->particles_collision_instance_free(p_rid);
 		return true;
+	} else {
+		return false;
 	}
-	*/
 }
 
 /* DEPENDENCIES */
@@ -207,6 +176,12 @@ void Utilities::base_update_dependency(RID p_base, DependencyTracker *p_instance
 	} else if (LightStorage::get_singleton()->owns_light(p_base)) {
 		Light *l = LightStorage::get_singleton()->get_light(p_base);
 		p_instance->update_dependency(&l->dependency);
+	} else if (ParticlesStorage::get_singleton()->owns_particles(p_base)) {
+		Dependency *dependency = ParticlesStorage::get_singleton()->particles_get_dependency(p_base);
+		p_instance->update_dependency(dependency);
+	} else if (ParticlesStorage::get_singleton()->owns_particles_collision(p_base)) {
+		Dependency *dependency = ParticlesStorage::get_singleton()->particles_collision_get_dependency(p_base);
+		p_instance->update_dependency(dependency);
 	}
 }
 

+ 44 - 5
gles3_builders.py

@@ -20,6 +20,7 @@ class GLES3HeaderStruct:
         self.texunit_names = []
         self.ubos = []
         self.ubo_names = []
+        self.feedbacks = []
 
         self.vertex_included_files = []
         self.fragment_included_files = []
@@ -168,6 +169,20 @@ def include_file_in_gles3_header(filename: str, header_data: GLES3HeaderStruct,
                 if not x in header_data.uniforms:
                     header_data.uniforms += [x]
 
+        if (line.strip().find("out ") == 0 or line.strip().find("flat ") == 0) and line.find("tfb:") != -1:
+            uline = line.replace("flat ", "")
+            uline = uline.replace("out ", "")
+            uline = uline.replace("highp ", "")
+            uline = uline.replace(";", "")
+            uline = uline[uline.find(" ") :].strip()
+
+            if uline.find("//") != -1:
+                name, bind = uline.split("//")
+                if bind.find("tfb:") != -1:
+                    name = name.strip()
+                    bind = bind.replace("tfb:", "").strip()
+                    header_data.feedbacks += [(name, bind)]
+
         line = line.replace("\r", "")
         line = line.replace("\n", "")
 
@@ -240,11 +255,11 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da
             defspec |= 1 << i
 
     fd.write(
-        "\t_FORCE_INLINE_ void version_bind_shader(RID p_version,ShaderVariant p_variant"
+        "\t_FORCE_INLINE_ bool version_bind_shader(RID p_version,ShaderVariant p_variant"
         + defvariant
         + ",uint64_t p_specialization="
         + str(defspec)
-        + ") { _version_bind_shader(p_version,p_variant,p_specialization); }\n\n"
+        + ") { return _version_bind_shader(p_version,p_variant,p_specialization); }\n\n"
     )
 
     if header_data.uniforms:
@@ -278,7 +293,7 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da
             + defvariant
             + ",uint64_t p_specialization="
             + str(defspec)
-            + ") { _FU glUniform1i(version_get_uniform(p_uniform,p_version,p_variant,p_specialization),p_value); }\n\n"
+            + ") { _FU glUniform1ui(version_get_uniform(p_uniform,p_version,p_variant,p_specialization),p_value); }\n\n"
         )
         fd.write(
             "\t_FORCE_INLINE_ void version_set_uniform(Uniforms p_uniform, int8_t p_value,RID p_version,ShaderVariant p_variant"
@@ -292,7 +307,7 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da
             + defvariant
             + ",uint64_t p_specialization="
             + str(defspec)
-            + ") { _FU glUniform1i(version_get_uniform(p_uniform,p_version,p_variant,p_specialization),p_value); }\n\n"
+            + ") { _FU glUniform1ui(version_get_uniform(p_uniform,p_version,p_variant,p_specialization),p_value); }\n\n"
         )
         fd.write(
             "\t_FORCE_INLINE_ void version_set_uniform(Uniforms p_uniform, int16_t p_value,RID p_version,ShaderVariant p_variant"
@@ -306,7 +321,7 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da
             + defvariant
             + ",uint64_t p_specialization="
             + str(defspec)
-            + ") { _FU glUniform1i(version_get_uniform(p_uniform,p_version,p_variant,p_specialization),p_value); }\n\n"
+            + ") { _FU glUniform1ui(version_get_uniform(p_uniform,p_version,p_variant,p_specialization),p_value); }\n\n"
         )
         fd.write(
             "\t_FORCE_INLINE_ void version_set_uniform(Uniforms p_uniform, int32_t p_value,RID p_version,ShaderVariant p_variant"
@@ -497,6 +512,8 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da
     else:
         fd.write("\t\tstatic UBOPair *_ubo_pairs=nullptr;\n")
 
+    specializations_found = []
+
     if header_data.specialization_names:
         fd.write("\t\tstatic Specialization _spec_pairs[]={\n")
         for i in range(len(header_data.specialization_names)):
@@ -507,10 +524,30 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da
                 defval = "false"
 
             fd.write('\t\t\t{"' + header_data.specialization_names[i] + '",' + defval + "},\n")
+            specializations_found.append(header_data.specialization_names[i])
         fd.write("\t\t};\n\n")
     else:
         fd.write("\t\tstatic Specialization *_spec_pairs=nullptr;\n")
 
+    feedback_count = 0
+
+    if header_data.feedbacks:
+
+        fd.write("\t\tstatic const Feedback _feedbacks[]={\n")
+        for x in header_data.feedbacks:
+            name = x[0]
+            spec = x[1]
+            if spec in specializations_found:
+                fd.write('\t\t\t{"' + name + '",' + str(1 << specializations_found.index(spec)) + "},\n")
+            else:
+                fd.write('\t\t\t{"' + name + '",0},\n')
+
+            feedback_count += 1
+
+        fd.write("\t\t};\n\n")
+    else:
+        fd.write("\t\tstatic const Feedback* _feedbacks=nullptr;\n")
+
     fd.write("\t\tstatic const char _vertex_code[]={\n")
     for x in header_data.vertex_lines:
         for c in x:
@@ -535,6 +572,8 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da
         + ",_uniform_strings,"
         + str(len(header_data.ubos))
         + ",_ubo_pairs,"
+        + str(feedback_count)
+        + ",_feedbacks,"
         + str(len(header_data.texunits))
         + ",_texunit_pairs,"
         + str(len(header_data.specialization_names))

+ 0 - 4
scene/2d/gpu_particles_2d.cpp

@@ -299,10 +299,6 @@ bool GPUParticles2D::get_interpolate() const {
 PackedStringArray GPUParticles2D::get_configuration_warnings() const {
 	PackedStringArray warnings = Node2D::get_configuration_warnings();
 
-	if (RenderingServer::get_singleton()->is_low_end()) {
-		warnings.push_back(RTR("GPU-based particles are not supported by the OpenGL video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles2D\" option for this purpose."));
-	}
-
 	if (process_material.is_null()) {
 		warnings.push_back(RTR("A material to process the particles is not assigned, so no behavior is imprinted."));
 	} else {

+ 1 - 1
scene/2d/gpu_particles_2d.h

@@ -64,7 +64,7 @@ private:
 #endif
 	Ref<Material> process_material;
 
-	DrawOrder draw_order;
+	DrawOrder draw_order = DRAW_ORDER_LIFETIME;
 
 	Ref<Texture2D> texture;
 

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

@@ -272,10 +272,6 @@ bool GPUParticles3D::get_interpolate() const {
 PackedStringArray GPUParticles3D::get_configuration_warnings() const {
 	PackedStringArray warnings = GeometryInstance3D::get_configuration_warnings();
 
-	if (RenderingServer::get_singleton()->is_low_end()) {
-		warnings.push_back(RTR("GPU-based particles are not supported by the OpenGL video driver.\nUse the CPUParticles3D node instead. You can use the \"Convert to CPUParticles3D\" option for this purpose."));
-	}
-
 	bool meshes_found = false;
 	bool anim_material_found = false;
 

+ 1 - 1
scene/3d/gpu_particles_3d.h

@@ -82,7 +82,7 @@ private:
 
 	Ref<Material> process_material;
 
-	DrawOrder draw_order;
+	DrawOrder draw_order = DRAW_ORDER_INDEX;
 
 	Vector<Ref<Mesh>> draw_passes;
 	Ref<Skin> skin;

+ 0 - 3
servers/rendering/dummy/storage/particles_storage.h

@@ -89,8 +89,6 @@ public:
 	virtual void particles_add_collision(RID p_particles, RID p_instance) override {}
 	virtual void particles_remove_collision(RID p_particles, RID p_instance) override {}
 
-	virtual void particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, RID p_texture) override {}
-
 	virtual void update_particles() override {}
 
 	/* PARTICLES COLLISION */
@@ -111,7 +109,6 @@ public:
 	virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) override {}
 	virtual AABB particles_collision_get_aabb(RID p_particles_collision) const override { return AABB(); }
 	virtual bool particles_collision_is_heightfield(RID p_particles_collision) const override { return false; }
-	virtual RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const override { return RID(); }
 
 	virtual RID particles_collision_instance_create(RID p_collision) override { return RID(); }
 	virtual void particles_collision_instance_free(RID p_rid) override {}

+ 1 - 0
servers/rendering/renderer_rd/storage_rd/particles_storage.cpp

@@ -1027,6 +1027,7 @@ void ParticlesStorage::_particles_process(Particles *p_particles, double p_delta
 				uniforms.push_back(u);
 			}
 			p_particles->collision_textures_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.default_shader_rd, 2);
+			p_particles->collision_heightmap_texture = collision_heightmap_texture;
 		}
 	}
 

+ 4 - 8
servers/rendering/renderer_rd/storage_rd/particles_storage.h

@@ -54,8 +54,7 @@ private:
 		float velocity[3];
 		uint32_t active;
 		float color[4];
-		float custom[3];
-		float lifetime;
+		float custom[4];
 	};
 
 	struct ParticlesFrameParams {
@@ -127,9 +126,6 @@ private:
 		Collider colliders[MAX_COLLIDERS];
 	};
 
-	struct ParticleEmissionBufferData {
-	};
-
 	struct ParticleEmissionBuffer {
 		struct Data {
 			float xform[16];
@@ -412,7 +408,7 @@ public:
 	bool owns_particles(RID p_rid) { return particles_owner.owns(p_rid); }
 
 	virtual RID particles_allocate() override;
-	virtual void particles_initialize(RID p_particles_collision) override;
+	virtual void particles_initialize(RID p_rid) override;
 	virtual void particles_free(RID p_rid) override;
 
 	virtual void particles_set_mode(RID p_particles, RS::ParticlesMode p_mode) override;
@@ -519,7 +515,7 @@ public:
 
 	virtual void particles_add_collision(RID p_particles, RID p_particles_collision_instance) override;
 	virtual void particles_remove_collision(RID p_particles, RID p_particles_collision_instance) override;
-	virtual void particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, RID p_texture) override;
+	void particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, RID p_texture);
 
 	virtual void update_particles() override;
 
@@ -546,7 +542,7 @@ public:
 	virtual AABB particles_collision_get_aabb(RID p_particles_collision) const override;
 	Vector3 particles_collision_get_extents(RID p_particles_collision) const;
 	virtual bool particles_collision_is_heightfield(RID p_particles_collision) const override;
-	virtual RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const override;
+	RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const;
 
 	Dependency *particles_collision_get_dependency(RID p_particles) const;
 

+ 0 - 3
servers/rendering/storage/particles_storage.h

@@ -94,8 +94,6 @@ public:
 	virtual void particles_add_collision(RID p_particles, RID p_particles_collision_instance) = 0;
 	virtual void particles_remove_collision(RID p_particles, RID p_particles_collision_instance) = 0;
 
-	virtual void particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, RID p_texture) = 0;
-
 	virtual void update_particles() = 0;
 
 	/* PARTICLES COLLISION */
@@ -116,7 +114,6 @@ public:
 	virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) = 0; //for SDF and vector field
 	virtual AABB particles_collision_get_aabb(RID p_particles_collision) const = 0;
 	virtual bool particles_collision_is_heightfield(RID p_particles_collision) const = 0;
-	virtual RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const = 0;
 
 	//used from 2D and 3D
 	virtual RID particles_collision_instance_create(RID p_collision) = 0;

+ 3 - 2
tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl

@@ -18,7 +18,7 @@ public:
 		DISABLE_LIGHTING=1,
 	};
 
-	_FORCE_INLINE_ void version_bind_shader(RID p_version,ShaderVariant p_variant,uint64_t p_specialization=0) { _version_bind_shader(p_version,p_variant,p_specialization); }
+	_FORCE_INLINE_ bool version_bind_shader(RID p_version,ShaderVariant p_variant,uint64_t p_specialization=0) { return _version_bind_shader(p_version,p_variant,p_specialization); }
 
 protected:
 
@@ -35,13 +35,14 @@ protected:
 			{"DISABLE_LIGHTING",false},
 		};
 
+		static const Feedback* _feedbacks=nullptr;
 		static const char _vertex_code[]={
 10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,102,108,111,97,116,59,10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,105,110,116,59,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,105,110,32,104,105,103,104,112,32,118,101,99,51,32,118,101,114,116,101,120,59,10,10,111,117,116,32,104,105,103,104,112,32,118,101,99,52,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,32,61,32,118,101,99,52,40,118,101,114,116,101,120,46,120,44,49,44,48,44,49,41,59,10,125,10,10,		0};
 
 		static const char _fragment_code[]={
 10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,102,108,111,97,116,59,10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,105,110,116,59,10,10,105,110,32,104,105,103,104,112,32,118,101,99,52,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,104,105,103,104,112,32,102,108,111,97,116,32,100,101,112,116,104,32,61,32,40,40,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,46,122,32,47,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,46,119,41,32,43,32,49,46,48,41,59,10,9,102,114,97,103,95,99,111,108,111,114,32,61,32,118,101,99,52,40,100,101,112,116,104,41,59,10,125,10,		0};
 
-		_setup(_vertex_code,_fragment_code,"VertexFragmentShaderGLES3",0,_uniform_strings,0,_ubo_pairs,0,_texunit_pairs,1,_spec_pairs,1,_variant_defines);
+		_setup(_vertex_code,_fragment_code,"VertexFragmentShaderGLES3",0,_uniform_strings,0,_ubo_pairs,0,_feedbacks,0,_texunit_pairs,1,_spec_pairs,1,_variant_defines);
 	}
 
 };

+ 1 - 0
tests/python_build/fixtures/gles3/vertex_fragment_expected_parts.json

@@ -31,6 +31,7 @@
   "texunit_names": [],
   "ubos": [],
   "ubo_names": [],
+  "feedbacks": [],
   "vertex_included_files": [],
   "fragment_included_files": [],
   "reading": "fragment",