Browse Source

Implement 2D instance shader parameters

Co-authored-by: kobewi <[email protected]>
Co-authored-by: yesfish <[email protected]>
Co-authored-by: Álex Román Núñez <[email protected]>
kobewi 2 years ago
parent
commit
ceefc0d38a

+ 17 - 0
doc/classes/CanvasItem.xml

@@ -461,6 +461,13 @@
 				Returns the transform from the local coordinate system of this [CanvasItem] to the [Viewport]s coordinate system.
 			</description>
 		</method>
+		<method name="get_instance_shader_parameter" qualifiers="const">
+			<return type="Variant" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Get the value of a shader parameter as set on this instance.
+			</description>
+		</method>
 		<method name="get_local_mouse_position" qualifiers="const">
 			<return type="Vector2" />
 			<description>
@@ -562,6 +569,16 @@
 				Queues the [CanvasItem] to redraw. During idle time, if [CanvasItem] is visible, [constant NOTIFICATION_DRAW] is sent and [method _draw] is called. This only occurs [b]once[/b] per frame, even if this method has been called multiple times.
 			</description>
 		</method>
+		<method name="set_instance_shader_parameter">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="value" type="Variant" />
+			<description>
+				Set the value of a shader uniform for this instance only ([url=$DOCS_URL/tutorials/shaders/shader_reference/shading_language.html#per-instance-uniforms]per-instance uniform[/url]). See also [method ShaderMaterial.set_shader_parameter] to assign a uniform on all instances using the same [ShaderMaterial].
+				[b]Note:[/b] For a shader uniform to be assignable on a per-instance basis, it [i]must[/i] be defined with [code]instance uniform ...[/code] rather than [code]uniform ...[/code] in the shader code.
+				[b]Note:[/b] [param name] is case-sensitive and must match the name of the uniform in the code exactly (not the capitalized name in the inspector).
+			</description>
+		</method>
 		<method name="set_notify_local_transform">
 			<return type="void" />
 			<param index="0" name="enable" type="bool" />

+ 1 - 1
doc/classes/GeometryInstance3D.xml

@@ -25,7 +25,7 @@
 				Set the value of a shader uniform for this instance only ([url=$DOCS_URL/tutorials/shaders/shader_reference/shading_language.html#per-instance-uniforms]per-instance uniform[/url]). See also [method ShaderMaterial.set_shader_parameter] to assign a uniform on all instances using the same [ShaderMaterial].
 				[b]Note:[/b] For a shader uniform to be assignable on a per-instance basis, it [i]must[/i] be defined with [code]instance uniform ...[/code] rather than [code]uniform ...[/code] in the shader code.
 				[b]Note:[/b] [param name] is case-sensitive and must match the name of the uniform in the code exactly (not the capitalized name in the inspector).
-				[b]Note:[/b] Per-instance shader uniforms are currently only available in 3D, so there is no 2D equivalent of this method.
+				[b]Note:[/b] Per-instance shader uniforms are only available in Spatial and CanvasItem shaders, but not for Fog, Sky, or Particles shaders.
 			</description>
 		</method>
 	</methods>

+ 33 - 0
doc/classes/RenderingServer.xml

@@ -436,6 +436,30 @@
 				[b]Note:[/b] The equivalent node is [CanvasItem].
 			</description>
 		</method>
+		<method name="canvas_item_get_instance_shader_parameter" qualifiers="const">
+			<return type="Variant" />
+			<param index="0" name="instance" type="RID" />
+			<param index="1" name="parameter" type="StringName" />
+			<description>
+				Returns the value of the per-instance shader uniform from the specified canvas item instance. Equivalent to [method CanvasItem.get_instance_shader_parameter].
+			</description>
+		</method>
+		<method name="canvas_item_get_instance_shader_parameter_default_value" qualifiers="const">
+			<return type="Variant" />
+			<param index="0" name="instance" type="RID" />
+			<param index="1" name="parameter" type="StringName" />
+			<description>
+				Returns the default value of the per-instance shader uniform from the specified canvas item instance. Equivalent to [method CanvasItem.get_instance_shader_parameter].
+			</description>
+		</method>
+		<method name="canvas_item_get_instance_shader_parameter_list" qualifiers="const">
+			<return type="Dictionary[]" />
+			<param index="0" name="instance" type="RID" />
+			<description>
+				Returns a dictionary of per-instance shader uniform names of the per-instance shader uniform from the specified canvas item instance.
+				The returned dictionary is in PropertyInfo format, with the keys [code]name[/code], [code]class_name[/code], [code]type[/code], [code]hint[/code], [code]hint_string[/code], and [code]usage[/code].
+			</description>
+		</method>
 		<method name="canvas_item_reset_physics_interpolation">
 			<return type="void" />
 			<param index="0" name="item" type="RID" />
@@ -524,6 +548,15 @@
 				Sets the index for the [CanvasItem].
 			</description>
 		</method>
+		<method name="canvas_item_set_instance_shader_parameter">
+			<return type="void" />
+			<param index="0" name="instance" type="RID" />
+			<param index="1" name="parameter" type="StringName" />
+			<param index="2" name="value" type="Variant" />
+			<description>
+				Sets the per-instance shader uniform on the specified canvas item instance. Equivalent to [method CanvasItem.set_instance_shader_parameter].
+			</description>
+		</method>
 		<method name="canvas_item_set_interpolated">
 			<return type="void" />
 			<param index="0" name="item" type="RID" />

+ 8 - 7
drivers/gles3/rasterizer_canvas_gles3.cpp

@@ -727,6 +727,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
 
 		// Bind per-batch uniforms.
 		material_storage->shaders.canvas_shader.version_set_uniform(CanvasShaderGLES3::BATCH_FLAGS, state.canvas_instance_batches[i].flags, shader_version, variant, specialization);
+		material_storage->shaders.canvas_shader.version_set_uniform(CanvasShaderGLES3::SPECULAR_SHININESS_IN, state.canvas_instance_batches[i].specular_shininess, shader_version, variant, specialization);
 
 		GLES3::CanvasShaderData::BlendMode blend_mode = state.canvas_instance_batches[i].blend_mode;
 		Color blend_color = state.canvas_instance_batches[i].blend_color;
@@ -907,15 +908,15 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
 		state.instance_data_array[r_index].color_texture_pixel_size[0] = 0.0;
 		state.instance_data_array[r_index].color_texture_pixel_size[1] = 0.0;
 
-		state.instance_data_array[r_index].pad[0] = 0.0;
-		state.instance_data_array[r_index].pad[1] = 0.0;
-
 		state.instance_data_array[r_index].lights[0] = lights[0];
 		state.instance_data_array[r_index].lights[1] = lights[1];
 		state.instance_data_array[r_index].lights[2] = lights[2];
 		state.instance_data_array[r_index].lights[3] = lights[3];
 
 		state.instance_data_array[r_index].flags = base_flags;
+		state.instance_data_array[r_index].instance_uniforms_ofs = p_item->instance_allocated_shader_uniforms_offset;
+
+		state.instance_data_array[r_index].flags = base_flags | (state.instance_data_array[r_index == 0 ? 0 : r_index - 1].flags & (BATCH_FLAGS_DEFAULT_NORMAL_MAP_USED | BATCH_FLAGS_DEFAULT_SPECULAR_MAP_USED)); // Reset on each command for safety, keep canvastexture binding config.
 
 		Color blend_color = base_color;
 		GLES3::CanvasShaderData::BlendMode blend_mode = p_blend_mode;
@@ -2387,10 +2388,10 @@ void RasterizerCanvasGLES3::_prepare_canvas_texture(RID p_texture, RS::CanvasIte
 		state.canvas_instance_batches[state.current_batch_index].flags &= ~BATCH_FLAGS_DEFAULT_NORMAL_MAP_USED;
 	}
 
-	state.instance_data_array[r_index].specular_shininess = uint32_t(CLAMP(ct->specular_color.a * 255.0, 0, 255)) << 24;
-	state.instance_data_array[r_index].specular_shininess |= uint32_t(CLAMP(ct->specular_color.b * 255.0, 0, 255)) << 16;
-	state.instance_data_array[r_index].specular_shininess |= uint32_t(CLAMP(ct->specular_color.g * 255.0, 0, 255)) << 8;
-	state.instance_data_array[r_index].specular_shininess |= uint32_t(CLAMP(ct->specular_color.r * 255.0, 0, 255));
+	state.canvas_instance_batches[state.current_batch_index].specular_shininess = uint32_t(CLAMP(ct->specular_color.a * 255.0, 0, 255)) << 24;
+	state.canvas_instance_batches[state.current_batch_index].specular_shininess |= uint32_t(CLAMP(ct->specular_color.b * 255.0, 0, 255)) << 16;
+	state.canvas_instance_batches[state.current_batch_index].specular_shininess |= uint32_t(CLAMP(ct->specular_color.g * 255.0, 0, 255)) << 8;
+	state.canvas_instance_batches[state.current_batch_index].specular_shininess |= uint32_t(CLAMP(ct->specular_color.r * 255.0, 0, 255));
 
 	r_texpixel_size.x = 1.0 / float(size_cache.x);
 	r_texpixel_size.y = 1.0 / float(size_cache.y);

+ 4 - 1
drivers/gles3/rasterizer_canvas_gles3.h

@@ -227,7 +227,7 @@ public:
 			};
 		};
 		uint32_t flags;
-		uint32_t specular_shininess;
+		uint32_t instance_uniforms_ofs;
 		uint32_t lights[4];
 	};
 
@@ -279,6 +279,9 @@ public:
 		uint32_t primitive_points = 0;
 
 		uint32_t flags = 0;
+		uint32_t specular_shininess = 0.0;
+
+		bool lights_disabled = false;
 	};
 
 	// DataBuffer contains our per-frame data. I.e. the resources that are updated each frame.

+ 5 - 4
drivers/gles3/shaders/canvas.glsl

@@ -83,7 +83,7 @@ layout(location = 15) in highp uvec4 attrib_H;
 #endif
 
 #define read_draw_data_flags attrib_G.z
-#define read_draw_data_specular_shininess attrib_G.w
+#define read_draw_data_instance_offset attrib_G.w
 #define read_draw_data_lights attrib_H
 
 // Varyings so the per-instance info can be used in the fragment shader
@@ -142,7 +142,7 @@ void main() {
 #endif // !USE_ATTRIBUTES
 #endif // USE_PRIMITIVE
 
-	varying_F = uvec2(read_draw_data_flags, read_draw_data_specular_shininess);
+	varying_F = uvec2(read_draw_data_flags, read_draw_data_instance_offset);
 	varying_G = read_draw_data_lights;
 
 	vec4 instance_custom = vec4(0.0);
@@ -325,7 +325,7 @@ flat in vec4 varying_E;
 flat in uvec2 varying_F;
 flat in uvec4 varying_G;
 #define read_draw_data_flags varying_F.x
-#define read_draw_data_specular_shininess varying_F.y
+#define read_draw_data_instance_offset varying_F.y
 #define read_draw_data_lights varying_G
 
 #ifndef DISABLE_LIGHTING
@@ -340,6 +340,7 @@ uniform sampler2D specular_texture; //texunit:-7
 uniform sampler2D color_texture; //texunit:0
 
 uniform mediump uint batch_flags;
+uniform highp uint specular_shininess_in;
 
 layout(location = 0) out vec4 frag_color;
 
@@ -660,7 +661,7 @@ void main() {
 
 	if (specular_shininess_used || (using_light && normal_used && bool(batch_flags & BATCH_FLAGS_DEFAULT_SPECULAR_MAP_USED))) {
 		specular_shininess = texture(specular_texture, uv);
-		specular_shininess *= godot_unpackUnorm4x8(read_draw_data_specular_shininess);
+		specular_shininess *= godot_unpackUnorm4x8(specular_shininess_in);
 		specular_shininess_used = true;
 	} else {
 		specular_shininess = vec4(1.0);

+ 1 - 0
drivers/gles3/storage/material_storage.cpp

@@ -1193,6 +1193,7 @@ MaterialStorage::MaterialStorage() {
 		actions.render_mode_defines["world_vertex_coords"] = "#define USE_WORLD_VERTEX_COORDS\n";
 
 		actions.global_buffer_array_variable = "global_shader_uniforms";
+		actions.instance_uniform_index_variable = "read_draw_data_instance_offset";
 
 		shaders.compiler_canvas.initialize(actions);
 	}

+ 77 - 0
scene/main/canvas_item.cpp

@@ -552,6 +552,60 @@ int CanvasItem::get_light_mask() const {
 	return light_mask;
 }
 
+const StringName *CanvasItem::_instance_shader_parameter_get_remap(const StringName &p_name) const {
+	StringName *r = instance_shader_parameter_property_remap.getptr(p_name);
+	if (!r) {
+		String s = p_name;
+		if (s.begins_with("instance_shader_parameters/")) {
+			StringName name = s.trim_prefix("instance_shader_parameters/");
+			instance_shader_parameter_property_remap[p_name] = name;
+			return instance_shader_parameter_property_remap.getptr(p_name);
+		}
+		return nullptr;
+	}
+	return r;
+}
+
+bool CanvasItem::_set(const StringName &p_name, const Variant &p_value) {
+	const StringName *r = _instance_shader_parameter_get_remap(p_name);
+	if (r) {
+		set_instance_shader_parameter(*r, p_value);
+		return true;
+	}
+	return false;
+}
+
+bool CanvasItem::_get(const StringName &p_name, Variant &r_ret) const {
+	const StringName *r = _instance_shader_parameter_get_remap(p_name);
+	if (r) {
+		r_ret = get_instance_shader_parameter(*r);
+		return true;
+	}
+
+	return false;
+}
+
+void CanvasItem::_get_property_list(List<PropertyInfo> *p_list) const {
+	List<PropertyInfo> pinfo;
+	RS::get_singleton()->canvas_item_get_instance_shader_parameter_list(get_canvas_item(), &pinfo);
+
+	for (PropertyInfo &pi : pinfo) {
+		bool has_def_value = false;
+		Variant def_value = RS::get_singleton()->canvas_item_get_instance_shader_parameter_default_value(get_canvas_item(), pi.name);
+		if (def_value.get_type() != Variant::NIL) {
+			has_def_value = true;
+		}
+		if (instance_shader_parameters.has(pi.name)) {
+			pi.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE | (has_def_value ? (PROPERTY_USAGE_CHECKABLE | PROPERTY_USAGE_CHECKED) : PROPERTY_USAGE_NONE);
+		} else {
+			pi.usage = PROPERTY_USAGE_EDITOR | (has_def_value ? PROPERTY_USAGE_CHECKABLE : PROPERTY_USAGE_NONE); // Do not save if not changed.
+		}
+
+		pi.name = "instance_shader_parameters/" + pi.name;
+		p_list->push_back(pi);
+	}
+}
+
 void CanvasItem::item_rect_changed(bool p_size_changed) {
 	ERR_MAIN_THREAD_GUARD;
 	if (p_size_changed) {
@@ -1082,6 +1136,26 @@ void CanvasItem::set_use_parent_material(bool p_use_parent_material) {
 	RS::get_singleton()->canvas_item_set_use_parent_material(canvas_item, p_use_parent_material);
 }
 
+void CanvasItem::set_instance_shader_parameter(const StringName &p_name, const Variant &p_value) {
+	if (p_value.get_type() == Variant::NIL) {
+		Variant def_value = RS::get_singleton()->canvas_item_get_instance_shader_parameter_default_value(get_canvas_item(), p_name);
+		RS::get_singleton()->canvas_item_set_instance_shader_parameter(get_canvas_item(), p_name, def_value);
+		instance_shader_parameters.erase(p_value);
+	} else {
+		instance_shader_parameters[p_name] = p_value;
+		if (p_value.get_type() == Variant::OBJECT) {
+			RID tex_id = p_value;
+			RS::get_singleton()->canvas_item_set_instance_shader_parameter(get_canvas_item(), p_name, tex_id);
+		} else {
+			RS::get_singleton()->canvas_item_set_instance_shader_parameter(get_canvas_item(), p_name, p_value);
+		}
+	}
+}
+
+Variant CanvasItem::get_instance_shader_parameter(const StringName &p_name) const {
+	return RS::get_singleton()->canvas_item_get_instance_shader_parameter(get_canvas_item(), p_name);
+}
+
 bool CanvasItem::get_use_parent_material() const {
 	ERR_READ_THREAD_GUARD_V(false);
 	return use_parent_material;
@@ -1244,6 +1318,9 @@ void CanvasItem::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_material", "material"), &CanvasItem::set_material);
 	ClassDB::bind_method(D_METHOD("get_material"), &CanvasItem::get_material);
 
+	ClassDB::bind_method(D_METHOD("set_instance_shader_parameter", "name", "value"), &CanvasItem::set_instance_shader_parameter);
+	ClassDB::bind_method(D_METHOD("get_instance_shader_parameter", "name"), &CanvasItem::get_instance_shader_parameter);
+
 	ClassDB::bind_method(D_METHOD("set_use_parent_material", "enable"), &CanvasItem::set_use_parent_material);
 	ClassDB::bind_method(D_METHOD("get_use_parent_material"), &CanvasItem::get_use_parent_material);
 

+ 10 - 0
scene/main/canvas_item.h

@@ -116,6 +116,8 @@ private:
 	TextureRepeat texture_repeat = TEXTURE_REPEAT_PARENT_NODE;
 
 	Ref<Material> material;
+	mutable HashMap<StringName, Variant> instance_shader_parameters;
+	mutable HashMap<StringName, StringName> instance_shader_parameter_property_remap;
 
 	mutable Transform2D global_transform;
 	mutable MTFlag global_invalid;
@@ -150,8 +152,13 @@ private:
 	void _update_texture_filter_changed(bool p_propagate);
 
 	void _notify_transform_deferred();
+	const StringName *_instance_shader_parameter_get_remap(const StringName &p_name) const;
 
 protected:
+	bool _set(const StringName &p_name, const Variant &p_value);
+	bool _get(const StringName &p_name, Variant &r_ret) const;
+	void _get_property_list(List<PropertyInfo> *p_list) const;
+
 	virtual void _update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat);
 	virtual void _update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter);
 
@@ -355,6 +362,9 @@ public:
 	virtual void set_material(const Ref<Material> &p_material);
 	Ref<Material> get_material() const;
 
+	void set_instance_shader_parameter(const StringName &p_name, const Variant &p_value);
+	Variant get_instance_shader_parameter(const StringName &p_name) const;
+
 	virtual void set_use_parent_material(bool p_use_parent_material);
 	bool get_use_parent_material() const;
 

+ 186 - 0
servers/rendering/instance_uniforms.cpp

@@ -0,0 +1,186 @@
+/**************************************************************************/
+/*  instance_uniforms.cpp                                                 */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "instance_uniforms.h"
+
+#include "rendering_server_globals.h"
+
+void InstanceUniforms::free(RID p_self) {
+	ERR_FAIL_COND(p_self.is_null());
+
+	if (is_allocated()) {
+		RSG::material_storage->global_shader_parameters_instance_free(p_self);
+		_location = -1;
+	}
+
+	_invalidate_items();
+}
+
+void InstanceUniforms::materials_start() {
+	_invalidate_items();
+}
+
+void InstanceUniforms::materials_append(RID p_material) {
+	ERR_FAIL_COND(p_material.is_null());
+
+	List<RendererMaterialStorage::InstanceShaderParam> params;
+	RSG::material_storage->material_get_instance_shader_parameters(p_material, &params);
+
+	for (const RendererMaterialStorage::InstanceShaderParam &srcp : params) {
+		StringName name = srcp.info.name;
+		if (Item *ptr = _parameters.getptr(name); ptr) {
+			if (!ptr->is_valid()) {
+				_init_param(*ptr, srcp);
+			} else if (ptr->index != srcp.index) {
+				WARN_PRINT("More than one material in instance export the same instance shader uniform '" + srcp.info.name +
+						"', but they do it with different indices. Only the first one (in order) will display correctly.");
+			} else if (ptr->info.type != srcp.info.type) {
+				WARN_PRINT("More than one material in instance export the same instance shader uniform '" + srcp.info.name +
+						"', but they do it with different data types. Only the first one (in order) will display correctly.");
+			}
+		} else {
+			Item i;
+			_init_param(i, srcp);
+			_parameters[name] = i;
+		}
+	}
+}
+
+bool InstanceUniforms::materials_finish(RID p_self) {
+	ERR_FAIL_COND_V(p_self.is_null(), false);
+
+	if (_parameters.is_empty()) {
+		if (is_allocated()) {
+			free(p_self);
+			return true;
+		}
+		return false;
+	}
+
+	const bool should_alloc = !is_allocated();
+
+	if (should_alloc) {
+		_location = RSG::material_storage->global_shader_parameters_instance_allocate(p_self);
+	}
+
+	for (KeyValue<StringName, Item> &kv : _parameters) {
+		Item &i = kv.value;
+		if (i.is_valid()) {
+			RSG::material_storage->global_shader_parameters_instance_update(p_self, i.index, i.value, i.flags);
+		}
+	}
+
+	return should_alloc;
+}
+
+Variant InstanceUniforms::get(const StringName &p_name) const {
+	if (const Item *ptr = _parameters.getptr(p_name); ptr) {
+		return ptr->value;
+	}
+	return Variant();
+}
+
+void InstanceUniforms::set(RID p_self, const StringName &p_name, const Variant &p_value) {
+	ERR_FAIL_COND(p_self.is_null());
+	ERR_FAIL_COND(p_value.get_type() == Variant::OBJECT);
+
+	if (Item *ptr = _parameters.getptr(p_name); ptr) {
+		ptr->value = p_value;
+		if (ptr->is_valid()) {
+			RSG::material_storage->global_shader_parameters_instance_update(p_self, ptr->index, ptr->value, ptr->flags);
+		}
+	} else {
+		Item i; // Initialize in materials_finish.
+		i.value = p_value;
+		_parameters[p_name] = i;
+	}
+}
+
+Variant InstanceUniforms::get_default(const StringName &p_name) const {
+	if (const Item *ptr = _parameters.getptr(p_name); ptr) {
+		return ptr->default_value;
+	}
+	return Variant();
+}
+
+void InstanceUniforms::get_property_list(List<PropertyInfo> &r_parameters) const {
+	Vector<StringName> names;
+
+	// Invalid items won't be saved, but will remain in memory in case of shader compilation failure.
+	for (const KeyValue<StringName, Item> &kv : _parameters) {
+		if (kv.value.is_valid()) {
+			names.push_back(kv.key);
+		}
+	}
+
+	names.sort_custom<StringName::AlphCompare>();
+
+	for (const StringName &n : names) {
+		PropertyInfo pinfo = _parameters[n].info;
+		r_parameters.push_back(pinfo);
+	}
+}
+
+void InstanceUniforms::_init_param(Item &r_item, const RendererMaterialStorage::InstanceShaderParam &p_param) const {
+	r_item.index = p_param.index;
+	r_item.flags = 0;
+	r_item.info = p_param.info;
+	r_item.default_value = p_param.default_value;
+
+	if (r_item.default_value.get_type() == Variant::NIL) {
+		Callable::CallError cerr;
+		Variant::construct(r_item.info.type, r_item.default_value, nullptr, 0, cerr);
+	}
+
+	if (r_item.value.get_type() == Variant::NIL) {
+		r_item.value = r_item.default_value;
+	}
+
+	if (r_item.info.hint == PROPERTY_HINT_FLAGS) {
+		// HACK: Detect boolean flags count and prevent overhead.
+		switch (r_item.info.hint_string.length()) {
+			case 3: // "x,y"
+				r_item.flags = 1;
+				break;
+			case 5: // "x,y,z"
+				r_item.flags = 2;
+				break;
+			case 7: // "x,y,z,w"
+				r_item.flags = 3;
+				break;
+		}
+	}
+}
+
+void InstanceUniforms::_invalidate_items() {
+	for (KeyValue<StringName, Item> &kv : _parameters) {
+		kv.value.index = -1;
+	}
+}

+ 73 - 0
servers/rendering/instance_uniforms.h

@@ -0,0 +1,73 @@
+/**************************************************************************/
+/*  instance_uniforms.h                                                   */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef INSTANCE_UNIFORMS_H
+#define INSTANCE_UNIFORMS_H
+
+#include "core/variant/variant.h"
+#include "servers/rendering/storage/material_storage.h"
+
+class InstanceUniforms {
+public:
+	void free(RID p_self);
+
+	void materials_start();
+	void materials_append(RID p_material);
+
+	// Assign location() to instance offset if materials_finish returns true.
+	bool materials_finish(RID p_self);
+
+	Variant get(const StringName &p_name) const;
+	void set(RID p_self, const StringName &p_name, const Variant &p_value);
+
+	Variant get_default(const StringName &p_name) const;
+	void get_property_list(List<PropertyInfo> &r_parameters) const;
+
+	inline int32_t location() const { return _location; }
+	inline bool is_allocated() const { return _location != -1; }
+
+private:
+	struct Item {
+		int32_t index = -1;
+		int32_t flags = 0;
+		Variant value;
+		Variant default_value;
+		PropertyInfo info;
+
+		inline bool is_valid() const { return index != -1; }
+	};
+	int32_t _location = -1;
+	HashMap<StringName, Item> _parameters;
+
+	void _init_param(Item &r_item, const RendererMaterialStorage::InstanceShaderParam &p_param) const;
+	void _invalidate_items();
+};
+
+#endif // INSTANCE_UNIFORMS_H

+ 121 - 5
servers/rendering/renderer_canvas_cull.cpp

@@ -44,6 +44,29 @@
 // while not making lines appear too soft.
 const static float FEATHER_SIZE = 1.25f;
 
+static RendererCanvasCull *_canvas_cull_singleton = nullptr;
+
+void RendererCanvasCull::_dependency_changed(Dependency::DependencyChangedNotification p_notification, DependencyTracker *p_tracker) {
+	Item *item = (Item *)p_tracker->userdata;
+
+	switch (p_notification) {
+		case Dependency::DEPENDENCY_CHANGED_MATERIAL: {
+			_canvas_cull_singleton->_item_queue_update(item, true);
+		} break;
+		default: {
+		} break;
+	}
+}
+
+void RendererCanvasCull::_dependency_deleted(const RID &p_dependency, DependencyTracker *p_tracker) {
+	Item *item = (Item *)p_tracker->userdata;
+
+	if (p_dependency == item->material) {
+		_canvas_cull_singleton->canvas_item_set_material(item->self, RID());
+	}
+	_canvas_cull_singleton->_item_queue_update(item, true);
+}
+
 void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info) {
 	RENDER_TIMESTAMP("Cull CanvasItem Tree");
 
@@ -525,6 +548,8 @@ RID RendererCanvasCull::canvas_item_allocate() {
 }
 void RendererCanvasCull::canvas_item_initialize(RID p_rid) {
 	canvas_item_owner.initialize_rid(p_rid);
+	Item *instance = canvas_item_owner.get_or_null(p_rid);
+	instance->self = p_rid;
 }
 
 void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) {
@@ -1844,6 +1869,7 @@ void RendererCanvasCull::canvas_item_clear(RID p_item) {
 	ERR_FAIL_NULL(canvas_item);
 
 	canvas_item->clear();
+
 #ifdef DEBUG_ENABLED
 	if (debug_redraw) {
 		canvas_item->debug_redraw_time = debug_redraw_time;
@@ -1875,6 +1901,7 @@ void RendererCanvasCull::canvas_item_set_material(RID p_item, RID p_material) {
 	ERR_FAIL_NULL(canvas_item);
 
 	canvas_item->material = p_material;
+	_item_queue_update(canvas_item, true);
 }
 
 void RendererCanvasCull::canvas_item_set_use_parent_material(RID p_item, bool p_enable) {
@@ -1882,6 +1909,37 @@ void RendererCanvasCull::canvas_item_set_use_parent_material(RID p_item, bool p_
 	ERR_FAIL_NULL(canvas_item);
 
 	canvas_item->use_parent_material = p_enable;
+	_item_queue_update(canvas_item, true);
+}
+
+void RendererCanvasCull::canvas_item_set_instance_shader_parameter(RID p_item, const StringName &p_parameter, const Variant &p_value) {
+	Item *item = canvas_item_owner.get_or_null(p_item);
+	ERR_FAIL_NULL(item);
+
+	item->instance_uniforms.set(item->self, p_parameter, p_value);
+}
+
+Variant RendererCanvasCull::canvas_item_get_instance_shader_parameter(RID p_item, const StringName &p_parameter) const {
+	const Item *item = const_cast<RendererCanvasCull *>(this)->canvas_item_owner.get_or_null(p_item);
+	ERR_FAIL_NULL_V(item, Variant());
+
+	return item->instance_uniforms.get(p_parameter);
+}
+
+Variant RendererCanvasCull::canvas_item_get_instance_shader_parameter_default_value(RID p_item, const StringName &p_parameter) const {
+	const Item *item = const_cast<RendererCanvasCull *>(this)->canvas_item_owner.get_or_null(p_item);
+	ERR_FAIL_NULL_V(item, Variant());
+
+	return item->instance_uniforms.get_default(p_parameter);
+}
+
+void RendererCanvasCull::canvas_item_get_instance_shader_parameter_list(RID p_item, List<PropertyInfo> *p_parameters) const {
+	ERR_FAIL_NULL(p_parameters);
+	const Item *item = const_cast<RendererCanvasCull *>(this)->canvas_item_owner.get_or_null(p_item);
+	ERR_FAIL_NULL(item);
+	const_cast<RendererCanvasCull *>(this)->update_dirty_items();
+
+	item->instance_uniforms.get_property_list(*p_parameters);
 }
 
 void RendererCanvasCull::canvas_item_set_visibility_notifier(RID p_item, bool p_enable, const Rect2 &p_area, const Callable &p_enter_callable, const Callable &p_exit_callable) {
@@ -2411,6 +2469,63 @@ Rect2 RendererCanvasCull::_debug_canvas_item_get_rect(RID p_item) {
 	return canvas_item->get_rect();
 }
 
+void RendererCanvasCull::_item_queue_update(Item *p_item, bool p_update_dependencies) {
+	if (p_update_dependencies) {
+		p_item->update_dependencies = true;
+	}
+
+	if (!p_item->update_item.in_list()) {
+		_item_update_list.add(&p_item->update_item);
+	}
+}
+
+void RendererCanvasCull::update_dirty_items() {
+	while (_item_update_list.first()) {
+		_update_dirty_item(_item_update_list.first()->self());
+	}
+
+	// Instance updates may affect resources.
+	RSG::utilities->update_dirty_resources();
+}
+
+void RendererCanvasCull::_update_dirty_item(Item *p_item) {
+	if (p_item->update_dependencies) {
+		RID material = p_item->material;
+
+		if (p_item->use_parent_material) {
+			Item *parent = canvas_item_owner.get_or_null(p_item->parent);
+			while (parent != nullptr) {
+				material = parent->material;
+				if (!parent->use_parent_material) {
+					break;
+				}
+				parent = canvas_item_owner.get_or_null(parent->parent);
+			}
+		}
+
+		p_item->dependency_tracker.update_begin();
+
+		p_item->instance_uniforms.materials_start();
+
+		if (material.is_valid()) {
+			p_item->instance_uniforms.materials_append(material);
+			RSG::material_storage->material_update_dependency(material, &p_item->dependency_tracker);
+		}
+
+		if (p_item->instance_uniforms.materials_finish(p_item->self)) {
+			p_item->instance_allocated_shader_uniforms_offset = p_item->instance_uniforms.location();
+		}
+
+		p_item->dependency_tracker.update_end();
+	}
+	_item_update_list.remove(&p_item->update_item);
+	p_item->update_dependencies = false;
+}
+
+void RendererCanvasCull::update() {
+	update_dirty_items();
+}
+
 bool RendererCanvasCull::free(RID p_rid) {
 	if (canvas_owner.owns(p_rid)) {
 		Canvas *canvas = canvas_owner.get_or_null(p_rid);
@@ -2468,11 +2583,9 @@ bool RendererCanvasCull::free(RID p_rid) {
 			visibility_notifier_allocator.free(canvas_item->visibility_notifier);
 		}
 
-		/*
-		if (canvas_item->material) {
-			canvas_item->material->owners.erase(canvas_item);
-		}
-		*/
+		canvas_item_set_material(canvas_item->self, RID());
+		canvas_item->instance_uniforms.free(canvas_item->self);
+		update_dirty_items();
 
 		if (canvas_item->canvas_group != nullptr) {
 			memdelete(canvas_item->canvas_group);
@@ -2634,6 +2747,8 @@ void RendererCanvasCull::InterpolationData::notify_free_canvas_light_occluder(RI
 }
 
 RendererCanvasCull::RendererCanvasCull() {
+	_canvas_cull_singleton = this;
+
 	z_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));
 	z_last_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));
 
@@ -2646,4 +2761,5 @@ RendererCanvasCull::RendererCanvasCull() {
 RendererCanvasCull::~RendererCanvasCull() {
 	memfree(z_list);
 	memfree(z_last_list);
+	_canvas_cull_singleton = nullptr;
 }

+ 30 - 1
servers/rendering/renderer_canvas_cull.h

@@ -34,11 +34,16 @@
 #include "core/templates/paged_allocator.h"
 #include "renderer_compositor.h"
 #include "renderer_viewport.h"
+#include "servers/rendering/instance_uniforms.h"
 
 class RendererCanvasCull {
+	static void _dependency_changed(Dependency::DependencyChangedNotification p_notification, DependencyTracker *p_tracker);
+	static void _dependency_deleted(const RID &p_dependency, DependencyTracker *p_tracker);
+
 public:
 	struct Item : public RendererCanvasRender::Item {
 		RID parent; // canvas it belongs to
+		RID self;
 		List<Item *>::Element *E;
 		int z_index;
 		bool z_relative;
@@ -71,7 +76,14 @@ public:
 
 		VisibilityNotifierData *visibility_notifier = nullptr;
 
-		Item() {
+		DependencyTracker dependency_tracker;
+		InstanceUniforms instance_uniforms;
+		SelfList<Item> update_item;
+
+		bool update_dependencies = false;
+
+		Item() :
+				update_item(this) {
 			children_order_dirty = true;
 			E = nullptr;
 			z_index = 0;
@@ -85,9 +97,16 @@ public:
 			ysort_xform = Transform2D();
 			ysort_index = 0;
 			ysort_parent_abs_z_index = 0;
+
+			dependency_tracker.userdata = this;
+			dependency_tracker.changed_callback = &RendererCanvasCull::_dependency_changed;
+			dependency_tracker.deleted_callback = &RendererCanvasCull::_dependency_deleted;
 		}
 	};
 
+	void _item_queue_update(Item *p_item, bool p_update_dependencies);
+	SelfList<Item>::List _item_update_list;
+
 	struct ItemIndexSort {
 		_FORCE_INLINE_ bool operator()(const Item *p_left, const Item *p_right) const {
 			return p_left->index < p_right->index;
@@ -267,6 +286,11 @@ public:
 
 	void canvas_item_set_use_parent_material(RID p_item, bool p_enable);
 
+	void canvas_item_set_instance_shader_parameter(RID p_item, const StringName &p_parameter, const Variant &p_value);
+	void canvas_item_get_instance_shader_parameter_list(RID p_item, List<PropertyInfo> *p_parameters) const;
+	Variant canvas_item_get_instance_shader_parameter(RID p_item, const StringName &p_parameter) const;
+	Variant canvas_item_get_instance_shader_parameter_default_value(RID p_item, const StringName &p_parameter) const;
+
 	void canvas_item_set_visibility_notifier(RID p_item, bool p_enable, const Rect2 &p_area, const Callable &p_enter_callable, const Callable &p_exit_callable);
 
 	void canvas_item_set_canvas_group_mode(RID p_item, RS::CanvasGroupMode p_mode, float p_clear_margin = 5.0, bool p_fit_empty = false, float p_fit_margin = 0.0, bool p_blur_mipmaps = false);
@@ -281,6 +305,8 @@ public:
 	RID canvas_light_allocate();
 	void canvas_light_initialize(RID p_rid);
 
+	void update();
+
 	void canvas_light_set_mode(RID p_light, RS::CanvasLightMode p_mode);
 	void canvas_light_attach_to_canvas(RID p_light, RID p_canvas);
 	void canvas_light_set_enabled(RID p_light, bool p_enabled);
@@ -344,6 +370,9 @@ public:
 	void canvas_item_set_default_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat);
 
 	void update_visibility_notifiers();
+	void update_dirty_items();
+
+	void _update_dirty_item(Item *p_item);
 
 	Rect2 _debug_canvas_item_get_rect(RID p_item);
 

+ 2 - 0
servers/rendering/renderer_canvas_render.h

@@ -343,6 +343,8 @@ public:
 		RID material;
 		RID skeleton;
 
+		int32_t instance_allocated_shader_uniforms_offset = -1;
+
 		Item *next = nullptr;
 
 		struct CopyBackBuffer {

+ 13 - 11
servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp

@@ -1785,6 +1785,7 @@ RendererCanvasRenderRD::RendererCanvasRenderRD() {
 		actions.base_varying_index = 5;
 
 		actions.global_buffer_array_variable = "global_shader_uniforms.data";
+		actions.instance_uniform_index_variable = "draw_data.instance_uniforms_ofs";
 
 		shader.compiler.initialize(actions);
 	}
@@ -2239,7 +2240,7 @@ void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target
 	state.last_instance_index += instance_index;
 }
 
-RendererCanvasRenderRD::InstanceData *RendererCanvasRenderRD::new_instance_data(float *p_world, uint32_t *p_lights, uint32_t p_base_flags, uint32_t p_index, TextureInfo *p_info) {
+RendererCanvasRenderRD::InstanceData *RendererCanvasRenderRD::new_instance_data(float *p_world, uint32_t *p_lights, uint32_t p_base_flags, uint32_t p_index, uint32_t p_uniforms_ofs, TextureInfo *p_info) {
 	InstanceData *instance_data = &state.instance_data_array[p_index];
 	// Zero out most fields.
 	for (int i = 0; i < 4; i++) {
@@ -2266,7 +2267,7 @@ RendererCanvasRenderRD::InstanceData *RendererCanvasRenderRD::new_instance_data(
 	instance_data->color_texture_pixel_size[0] = p_info->texpixel_size.width;
 	instance_data->color_texture_pixel_size[1] = p_info->texpixel_size.height;
 
-	instance_data->pad1 = 0;
+	instance_data->instance_uniforms_ofs = p_uniforms_ofs;
 
 	return instance_data;
 }
@@ -2284,6 +2285,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 	Color base_color = p_item->final_modulate;
 	bool use_linear_colors = p_render_target.use_linear_colors;
 	uint32_t base_flags = 0;
+	uint32_t uniforms_ofs = static_cast<uint32_t>(p_item->instance_allocated_shader_uniforms_offset);
 
 	bool reclip = false;
 
@@ -2383,7 +2385,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 					r_current_batch->tex_info = tex_info;
 				}
 
-				InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+				InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 				Rect2 src_rect;
 				Rect2 dst_rect;
 
@@ -2484,7 +2486,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 					r_current_batch->tex_info = tex_info;
 				}
 
-				InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+				InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 
 				Rect2 src_rect;
 				Rect2 dst_rect(np->rect.position.x, np->rect.position.y, np->rect.size.x, np->rect.size.y);
@@ -2566,7 +2568,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 					r_current_batch->render_primitive = _primitive_type_to_render_primitive(polygon->primitive);
 				}
 
-				InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+				InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 
 				Color color = base_color;
 				if (use_linear_colors) {
@@ -2626,7 +2628,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 					r_current_batch->tex_info = tex_info;
 				}
 
-				InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+				InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 
 				for (uint32_t j = 0; j < MIN(3u, primitive->point_count); j++) {
 					instance_data->points[j * 2 + 0] = primitive->points[j].x;
@@ -2644,7 +2646,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 				_add_to_batch(r_index, r_batch_broken, r_current_batch);
 
 				if (primitive->point_count == 4) {
-					instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+					instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 
 					for (uint32_t j = 0; j < 3; j++) {
 						int offset = j == 0 ? 0 : 1;
@@ -2687,7 +2689,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 						_prepare_batch_texture_info(m->texture, tex_state, tex_info);
 					}
 					r_current_batch->tex_info = tex_info;
-					instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+					instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 
 					r_current_batch->mesh_instance_count = 1;
 					_update_transform_2d_to_mat2x3(base_transform * draw_transform * m->transform, instance_data->world);
@@ -2714,7 +2716,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 						_prepare_batch_texture_info(mm->texture, tex_state, tex_info);
 					}
 					r_current_batch->tex_info = tex_info;
-					instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+					instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 
 					r_current_batch->flags |= 1; // multimesh, trails disabled
 
@@ -2736,7 +2738,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 						_prepare_batch_texture_info(pt->texture, tex_state, tex_info);
 					}
 					r_current_batch->tex_info = tex_info;
-					instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+					instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 
 					uint32_t divisor = 1;
 					r_current_batch->mesh_instance_count = particles_storage->particles_get_amount(pt->particles, divisor);
@@ -2859,7 +2861,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
 			r_current_batch->tex_info = tex_info;
 		}
 
-		InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, tex_info);
+		InstanceData *instance_data = new_instance_data(world, lights, base_flags, r_index, uniforms_ofs, tex_info);
 
 		Rect2 src_rect;
 		Rect2 dst_rect;

+ 2 - 2
servers/rendering/renderer_rd/renderer_canvas_render_rd.h

@@ -342,7 +342,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
 	struct InstanceData {
 		float world[6];
 		uint32_t flags;
-		uint32_t pad1;
+		uint32_t instance_uniforms_ofs;
 		union {
 			//rect
 			struct {
@@ -605,7 +605,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
 	void _record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, Batch *&r_current_batch);
 	void _render_batch(RD::DrawListID p_draw_list, CanvasShaderData *p_shader_data, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info = nullptr);
 	void _prepare_batch_texture_info(RID p_texture, TextureState &p_state, TextureInfo *p_info);
-	InstanceData *new_instance_data(float *p_world, uint32_t *p_lights, uint32_t p_base_flags, uint32_t p_index, TextureInfo *p_info);
+	InstanceData *new_instance_data(float *p_world, uint32_t *p_lights, uint32_t p_base_flags, uint32_t p_index, uint32_t p_uniforms_ofs, TextureInfo *p_info);
 	[[nodiscard]] Batch *_new_batch(bool &r_batch_broken);
 	void _add_to_batch(uint32_t &r_index, bool &r_batch_broken, Batch *&r_current_batch);
 	void _allocate_instance_buffer();

+ 1 - 1
servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl

@@ -23,7 +23,7 @@ struct InstanceData {
 	vec2 world_y;
 	vec2 world_ofs;
 	uint flags;
-	uint pad2;
+	uint instance_uniforms_ofs;
 #ifdef USE_PRIMITIVE
 	vec2 points[3];
 	vec2 uvs[3];

+ 17 - 123
servers/rendering/renderer_scene_cull.cpp

@@ -701,7 +701,7 @@ void RendererSceneCull::instance_set_base(RID p_instance, RID p_base) {
 				geom->geometry_instance->set_use_baked_light(instance->baked_light);
 				geom->geometry_instance->set_use_dynamic_gi(instance->dynamic_gi);
 				geom->geometry_instance->set_use_lightmap(RID(), instance->lightmap_uv_scale, instance->lightmap_slice_index);
-				geom->geometry_instance->set_instance_shader_uniforms_offset(instance->instance_allocated_shader_uniforms_offset);
+				geom->geometry_instance->set_instance_shader_uniforms_offset(instance->instance_uniforms.location());
 				geom->geometry_instance->set_cast_double_sided_shadows(instance->cast_shadows == RS::SHADOW_CASTING_SETTING_DOUBLE_SIDED);
 				if (instance->lightmap_sh.size() == 9) {
 					geom->geometry_instance->set_lightmap_capture(instance->lightmap_sh.ptr());
@@ -1636,58 +1636,21 @@ void RendererSceneCull::instance_geometry_set_shader_parameter(RID p_instance, c
 	Instance *instance = instance_owner.get_or_null(p_instance);
 	ERR_FAIL_NULL(instance);
 
-	ERR_FAIL_COND(p_value.get_type() == Variant::OBJECT);
-
-	HashMap<StringName, Instance::InstanceShaderParameter>::Iterator E = instance->instance_shader_uniforms.find(p_parameter);
-
-	if (!E) {
-		Instance::InstanceShaderParameter isp;
-		isp.index = -1;
-		isp.info = PropertyInfo();
-		isp.value = p_value;
-		instance->instance_shader_uniforms[p_parameter] = isp;
-	} else {
-		E->value.value = p_value;
-		if (E->value.index >= 0 && instance->instance_allocated_shader_uniforms) {
-			int flags_count = 0;
-			if (E->value.info.hint == PROPERTY_HINT_FLAGS) {
-				// A small hack to detect boolean flags count and prevent overhead.
-				switch (E->value.info.hint_string.length()) {
-					case 3: // "x,y"
-						flags_count = 1;
-						break;
-					case 5: // "x,y,z"
-						flags_count = 2;
-						break;
-					case 7: // "x,y,z,w"
-						flags_count = 3;
-						break;
-				}
-			}
-			//update directly
-			RSG::material_storage->global_shader_parameters_instance_update(p_instance, E->value.index, p_value, flags_count);
-		}
-	}
+	instance->instance_uniforms.set(instance->self, p_parameter, p_value);
 }
 
 Variant RendererSceneCull::instance_geometry_get_shader_parameter(RID p_instance, const StringName &p_parameter) const {
 	const Instance *instance = instance_owner.get_or_null(p_instance);
 	ERR_FAIL_NULL_V(instance, Variant());
 
-	if (instance->instance_shader_uniforms.has(p_parameter)) {
-		return instance->instance_shader_uniforms[p_parameter].value;
-	}
-	return Variant();
+	return instance->instance_uniforms.get(p_parameter);
 }
 
 Variant RendererSceneCull::instance_geometry_get_shader_parameter_default_value(RID p_instance, const StringName &p_parameter) const {
 	const Instance *instance = instance_owner.get_or_null(p_instance);
 	ERR_FAIL_NULL_V(instance, Variant());
 
-	if (instance->instance_shader_uniforms.has(p_parameter)) {
-		return instance->instance_shader_uniforms[p_parameter].default_value;
-	}
-	return Variant();
+	return instance->instance_uniforms.get_default(p_parameter);
 }
 
 void RendererSceneCull::mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) {
@@ -1699,20 +1662,13 @@ uint32_t RendererSceneCull::get_pipeline_compilations(RS::PipelineSource p_sourc
 }
 
 void RendererSceneCull::instance_geometry_get_shader_parameter_list(RID p_instance, List<PropertyInfo> *p_parameters) const {
+	ERR_FAIL_NULL(p_parameters);
 	const Instance *instance = instance_owner.get_or_null(p_instance);
 	ERR_FAIL_NULL(instance);
 
 	update_dirty_instances();
 
-	Vector<StringName> names;
-	for (const KeyValue<StringName, Instance::InstanceShaderParameter> &E : instance->instance_shader_uniforms) {
-		names.push_back(E.key);
-	}
-	names.sort_custom<StringName::AlphCompare>();
-	for (int i = 0; i < names.size(); i++) {
-		PropertyInfo pinfo = instance->instance_shader_uniforms[names[i]].info;
-		p_parameters->push_back(pinfo);
-	}
+	instance->instance_uniforms.get_property_list(*p_parameters);
 }
 
 void RendererSceneCull::_update_instance(Instance *p_instance) const {
@@ -4047,34 +4003,6 @@ void RendererSceneCull::render_particle_colliders() {
 	}
 }
 
-void RendererSceneCull::_update_instance_shader_uniforms_from_material(HashMap<StringName, Instance::InstanceShaderParameter> &isparams, const HashMap<StringName, Instance::InstanceShaderParameter> &existing_isparams, RID p_material) const {
-	List<RendererMaterialStorage::InstanceShaderParam> plist;
-	RSG::material_storage->material_get_instance_shader_parameters(p_material, &plist);
-	for (const RendererMaterialStorage::InstanceShaderParam &E : plist) {
-		StringName name = E.info.name;
-		if (isparams.has(name)) {
-			if (isparams[name].info.type != E.info.type) {
-				WARN_PRINT("More than one material in instance export the same instance shader uniform '" + E.info.name + "', but they do it with different data types. Only the first one (in order) will display correctly.");
-			}
-			if (isparams[name].index != E.index) {
-				WARN_PRINT("More than one material in instance export the same instance shader uniform '" + E.info.name + "', but they do it with different indices. Only the first one (in order) will display correctly.");
-			}
-			continue; //first one found always has priority
-		}
-
-		Instance::InstanceShaderParameter isp;
-		isp.index = E.index;
-		isp.info = E.info;
-		isp.default_value = E.default_value;
-		if (existing_isparams.has(name)) {
-			isp.value = existing_isparams[name].value;
-		} else {
-			isp.value = E.default_value;
-		}
-		isparams[name] = isp;
-	}
-}
-
 void RendererSceneCull::_update_dirty_instance(Instance *p_instance) const {
 	if (p_instance->update_aabb) {
 		_update_instance_aabb(p_instance);
@@ -4118,7 +4046,8 @@ void RendererSceneCull::_update_dirty_instance(Instance *p_instance) const {
 
 			bool can_cast_shadows = true;
 			bool is_animated = false;
-			HashMap<StringName, Instance::InstanceShaderParameter> isparams;
+
+			p_instance->instance_uniforms.materials_start();
 
 			if (p_instance->cast_shadows == RS::SHADOW_CASTING_SETTING_OFF) {
 				can_cast_shadows = false;
@@ -4129,7 +4058,7 @@ void RendererSceneCull::_update_dirty_instance(Instance *p_instance) const {
 					can_cast_shadows = false;
 				}
 				is_animated = RSG::material_storage->material_is_animated(p_instance->material_override);
-				_update_instance_shader_uniforms_from_material(isparams, p_instance->instance_shader_uniforms, p_instance->material_override);
+				p_instance->instance_uniforms.materials_append(p_instance->material_override);
 			} else {
 				if (p_instance->base_type == RS::INSTANCE_MESH) {
 					RID mesh = p_instance->base;
@@ -4151,7 +4080,7 @@ void RendererSceneCull::_update_dirty_instance(Instance *p_instance) const {
 									is_animated = true;
 								}
 
-								_update_instance_shader_uniforms_from_material(isparams, p_instance->instance_shader_uniforms, mat);
+								p_instance->instance_uniforms.materials_append(mat);
 
 								RSG::material_storage->material_update_dependency(mat, &p_instance->dependency_tracker);
 							}
@@ -4182,7 +4111,7 @@ void RendererSceneCull::_update_dirty_instance(Instance *p_instance) const {
 									is_animated = true;
 								}
 
-								_update_instance_shader_uniforms_from_material(isparams, p_instance->instance_shader_uniforms, mat);
+								p_instance->instance_uniforms.materials_append(mat);
 
 								RSG::material_storage->material_update_dependency(mat, &p_instance->dependency_tracker);
 							}
@@ -4220,7 +4149,7 @@ void RendererSceneCull::_update_dirty_instance(Instance *p_instance) const {
 									is_animated = true;
 								}
 
-								_update_instance_shader_uniforms_from_material(isparams, p_instance->instance_shader_uniforms, mat);
+								p_instance->instance_uniforms.materials_append(mat);
 
 								RSG::material_storage->material_update_dependency(mat, &p_instance->dependency_tracker);
 							}
@@ -4236,7 +4165,7 @@ void RendererSceneCull::_update_dirty_instance(Instance *p_instance) const {
 			if (p_instance->material_overlay.is_valid()) {
 				can_cast_shadows = can_cast_shadows && RSG::material_storage->material_casts_shadows(p_instance->material_overlay);
 				is_animated = is_animated || RSG::material_storage->material_is_animated(p_instance->material_overlay);
-				_update_instance_shader_uniforms_from_material(isparams, p_instance->instance_shader_uniforms, p_instance->material_overlay);
+				p_instance->instance_uniforms.materials_append(p_instance->material_overlay);
 			}
 
 			if (can_cast_shadows != geom->can_cast_shadows) {
@@ -4250,41 +4179,9 @@ void RendererSceneCull::_update_dirty_instance(Instance *p_instance) const {
 			}
 
 			geom->material_is_animated = is_animated;
-			p_instance->instance_shader_uniforms = isparams;
-
-			if (p_instance->instance_allocated_shader_uniforms != (p_instance->instance_shader_uniforms.size() > 0)) {
-				p_instance->instance_allocated_shader_uniforms = (p_instance->instance_shader_uniforms.size() > 0);
-				if (p_instance->instance_allocated_shader_uniforms) {
-					p_instance->instance_allocated_shader_uniforms_offset = RSG::material_storage->global_shader_parameters_instance_allocate(p_instance->self);
-					ERR_FAIL_NULL(geom->geometry_instance);
-					geom->geometry_instance->set_instance_shader_uniforms_offset(p_instance->instance_allocated_shader_uniforms_offset);
-
-					for (const KeyValue<StringName, Instance::InstanceShaderParameter> &E : p_instance->instance_shader_uniforms) {
-						if (E.value.value.get_type() != Variant::NIL) {
-							int flags_count = 0;
-							if (E.value.info.hint == PROPERTY_HINT_FLAGS) {
-								// A small hack to detect boolean flags count and prevent overhead.
-								switch (E.value.info.hint_string.length()) {
-									case 3: // "x,y"
-										flags_count = 1;
-										break;
-									case 5: // "x,y,z"
-										flags_count = 2;
-										break;
-									case 7: // "x,y,z,w"
-										flags_count = 3;
-										break;
-								}
-							}
-							RSG::material_storage->global_shader_parameters_instance_update(p_instance->self, E.value.index, E.value.value, flags_count);
-						}
-					}
-				} else {
-					RSG::material_storage->global_shader_parameters_instance_free(p_instance->self);
-					p_instance->instance_allocated_shader_uniforms_offset = -1;
-					ERR_FAIL_NULL(geom->geometry_instance);
-					geom->geometry_instance->set_instance_shader_uniforms_offset(-1);
-				}
+
+			if (p_instance->instance_uniforms.materials_finish(p_instance->self)) {
+				geom->geometry_instance->set_instance_shader_uniforms_offset(p_instance->instance_uniforms.location());
 			}
 		}
 
@@ -4379,10 +4276,7 @@ bool RendererSceneCull::free(RID p_rid) {
 		instance_geometry_set_material_overlay(p_rid, RID());
 		instance_attach_skeleton(p_rid, RID());
 
-		if (instance->instance_allocated_shader_uniforms) {
-			//free the used shader parameters
-			RSG::material_storage->global_shader_parameters_instance_free(instance->self);
-		}
+		instance->instance_uniforms.free(instance->self);
 		update_dirty_instances(); //in case something changed this
 
 		instance_owner.free(p_rid);

+ 2 - 12
servers/rendering/renderer_scene_cull.h

@@ -40,6 +40,7 @@
 #include "core/templates/pass_func.h"
 #include "core/templates/rid_owner.h"
 #include "core/templates/self_list.h"
+#include "servers/rendering/instance_uniforms.h"
 #include "servers/rendering/renderer_scene_occlusion_cull.h"
 #include "servers/rendering/renderer_scene_render.h"
 #include "servers/rendering/rendering_method.h"
@@ -460,16 +461,7 @@ public:
 		AABB transformed_aabb;
 		AABB prev_transformed_aabb;
 
-		struct InstanceShaderParameter {
-			int32_t index = -1;
-			Variant value;
-			Variant default_value;
-			PropertyInfo info;
-		};
-
-		HashMap<StringName, InstanceShaderParameter> instance_shader_uniforms;
-		bool instance_allocated_shader_uniforms = false;
-		int32_t instance_allocated_shader_uniforms_offset = -1;
+		InstanceUniforms instance_uniforms;
 
 		//
 
@@ -1095,8 +1087,6 @@ public:
 	virtual void instance_geometry_set_lightmap(RID p_instance, RID p_lightmap, const Rect2 &p_lightmap_uv_scale, int p_slice_index);
 	virtual void instance_geometry_set_lod_bias(RID p_instance, float p_lod_bias);
 
-	void _update_instance_shader_uniforms_from_material(HashMap<StringName, Instance::InstanceShaderParameter> &isparams, const HashMap<StringName, Instance::InstanceShaderParameter> &existing_isparams, RID p_material) const;
-
 	virtual void instance_geometry_set_shader_parameter(RID p_instance, const StringName &p_parameter, const Variant &p_value);
 	virtual void instance_geometry_get_shader_parameter_list(RID p_instance, List<PropertyInfo> *p_parameters) const;
 	virtual Variant instance_geometry_get_shader_parameter(RID p_instance, const StringName &p_parameter) const;

+ 1 - 0
servers/rendering/rendering_server_default.cpp

@@ -77,6 +77,7 @@ void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) {
 
 	RENDER_TIMESTAMP("Prepare Render Frame");
 	RSG::scene->update(); //update scenes stuff before updating instances
+	RSG::canvas->update();
 
 	frame_setup_time = double(OS::get_singleton()->get_ticks_usec() - time_usec) / 1000.0;
 

+ 5 - 0
servers/rendering/rendering_server_default.h

@@ -990,6 +990,11 @@ public:
 
 	FUNC2(canvas_item_set_material, RID, RID)
 
+	FUNC3(canvas_item_set_instance_shader_parameter, RID, const StringName &, const Variant &)
+	FUNC2RC(Variant, canvas_item_get_instance_shader_parameter, RID, const StringName &)
+	FUNC2RC(Variant, canvas_item_get_instance_shader_parameter_default_value, RID, const StringName &)
+	FUNC2C(canvas_item_get_instance_shader_parameter_list, RID, List<PropertyInfo> *)
+
 	FUNC2(canvas_item_set_use_parent_material, RID, bool)
 
 	FUNC5(canvas_item_set_visibility_notifier, RID, bool, const Rect2 &, const Callable &, const Callable &)

+ 1 - 1
servers/rendering/shader_language.cpp

@@ -9143,7 +9143,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
 						}
 					}
 #endif // DEBUG_ENABLED
-					if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") {
+					if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial" && String(shader_type_identifier) != "canvas_item") {
 						_set_error(vformat(RTR("Uniform instances are not yet implemented for '%s' shaders."), shader_type_identifier));
 						return ERR_PARSE_ERROR;
 					}

+ 11 - 0
servers/rendering_server.cpp

@@ -2052,6 +2052,12 @@ TypedArray<Dictionary> RenderingServer::_instance_geometry_get_shader_parameter_
 	return convert_property_list(&params);
 }
 
+TypedArray<Dictionary> RenderingServer::_canvas_item_get_instance_shader_parameter_list(RID p_instance) const {
+	List<PropertyInfo> params;
+	canvas_item_get_instance_shader_parameter_list(p_instance, &params);
+	return convert_property_list(&params);
+}
+
 TypedArray<Image> RenderingServer::_bake_render_uv2(RID p_base, const TypedArray<RID> &p_material_overrides, const Size2i &p_image_size) {
 	TypedArray<RID> mat_overrides;
 	for (int i = 0; i < p_material_overrides.size(); i++) {
@@ -3291,6 +3297,11 @@ void RenderingServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("canvas_item_set_material", "item", "material"), &RenderingServer::canvas_item_set_material);
 	ClassDB::bind_method(D_METHOD("canvas_item_set_use_parent_material", "item", "enabled"), &RenderingServer::canvas_item_set_use_parent_material);
 
+	ClassDB::bind_method(D_METHOD("canvas_item_set_instance_shader_parameter", "instance", "parameter", "value"), &RenderingServer::canvas_item_set_instance_shader_parameter);
+	ClassDB::bind_method(D_METHOD("canvas_item_get_instance_shader_parameter", "instance", "parameter"), &RenderingServer::canvas_item_get_instance_shader_parameter);
+	ClassDB::bind_method(D_METHOD("canvas_item_get_instance_shader_parameter_default_value", "instance", "parameter"), &RenderingServer::canvas_item_get_instance_shader_parameter_default_value);
+	ClassDB::bind_method(D_METHOD("canvas_item_get_instance_shader_parameter_list", "instance"), &RenderingServer::_canvas_item_get_instance_shader_parameter_list);
+
 	ClassDB::bind_method(D_METHOD("canvas_item_set_visibility_notifier", "item", "enable", "area", "enter_callable", "exit_callable"), &RenderingServer::canvas_item_set_visibility_notifier);
 	ClassDB::bind_method(D_METHOD("canvas_item_set_canvas_group_mode", "item", "mode", "clear_margin", "fit_empty", "fit_margin", "blur_mipmaps"), &RenderingServer::canvas_item_set_canvas_group_mode, DEFVAL(5.0), DEFVAL(false), DEFVAL(0.0), DEFVAL(false));
 

+ 6 - 0
servers/rendering_server.h

@@ -1546,6 +1546,11 @@ public:
 
 	virtual void canvas_item_set_use_parent_material(RID p_item, bool p_enable) = 0;
 
+	virtual void canvas_item_set_instance_shader_parameter(RID p_item, const StringName &, const Variant &p_value) = 0;
+	virtual Variant canvas_item_get_instance_shader_parameter(RID p_item, const StringName &) const = 0;
+	virtual Variant canvas_item_get_instance_shader_parameter_default_value(RID p_item, const StringName &) const = 0;
+	virtual void canvas_item_get_instance_shader_parameter_list(RID p_item, List<PropertyInfo> *p_parameters) const = 0;
+
 	virtual void canvas_item_set_visibility_notifier(RID p_item, bool p_enable, const Rect2 &p_area, const Callable &p_enter_callbable, const Callable &p_exit_callable) = 0;
 
 	enum CanvasGroupMode {
@@ -1832,6 +1837,7 @@ private:
 	void _mesh_add_surface(RID p_mesh, const Dictionary &p_surface);
 	Dictionary _mesh_get_surface(RID p_mesh, int p_idx);
 	TypedArray<Dictionary> _instance_geometry_get_shader_parameter_list(RID p_instance) const;
+	TypedArray<Dictionary> _canvas_item_get_instance_shader_parameter_list(RID p_item) const;
 	TypedArray<Image> _bake_render_uv2(RID p_base, const TypedArray<RID> &p_material_overrides, const Size2i &p_image_size);
 	void _particles_set_trail_bind_poses(RID p_particles, const TypedArray<Transform3D> &p_bind_poses);
 #ifdef TOOLS_ENABLED