Repository: godot
Issue: Write / reading `frag_color` multiple times in shader seems to cause performance problem
Labels: discussion,topic:rendering,performance

### Godot version

3.6 beta 3 (but likely existed for years)

### System information

Linux Mint 21.1, Intel Core i7-13700T, Intel a780 GPU

### Issue description

After spending some time tracking down a performance problem when working on core shaders in the 3D platformer demo, I tracked down a large drop in fill rate performance to modifying `frag_color` multiple times in the fragment shader.

I'm half expecting this to be an error on my part but I'd rather flag it and feel silly :blush: than let it pass and potentially miss a good speedup.

### GLES2
Adding the second line here:
```
gl_FragColor = vec4(ambient_light + diffuse_light + specular_light, alpha);
gl_FragColor *= 1.0001;
```
dropped the frame rate from 127fps to 62fps in GLES2.

Whereas using this instead:
```
vec4 temp_frag_color = vec4(ambient_light + diffuse_light + specular_light, alpha);
temp_frag_color *= 1.0001;
gl_FragColor = temp_frag_color;
```
kept the frame rate at 127fps.

This was very surprising to me, and I was expecting it to be an artifact, but it seems very repeatable on my hardware. It may come down to the drivers, but if it happens for me it likely happens on other setups.

This paradigm of writing to `frag_color` multiple times is used in GLES2 and GLES3 (for at least fog and emission), and may be dropping performance unnecessarily. It may also be a problem in 4.x (haven't examined but I've mentioned this in rocket chat).

### GLES3
I also tried the same in GLES3.

Again, adding the second line here:
```
	frag_color = vec4(ambient_light + diffuse_light + specular_light, alpha);
	frag_color.rgb *= 1.0001;
```
dropped performance from 88fps to 30fps. That's almost a 3 fold drop in performance.

GLES3 seems less susceptible currently, as it only modifies `frag_color` when adding emission:
```
#ifdef USE_FORWARD_LIGHTING //ubershader-runtime
	frag_color.rgb += emission;
#endif //ubershader-runtime
```

### Steps to reproduce

See above.

### Minimal reproduction project

3D Platformer demo, modify `scene.glsl` shaders as above, and recompile engine.

### Discussion
I'm not a shader guru by any means. I had a vague memory of this as a possible thing to watch for, but I'm not up to date. I haven't previously touched this part of the shaders, I just noticed while running experiments with blob shadows.

It could be that modifying the existing `frag_color` is causing some kind of round trip, or preventing this being optimized away into a local register, whereas using a temporary variable prevents side effects so doesn't have the performance drop. Alternatively it could be a problem introduced by our own shader "translator" code (I haven't examined the final glsl).

I also considered the problem was due to modification of alpha, but using a line like `gl_FragColor.rgb *= 0.999;` also creates the problem.

I'm still not absolutely sure it isn't some artifact I've created somehow - it would be nice to see it independently verified. It may not occur on all hardware / drivers.

It is possible this could also occur in user shaders.

If confirmed perhaps we can take some (hopefully) simple steps to eliminate this problem, by standardizing our shaders to use temporaries, and only write to the final GL builtin once, at the end.

## Update
My small PR for GLES2 would seem to confirm this is a valid issue, it gives 2-3x increase in fps in scenes which tax fill rate and use emission / fog. :+1: 
It is likely that the 3D platformer is taxing on fill rate perhaps because of reflection probes. Will try this in TPS demo too.