How to make a Decal notice that its texture changed

Godot Version

4.5.1
Forward+

Question

Hi everyone,

I am developing a real-time spray-painting mechanic using Decals in Godot 4.x. I’ve encountered a significant bottleneck where the only method that successfully renders updates in-game is also causing severe performance degradation and eventual GPU driver hangups.

The Problem

To reflect CPU-side Image changes on a Decal, I am forced to use: texture_albedo = ImageTexture.create_from_image(canvas_image)

While this works visually, it is a destructive workaround for a real-time use case. On integrated graphics (Intel Iris/HD), calling this at 20-30 FPS causes:

  1. Frame Stutters: Frequent spikes, likely due to Godot re-packing the global Decal Atlas and generating new RIDs.

  2. Driver Timeout (TDR): After 30-60 seconds of continuous painting, the GPU hangs/crashes, despite no detectable spikes in VRAM or total texture memory usage.

The “Ghost Update” Issue

I have attempted more performant, “in-place” update methods to avoid generating new RIDs, but they fail to update the Decal visually:

  • RenderingServer.texture_2d_update(): The data reaches the GPU (verified via image.save_png() and checking the texture in the inspector), but the Decal itself remains unchanged or invisible in the 3D world.

  • canvas_texture.update(canvas_image): Similar to the RenderingServer method, the underlying resource updates, but the Decal does not refresh its visual representation.

It appears that Decals only “notice” a texture change if the texture_albedo property is assigned an entirely new ImageTexture object, which triggers the expensive Atlas re-bake.

Specific Question

Is there a supported way to update a Decal’s texture data in-place without creating a new RID or forcing a full Atlas re-pack?

Specifically, how can I notify the Decal/RenderingServer that the existing texture_albedo RID has new data so it can refresh its position in the Atlas efficiently, rather than treating it as a brand new resource?

You should be able to get the actual texture via Decal::get_texture(). Note that RenderingServer::texture_2d_update() will still cause bottleneck especially if called every frame as it involves uploading the data from the cpu address space.

I dont want to get the texture. I want the Decal to update visually in the 3D world, when the texture has changed(through my code) without the workaround of attaching a new texture resource with a different RID.

Well change that texture instead of a specific assigned texture.

Have in mind that if you need to do that frequently, decals might not be the best option unless you change the texture directly on the gpu via a compute shader.

Yeah I am changing that texture but that doesnt reflect on the Decal. The Decal only updates if I give it a new texture. But I guess than Decals really are not the right thing to use for frequent updates

You are updating the texture data gotten from Decal::get_texture() on the rendering server and the change doesn’t show up?

Well I change the albedo_texture. And if I save it as a png it reflects the changes but not in the game without somehow giving it a new RID.

That’s not the same. get_texture() should retrieve the actual atlas that’s used for drawing the decal. Use its RID on the server to change the texture data.

Hm, scrap my previous post. It looks like it’s not possible to get the atlas RID in script. If you need frequent updates, Decal nodes are not the way to go.

Okay, thanks for clarifying that. Guess I have to find a different way