Repository: godotengine/godot (language: cpp)

Issue:
[4.4beta1] Script modification of CanvasTexture doesn't update
### Tested versions

v4.4.beta1.official [d33da79d3]

### System information

Godot v4.4.beta1 - Windows 10 (build 19045) - Multi-window, 1 monitor - Vulkan (Mobile) - dedicated AMD Radeon RX 6600 (Advanced Micro Devices, Inc.; 32.0.11027.1003) - AMD Ryzen 5 3600 6-Core Processor (12 threads)

### Issue description

Altering the `.diffuse_texture` of a CanvasTexture at runtime via script causes no visual update. This causes my player character to no longer turn around.

### Steps to reproduce

Modify the `.diffuse_texture` of a CanvasTexture via script. It doesn't change visually.

### Minimal reproduction project (MRP)

https://github.com/EmilyV99/godot-bug-examples/tree/texture-diffuse-mrp

Pressing left/right changes the diffuse texture to face the pressed direction. Nothing happens at all visually.


Discussion / hints:
possibly related to #101702 
Ah, it isn't just script modification either- using an `AnimatedTexture` as a diffuse texture fails to animate at all. So yeah, CanvasTextures seem to just be caching something and never updating it for any reason whatsoever.

Fix patch:
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 02e2486fb9c9..de376599de4c 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -2971,8 +2971,23 @@ void RendererCanvasRenderRD::_uniform_set_invalidation_callback(void *p_userdata
 	static_cast<RendererCanvasRenderRD *>(singleton)->rid_set_to_uniform_set.erase(*key);
 }
 
+void RendererCanvasRenderRD::_canvas_texture_invalidation_callback(bool p_deleted, void *p_userdata) {
+	KeyValue<RID, TightLocalVector<RID>> *kv = static_cast<KeyValue<RID, TightLocalVector<RID>> *>(p_userdata);
+	RD *rd = RD::get_singleton();
+	for (RID rid : kv->value) {
+		// the invalidation callback will take care of clearing rid_set_to_uniform_set cache also
+		rd->free(rid);
+	}
+	kv->value.clear();
+	if (p_deleted) {
+		static_cast<RendererCanvasRenderRD *>(singleton)->canvas_texture_to_uniform_set.erase(kv->key);
+	}
+}
+
 void RendererCanvasRenderRD::_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) {
 	{
+		RendererRD::TextureStorage *ts = RendererRD::TextureStorage::get_singleton();
+
 		RIDSetKey key(
 				p_batch->tex_info->state,
 				state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
@@ -2992,6 +3007,19 @@ void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, CanvasSha
 			const RIDCache::Pair *iter = rid_set_to_uniform_set.insert(key, rid);
 			uniform_set = &iter->data;
 			RD::get_singleton()->uniform_set_set_invalidation_callback(rid, RendererCanvasRenderRD::_uniform_set_invalidation_callback, (void *)&iter->key);
+
+			// If this is a CanvasTexture, it must be tracked so that any changes to the diffuse, normal
+			// or specular channels invalidate all associated uniform sets.
+			if (ts->owns_canvas_texture(p_batch->tex_info->state.texture)) {
+				KeyValue<RID, TightLocalVector<RID>> *kv = nullptr;
+				if (HashMap<RID, TightLocalVector<RID>>::Iterator i = canvas_texture_to_uniform_set.find(p_batch->tex_info->state.texture); i == canvas_texture_to_uniform_set.end()) {
+					kv = &*canvas_texture_to_uniform_set.insert(p_batch->tex_info->state.texture, { *uniform_set });
+				} else {
+					i->value.push_back(rid);
+					kv = &*i;
+				}
+				ts->canvas_texture_set_invalidation_callback(p_batch->tex_info->state.texture, RendererCanvasRenderRD::_canvas_texture_invalidation_callback, kv);
+			}
 		}
 
 		if (state.current_batch_uniform_set != *uniform_set) {
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index 3b071cd804a1..0605b18ba44e 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -485,9 +485,14 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
 
 	static void _before_evict(RendererCanvasRenderRD::RIDSetKey &p_key, RID &p_rid);
 	static void _uniform_set_invalidation_callback(void *p_userdata);
+	static void _canvas_texture_invalidation_callback(bool p_deleted, void *p_userdata);
 
 	typedef LRUCache<RIDSetKey, RID, HashableHasher<RIDSetKey>, HashMapComparatorDefault<RIDSetKey>, _before_evict> RIDCache;
 	RIDCache rid_set_to_uniform_set;
+	/// Maps a CanvasTexture to its associated uniform sets, which must
+	/// be invalidated when the CanvasTexture is updated, such as changing the
+	/// diffuse texture.
+	HashMap<RID, TightLocalVector<RID>> canvas_texture_to_uniform_set;
 
 	struct Batch {
 		// Position in the UBO measured in bytes
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index a297b9717816..69ae3d971a4e 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -43,9 +43,15 @@ using namespace RendererRD;
 void TextureStorage::CanvasTexture::clear_cache() {
 	info_cache[0] = CanvasTextureCache();
 	info_cache[1] = CanvasTextureCache();
+	if (invalidated_callback != nullptr) {
+		invalidated_callback(false, invalidated_callback_userdata);
+	}
 }
 
 TextureStorage::CanvasTexture::~CanvasTexture() {
+	if (invalidated_callback != nullptr) {
+		invalidated_callback(true, invalidated_callback_userdata);
+	}
 }
 
 ///////////////////////////////////////////////////////////////////////////
@@ -735,6 +741,16 @@ TextureStorage::CanvasTextureInfo TextureStorage::canvas_texture_get_info(RID p_
 	return res;
 }
 
+void TextureStorage::canvas_texture_set_invalidation_callback(RID p_canvas_texture, InvalidationCallback p_callback, void *p_userdata) {
+	CanvasTexture *ct = canvas_texture_owner.get_or_null(p_canvas_texture);
+	if (!ct) {
+		return;
+	}
+
+	ct->invalidated_callback = p_callback;
+	ct->invalidated_callback_userdata = p_userdata;
+}
+
 /* Texture API */
 
 RID TextureStorage::texture_allocate() {
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
index bfee1e367e0c..2073c34ded7b 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
@@ -90,6 +90,8 @@ class TextureStorage : public RendererTextureStorage {
 		_FORCE_INLINE_ bool is_null() const { return diffuse.is_null(); }
 	};
 
+	typedef void (*InvalidationCallback)(bool p_deleted, void *p_userdata);
+
 private:
 	friend class LightStorage;
 	friend class MaterialStorage;
@@ -118,6 +120,9 @@ class TextureStorage : public RendererTextureStorage {
 		RS::CanvasItemTextureRepeat texture_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT;
 		CanvasTextureCache info_cache[2];
 
+		InvalidationCallback invalidated_callback = nullptr;
+		void *invalidated_callback_userdata = nullptr;
+
 		Size2i size_cache = Size2i(1, 1);
 		bool use_normal_cache = false;
 		bool use_specular_cache = false;
@@ -499,6 +504,7 @@ class TextureStorage : public RendererTextureStorage {
 	virtual void canvas_texture_set_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat) override;
 
 	CanvasTextureInfo canvas_texture_get_info(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, bool p_use_srgb, bool p_texture_is_data);
+	void canvas_texture_set_invalidation_callback(RID p_canvas_texture, InvalidationCallback p_callback, void *p_userdata);
 
 	/* Texture API */
 


Test patch: