Repository: godot
Issue: First use of shader with refraction_enabled=true causes stutter
Labels: topic:rendering,topic:3d,performance

### Tested versions

- Reproducible in 4.3.stable, 4.4.beta[4ce466d]

### System information

Godot v4.4.dev.mono (611d6e8da) - Pop!_OS 22.04 LTS on Wayland - X11 display driver, Multi-window, 3 monitors - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 2070 SUPER (nvidia; 565.77) - AMD Ryzen 7 5800X 8-Core Processor (16 threads)

### Issue description

This was discussed in depth in the Godot rocket chat here: https://chat.godotengine.org/channel/vfx-tech-art/thread/3w7r2PGyTifFXPv84

I will provide a quick summary followed by more details.

### Quick Summary

When a shader that uses ``refraction_enabled=true`` first becomes visible and in view, there is a brief stutter while Godot allocates the necessary resources for that shader.

### Details

I'm going to quote @DarioSamo has he did the digging into these details after we narrowed down the cause being the refraction setting.

> This might not be that hard to fix. There's a lot of stuff that is only queried based on what surfaces are visible on the scene:

```cpp
if (uses_lightmap) {
				surf->sort.uses_lightmap = 1;
				scene_state.used_lightmap = true;
}

if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_SUBSURFACE_SCATTERING) {
				scene_state.used_sss = true;
}
if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_SCREEN_TEXTURE) {
				scene_state.used_screen_texture = true;
}
if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_NORMAL_TEXTURE) {
				scene_state.used_normal_texture = true;
}
if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DEPTH_TEXTURE) {
				scene_state.used_depth_texture = true;
}
```

> if these pass through, then it does whatever it needs to create these buffers, but the existence of these textures depends on the viewport itself

> I wanted to bring attention to this in particular

```cpp
if (scene_state.used_screen_texture) {
	RENDER_TIMESTAMP("Copy Screen Texture");

	// Copy screen texture to backbuffer so we can read from it
	_render_buffers_copy_screen_texture(p_render_data);
}
```

> this is only triggered if the boolean is true

And then @Calinou pointed out:

> refraction requires screen and depth texture (depth since 4.4), so changing it will cause recompilation

To which @DarioSamo discovered we can expand our approach to ubershaders in order to take into account the different textures needed on scene load:


> Not much different than what I described. We have a surface cache construction step. The idea would be we check for these flags and keep a counter for each time a surface is added and removed that uses the feature. Then we default scene state to true if the counter > 0.

```cpp
if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_SCREEN_TEXTURE) {
  default_scene_state.surfaces_using_screen_texture++;
}
if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_NORMAL_TEXTURE) {				 
 default_scene_state.surfaces_using_normal_texture++;
}
if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DEPTH_TEXTURE) {				 
 default_scene_state.surfaces_using_depth_texture++;
}
```

> Something along those lines, then decrease the counter on surface destruction

With that said, it sounds like the scope of this issue may be beyond just refraction.  I suspect anything that needs to allocate a texture?  I'll let @DarioSamo confirm that and if so, we may want to update the title of this issue to reflect it.

### Workaround

Create a quad with a simple shader attached that makes use of the resources you need allocated.  In my case for example, I would create a quad with a simple StandardMaterial3D that has ``refraction_enabled=true`` visible to the camera, but hidden from the player behind a loading scene as the rest of the scene loads.

### Steps to reproduce

1. Get a lower end device.  In my case it is an Android device. Details:
Model Name: Galaxy S10e
Model number: SM-G970U1
This will not work with a high end cpu as it will create the resources before the next frame refresh occurs and you will not notice a stutter.
2. Have something moving in the background so you notice the stutter.
3. Place the quad with a refractive material in view of the camera but leave it invisible.
4. Set it to become visible while the scene is playing and notice a brief freeze on its first draw.

I have an MRP attached that is slightly more involved as I originally thought it was related to GPUParticles.  But nonetheless, I attached it in case anyone finds it useful as the stutter does occur with it on my Android device.

### Minimal reproduction project (MRP)

[mrp.zip](https://github.com/user-attachments/files/18415153/mrp.zip)

NOTE: 

This particular MRP uses GPUParticles3D.  The first particle does not use refraction and hence doesn't cause a stutter.  The sub-emitter (shockwave) emits particles with refraction and causes this stutter.  Just press the "emit" button and see it play.