I think the way to go about this is using the custom_viewport
property of CanvasLayer
I got saving of the image working with this giant label that is bigger than the main Viewport

For this node setup, the node Content is a PanelContainer
and therefore the size of the everything being draw on this CanvasLayer
. It doesn't need to be a PanelContainer
just something that contains everything being drawn.

The CanvasLayer
has a script attached to it with the following code:
extends CanvasLayer
func _ready() -> void:
var viewport := Viewport.new()
viewport.disable_3d = true
viewport.size = $Content.rect_size
viewport.render_target_update_mode = Viewport.UPDATE_ONCE
custom_viewport = viewport
get_parent().call_deferred("add_child", viewport)
$"../TextureRect".texture = viewport.get_texture()
The Viewport
can be manually updated again (therefore updating the image) with the following line code:
custom_viewport.render_target_update_mode = Viewport.UPDATE_ONCE
If you just want to save the texture and then free everything you can call .duplicate()
on the texture (this must be done after at least one frame is drawn or else it'll be fully black) and then .queue_free()
on the Viewport
and your CanvasLayer
Hope this helps :)