Compositor effect only showing in the editor

Godot Version

4.7

Question

Hello,

I’m trying to make a compositor effect. The compositor is on camera3D, placed inside a SubViewport, inside a SubViewportContainer, and the camera is not instanciated at the start of the game.

I mixed different examples I’ve seen out there to create my code (in C#), but I’m facing an issue : my compositor effect is visible in the editor (when the compositor has [tool]) and works perfectly fine, but as soon as I run the project, the compositor stop appearing. From the tests I’ve done, the render goes throught TestEffect()(see code below), but never throught _RenderCallback(…)when in game.

I have no experience with compositor effects and compute shaders (only experienced fragment shaders) thus I kind of have no idea what could possibly be wrong.

here’s my code (TestEffect.cs) :

using Godot;

[GlobalClass]
[Tool]

public partial class TestEffect : CompositorEffect
{
    public RenderingDevice rd;
    public Rid shader;
    public Rid pipeline;

    public TestEffect()
    {
        EffectCallbackType = EffectCallbackTypeEnum.PostTransparent;
        if(!Engine.IsEditorHint()) GD.Print("Compositor effect instanciation in-game");
    }

    public override void _Notification( int what )
	{
		if ( what == NotificationPredelete )
		{
			if ( shader.IsValid )
			{
				try{
                    rd.FreeRid(shader);
                }
                catch {GD.Print("Failed to free the shader");}

			}
		}
	}

    	public void _InitializeCompute()
	{
		rd = RenderingServer.GetRenderingDevice();

		if ( rd == null )
		{
            GD.Print("Failed to get rendering device");
			return;
		}

		var shaderFile  = GD.Load<RDShaderFile>("res://Assets/Shaders/test_shader.glsl");
		var shaderSpirv = shaderFile.GetSpirV();
        GD.Print("Shader compiled");

		shader = rd.ShaderCreateFromSpirV( shaderSpirv );

		if ( shader.IsValid )
		{
			pipeline = rd.ComputePipelineCreate(shader);
            GD.Print("Pipeline initialized");
		}
	}

    public override void _RenderCallback( int callbacktype, RenderData pRenderData)
    {
        if(rd == null) _InitializeCompute();
        if(rd == null) return;
        if(!Engine.IsEditorHint()) GD.Print("Render Callback");
        var renderSceneBuffers  = ( RenderSceneBuffersRD ) pRenderData.GetRenderSceneBuffers();
        Vector2I size = renderSceneBuffers.GetInternalSize();


        var pushConstant = new float[]{
            size.X,
		    size.Y
        };

        Vector3I groups = new Vector3I(size.X, size.Y, 1);

        var bytesList = new List<byte>();
        Array.ForEach( pushConstant, c => bytesList.AddRange( BitConverter.GetBytes( c ) ) );
        var pushConstantBytes = bytesList.ToArray();

        int viewCount = (int) renderSceneBuffers.GetViewCount();
        for ( var i = 0; i < viewCount; i++)
        {
            var view = (uint) i;
            Rid inputImage = renderSceneBuffers.GetColorLayer(view);

            var uniform  = new RDUniform();
            uniform.UniformType = RenderingDevice.UniformType.Image;
            uniform.Binding = 0;
            uniform.AddId( inputImage );

            var uniformSet  = UniformSetCacheRD.GetCache(shader, 0, new Godot.Collections.Array<RDUniform>(){uniform});

            var computeList  = rd.ComputeListBegin();
					rd.ComputeListBindComputePipeline(computeList, pipeline);
					rd.ComputeListBindUniformSet(computeList, uniformSet, 0);
					rd.ComputeListSetPushConstant(computeList, pushConstantBytes, (uint) pushConstantBytes.Length );
					rd.ComputeListDispatch(computeList, (uint)groups.X, (uint)groups.Y, (uint)groups.Z);
					rd.ComputeListEnd();
        }
    }
}

and my shader (test_shader.glsl) :

#[compute]
#version 450

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

layout(push_constant) uniform push_constants {
    vec2 raster_size;
} p;

layout(rgba16f, set = 0, binding = 0) uniform image2D screen_tex;

void main() {
    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
    vec2 size = p.raster_size;

    if (uv.x >= size.x || uv.y >= size.y)
		return;

    vec4 color = imageLoad(screen_tex, uv);
    vec4 res;
    if(color.a>0.) res = vec4(1., 0., 0., 1.);
    else res = vec4(0., 0., 0., 0.);
    imageStore(screen_tex, uv, res);
}

Some additional precisions : I verified the remote, the camera, subviewport and container are visible (and display effectively the content but without applying the compositor effect). The compositor effect is also enabled.

If you have any idea that could help, that would be awesome. Please feel free to ask for more details.

Where is the compositor applied?

I’m not sure what information you want precisely.
the architecture is approx. like that :

Scene
-- Character
---- MainCamera3D (that renders visual layer 1)
---- SubViewportContainer
------ SubViewport
-------- EffectCamera3D (that renders visual layer 2)

The compositor is affected to the EffectCamera3D (the one that renders visual layer 2), and there is only one compositor effect on it.

Post editor and runtime screenshots. Are there any error reported by the engine at runtime?

Also, when posting scene tree structure, always post an actual screenshot from the editor, not text only.

Sorry I can’t post embedded pictures as my account is new.

Is _RenderCallback() not running at all? Put a print statement at the very beginning of the function to check that. And double check if there are any errors reported in the debugger at runtime.

I added if(!Engine.IsEditorHint()) GD.Print("Render Callback"); in _RenderCallback and it is not printed when running. If I delete the if statement, the line is printed (So it’s perfectly running in the editor).

Well put it at the very beginning of the function, not after potential return. The point is to determine if it’s getting called at all.

And please answer if you have any error reported by the engine or not.

Yes but in either case, it should print something. I tried putting it before any return and it is the same result. In the debugger there is no errors and warnings related to the issue (only warnings on model importation issues that don’t appear in the scene)

What happens if you attach the compositor and the effect to the main camera?

I get the exact same thing : effect in editor, no effect in-game, and process not entering _RenderCallback.

The same thing happens when I put it in a WorldEnvironnement node.

Can you minimally reproduce the problem in a fresh project?

Make a scene with only a camera, attach compositor and a dummy effect that only implements _RenderCallback() that prints something.

Now it works perfectly fine, Render Callback is printed. I replicated the subviewport architecture from the main project.

Something likely got messed up in your setup. Hard to tell without seeing the project. Try to redo the whole setup in the main project. Start with the dummy effect without any actual code and make sure that the callback gets called. Once you have that, bring back the code.

I did some experiments with minimal code like the dummy project, but nothing worked. But I have an idea of what could possibly go wrong. My character is spawned in the game later after the beginning and the effect is clearly instanciated at the really start of the program. Maybe because the instanciation and the start of the rendercallback loop are at different moments, something happens.

My project is multiplayer, pretty vast and other people participated so ripping it apart is not very suitable, sorry for the inconvenience.

Try dynamically creating the compositor and the effect in scene’s _Ready(), and add them to the camera. Explicitly set effect’s Enabled flag to true

Hello, I’m very sorry, it appears that my project (that supports multiple graphics libs) was running on OpenGL, and therefor, on the gl_compatibility renderer which is not compatible with compositor. Sorry for all this time, and hope this helps other inattentive people like me :slight_smile: