Optimizing keeping rendered texture to the next frame in compositor effect

Godot Version

v4.7.2.stable.arch_linux [ed1daf0bf]

Question

Hi, I have this one project that creates a temporal effect to mimic a common JPEG compression error known colloquially as datamoshing.

It is pretty much complete, but there’s one blatant problem: The performance is downright awful. I’ve found several ways of improving performance that aren’t currently implemented in the project yet (only writing to relevant texels in both main and auxiliary shaders, for one), but one still eludes me, and that is how to efficiently preserve the main texture to the next frame.

One way that I found that works, but is awfully performant is to create a separate texture in memory with RenderingDevice::texture_create(), and write the screen texture twice, once to the screen, and once to the auxiliary texture, within the glsl shader. I want to change this part.

I saw this other project that also does datamoshing, but with a very different approach, that isn’t designed to be standalone. It uses RenderSceneBuffersRD::create_texture() instead, but I haven’t tried that yet, since it’s really late and I should be sleeping, and I only just found that out while researching to write this post. I will test that tomorrow morning and report back no matter what.

Edit: Does that method account for viewports with more than one view? I couldn’t find that in this short time, and I really should go to sleep now.

What I tried is altering the clear_mode of the rendered viewport with RenderingServer::viewport_set_clear_mode() to RenderingServer.VIEWPORT_CLEAR_NEVER and only writing once to the main viewport, instead of twice to it and the extra texture. That compiles, but the write is still cleared every other frame. The effect kinda works, but doesn’t accumulate itself to the desired visual.

I assume the problem to be the fact that the scene is still rendered without accounting for the compositor effect, and then the compositor effect works from there, but I still don’t get exactly why it doesn’t accumulate.

Is this “optimization” impossible, and, therefore, not an optimization at all?

You need to ping-pong with the previous rendered texture as the project you found does. You can also check this compositor effect:

Which has more comments explaining what it does and how to do it.

Just a quick question while I’m trying to understand the procedure: Is the way I’m transmitting textures between the shaders correct?

I see now that the method I used to keep the previous frame is incorrect, but have I got the texture transmission right?

Sorry, but I really can’t get how to make a ping pong system. I understand you need to write to one texture at a time, and alternate between them, but I don’t get how to achieve it in a way that works…

An explanation on implementing it would help a ton. I understand that I’ll have to change my approach entirely, but I’m unsure of how to approach it, exactly…

Just to avoid a potential answer after I have solved the issue, I’m reporting back to announce that I found the solution. It was really easy, actually, and I was overcomplicating things (although, hard to not overcomplicate this with no direct guidance).

I’ll mark this as solved and update my MRP with the ping-pong-test branch.

Edit: Performance is still atrocious, but at least it’s more elegant, now…

Just for the record, the only way I have found to properly optimize this process (not to mean it’s the only one, just the one I found), specifically when it comes to datamoshing (the effect I’m working on), was to branch on whether the motion vectors are 0 or not, by using dot(motion.rg, motion.rg) == 0.0, or if the surrounding 8 texels are also 0.

Branching on GPU is bad, but it’s a lot better than an entire texture write.