Godot Version
4.7.1.stable
Question
Hi! I’m making a CompositorEffect that requires a second camera and viewport to transmit a texture to my main compositor effect. I have it all set up already, it’s just one texture is consistently failing to display properly, and sometimes even throws an error to the godot console.
I have setup the auxiliary “donor” effect as such:
@tool
class_name AuxEffect extends CompositorEffect
var rd: RenderingDevice
var shader: RID
var main_pipeline: RID
var auxtex: Array[RID]
var depthex: Array[RID]
var scene_buffers: RenderSceneBuffersRD
var started: bool = false
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE and shader.is_valid() and shader:
RenderingServer.free_rid(shader)
func _init() -> void:
needs_motion_vectors = false
effect_callback_type = CompositorEffect.EFFECT_CALLBACK_TYPE_POST_TRANSPARENT
func _render_callback(_effect_callback_type: int, render_data: RenderData) -> void:
scene_buffers = render_data.get_render_scene_buffers()
if not scene_buffers: return
auxtex.resize(scene_buffers.get_view_count())
depthex.resize(scene_buffers.get_view_count())
if not started:
started = true # This delays the shader by one render callback
return
for view in scene_buffers.get_view_count():
var aux: RID = scene_buffers.get_color_layer(view)
var depth: RID = scene_buffers.get_depth_layer(view)
auxtex[view] = aux
depthex[view] = depth
And a helper autoload class that only exists because you can’t access the scene tree from within a compositor effect. It has an array of viewports because the editor can have up to 4 viewports simultaneously, and this approach prevents the effect from bugging out when adding those extra viewports:
@tool
extends Node
var mask_tex: Array[Array]
var depth_tex: Array[Array]
var aux_effects: Array[AuxEffect]
var aux_viewports: Array[SubViewport]
var aux_scene_buffers: Array[RenderSceneBuffersRD]
var tree: SceneTree
var currently_edited_scene: Node
var aux_viewport_scene = preload("res://assets/scenes/aux_camera.tscn")
var hash_array: Array[StringName] = ["One", "Two", "Three", "Four"]
func _ready():
if Engine.is_editor_hint():
start_values.call_deferred()
for i in range(4):
mask_tex.append([RID()])
depth_tex.append([RID()])
aux_effects.append(null)
func start_values() -> void:
if Engine.is_editor_hint():
for aux_viewport in aux_viewports:
if is_instance_valid(aux_viewport):
if aux_viewport.get_parent() == currently_edited_scene:
currently_edited_scene.remove_child(aux_viewport)
aux_viewport.queue_free()
aux_viewports.clear()
aux_effects.clear()
mask_tex.clear()
depth_tex.clear()
aux_scene_buffers.clear()
if currently_edited_scene is WorldEnvironment:
for index in range(4):
if not currently_edited_scene.find_child(str(hash_array[index].hash()), false, false):
aux_viewports.append(aux_viewport_scene.instantiate())
aux_viewports[index].name = str(hash_array[index].hash())
aux_viewports[index].size = EditorInterface.get_editor_viewport_3d(index).get_size()
currently_edited_scene.add_child(aux_viewports[index])
aux_effects.append(aux_viewports[index].get_camera_3d().compositor.compositor_effects[0])
mask_tex.append(aux_effects[index].auxtex)
depth_tex.append(aux_effects[index].depthex)
aux_scene_buffers.append(aux_effects[index].scene_buffers)
func _process(_delta: float) -> void:
if Engine.is_editor_hint():
var previously_edited_scene: Node = currently_edited_scene
currently_edited_scene = EditorInterface.get_edited_scene_root()
if currently_edited_scene != previously_edited_scene and previously_edited_scene:
for child_index in range(4):
var child = previously_edited_scene.find_child(str(hash_array[child_index].hash()), false, false)
if child in aux_viewports:
previously_edited_scene.remove_child(child)
child.queue_free()
aux_viewports.remove_at(aux_viewports.find(child))
tree = currently_edited_scene.get_tree()
if aux_viewports.size() < 4:
start_values()
return
for index in range(aux_viewports.size()):
var current_viewport: SubViewport = EditorInterface.get_editor_viewport_3d(index)
if current_viewport != aux_viewports[index]:
start_values()
return
elif not previously_edited_scene and currently_edited_scene:
start_values()
if currently_edited_scene is WorldEnvironment:
for index in range(4):
var editor_viewport: SubViewport = EditorInterface.get_editor_viewport_3d(index)
aux_viewports[index].size = editor_viewport.size
var aux_camera: Camera3D = aux_viewports[index].get_node("AuxCamera")
var editor_camera: Camera3D = editor_viewport.get_camera_3d()
aux_camera.transform = editor_camera.transform
aux_camera.fov = editor_camera.fov
mask_tex[index] = aux_effects[index].auxtex
depth_tex[index] = aux_effects[index].depthex
aux_scene_buffers[index] = aux_effects[index].scene_buffers
And the main effect. Only binding 4 matters to my problem, as it’s the one with errors:
@tool
class_name TrueDatamosh extends CompositorEffect
var rd: RenderingDevice
var shader: RID
var main_pipeline: RID
var nearest_sampler: RID
var fmt: RDTextureFormat
var size: Vector2i = Vector2i(1920, 1080)
var prevtex: Array[RID] = []
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE and shader:
if shader.is_valid():
RenderingServer.free_rid(shader)
func initialize_cs() -> void:
rd = RenderingServer.get_rendering_device()
if not rd:
#print("Failed to get RD.")
return
#print("RD got successfully.")
var glsl_file: RDShaderFile = load("res://assets/shaders/true_datamosh.glsl")
shader = rd.shader_create_from_spirv(glsl_file.get_spirv())
if not shader:
#print("Failed to get shader.")
return
#print("Shader got successfully.")
main_pipeline = rd.compute_pipeline_create(shader)
if not main_pipeline:
#print("Failed to get pipeline.")
return
#print("Pipeline got successfully.")
func _init() -> void:
needs_motion_vectors = true
effect_callback_type = CompositorEffect.EFFECT_CALLBACK_TYPE_POST_TRANSPARENT
RenderingServer.call_on_render_thread(initialize_cs)
prevtex.clear()
var sampler_state: RDSamplerState = RDSamplerState.new()
sampler_state.min_filter = RenderingDevice.SAMPLER_FILTER_NEAREST
sampler_state.mag_filter = RenderingDevice.SAMPLER_FILTER_NEAREST
nearest_sampler = rd.sampler_create(sampler_state)
fmt = RDTextureFormat.new()
fmt.width = size.x
fmt.height = size.y
fmt.format = RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT
fmt.usage_bits = RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT \
| RenderingDevice.TEXTURE_USAGE_STORAGE_BIT \
| RenderingDevice.TEXTURE_USAGE_CAN_COPY_FROM_BIT \
| RenderingDevice.TEXTURE_USAGE_CPU_READ_BIT \
| RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT
prevtex.append(rd.texture_create(fmt, RDTextureView.new(), []))
func _render_callback(_effect_callback_type: int, render_data: RenderData) -> void:
if ShaderGlobals.aux_viewports.size() > 0:
if not rd or not shader or not main_pipeline:
initialize_cs()
return
var masktex: RID
var maskdepth: RID
var scene_buffers: RenderSceneBuffersRD = render_data.get_render_scene_buffers()
if not scene_buffers: return
size = scene_buffers.get_internal_size()
if size.x == 0 or size.y == 0: return
fmt.width = size.x
fmt.height = size.y
@warning_ignore_start("integer_division")
var x_groups: int = (size.x - 1) / 16 + 1
var y_groups: int = (size.y - 1) / 16 + 1
@warning_ignore_restore("integer_division")
var push_constants: PackedFloat32Array
push_constants.append(size.x)
push_constants.append(size.y)
for view in scene_buffers.get_view_count():
var screentex: RID = scene_buffers.get_color_layer(view)
var motiontex: RID = scene_buffers.get_velocity_layer(view)
var depthext: RID = scene_buffers.get_depth_layer(view)
var render_target: RID = render_data.get_render_scene_buffers().get_render_target()
for index in range(4):
var viewport: SubViewport = EditorInterface.get_editor_viewport_3d(index)
var viewport_RID: RID = viewport.get_viewport_rid()
var viewport_render_target_RID: RID = RenderingServer.viewport_get_render_target(viewport_RID)
if render_target == viewport_render_target_RID:
masktex = ShaderGlobals.mask_tex[index][view]
#masktex = ShaderGlobals.aux_scene_buffers[index].get_color_layer(view)
maskdepth = ShaderGlobals.depth_tex[index][view]
#maskdepth = ShaderGlobals.aux_scene_buffers[index].get_color_layer(view)
break
var uniform_mask: RDUniform = RDUniform.new()
uniform_mask.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
uniform_mask.binding = 4
uniform_mask.add_id(masktex)
var image_uniform_set: RID
image_uniform_set = UniformSetCacheRD.get_cache(shader, 0, [uniform_screen, uniform_vector, uniform_depth, uniform_prev, uniform_mask, uniform_maskdepth])
var compute_list: int = rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, main_pipeline)
rd.compute_list_bind_uniform_set(compute_list, image_uniform_set, 0)
rd.compute_list_set_push_constant(compute_list, push_constants.to_byte_array(), push_constants.size() * 4)
rd.compute_list_dispatch(compute_list, x_groups, y_groups, 1)
rd.compute_list_end()
My main problem now is that I get a black screen, if I use imageStore(screen_tex, giid, mask_tex), and half the time the editor will throw errors to the console, always with binding 4, which I can’t figure out what is really wrong with it. I tried to get the scene buffers directly, or just the textures, and I still can’t figure out how to properly do it.
Can someone here please help me? There is no documentation in any of this CompositorEffect thingy…

