class_name ImageCompositor extends SubViewport var image_sprite: Sprite2D func _init() -> void: # Overwrite some variables self.render_target_update_mode = SubViewport.UPDATE_ALWAYS self.disable_3d = true self.transparent_bg = true self.canvas_item_default_texture_filter = Viewport.DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST self.image_sprite = Sprite2D.new() @onready var camera = %Camera @onready var image_viewport_display = %ImageViewportDisplay func _ready() -> void: # Add image sprite as child to be rendered self.add_child(image_sprite) var _fragment_function_regex: RegEx = RegEx.create_from_string(r'\s*void\s+fragment\s*\(\s*\)\s*{\s*') func validate_shader_compilation(shader: Shader) -> bool: # Inject code to validate shader compilation var shader_code = shader.code; # -> get position of fragment shader var fragment_function_match = _fragment_function_regex.search(shader.code) if fragment_function_match == null: return false # -> inject uniform var uniform_name = "shader_compilation_validate_" + str(randi_range(999999999, 100000000)) var uniform_code_line = "\nuniform bool " + uniform_name + ";\n" shader_code = shader_code.insert(fragment_function_match.get_start(), uniform_code_line) # -> inject variable access to prevent that the uniform gets optimized away shader_code = shader_code.insert(fragment_function_match.get_end() + len(uniform_code_line), "\n" + uniform_name + ";\n") # apply shader code shader.code = shader_code # test if uniform list is empty -> if it is empty, the shader compilation failed return len(shader.get_shader_uniform_list()) > 0 func shader_has_uniform(shader: Shader, var_name: String, type: int) -> bool: for u in shader.get_shader_uniform_list(): if u["name"] == var_name && u["type"] == type: return true return false func set_vsync(enabled: bool): if enabled: DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED) else: DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) func update() -> Array: # returns error messages (strings) if Filesystem.input_image_path == "": return ["No image loaded!"] var shader = Filesystem.shader # read from disk if shader == null: return ["No shader opened!"] # validate shader if not validate_shader_compilation(shader): return ["Shader compilation failed!"] var errors = [] # load texture var fit_image = Filesystem.input_image_path != Filesystem.last_input_image_path var err = Filesystem.load_image() if err != "": errors.append(err) image_viewport_display.hide() return errors # apply texture image_sprite.texture = Filesystem.input_image_texture image_sprite.offset = Filesystem.input_image_texture.get_size() / 2 self.size = Filesystem.input_image_texture.get_size() # show the image viewport & fit the image if fit_image: camera.fit_image() image_viewport_display.show() # create shader material var mat = ShaderMaterial.new() mat.shader = shader # assign material image_sprite.material = mat # iterate n times set_vsync(false) # speed up processing # Get viewport texture await RenderingServer.frame_post_draw # wait for next frame to get drawn Filesystem.result = get_texture().get_image() image_sprite.texture = ImageTexture.create_from_image(Filesystem.result) set_vsync(true) # reenable vsync image_sprite.material = null # done return errors