Skip to content

Instantly share code, notes, and snippets.

@clayjohn
Created August 5, 2019 22:04
Show Gist options
  • Save clayjohn/80d9b1e52e1b9a23445504e85a8d7890 to your computer and use it in GitHub Desktop.
Save clayjohn/80d9b1e52e1b9a23445504e85a8d7890 to your computer and use it in GitHub Desktop.
Uses custom viewports to generate textures before the first frame is drawn
extends Node2D
var myViewport
var myCanvas
func _ready():
## Stop main viewport from drawing when we force draw
## should loop through all other viewports as well
VisualServer.viewport_set_active(get_viewport().get_viewport_rid(), false)
# initialize Viewport, needs canvas
myViewport = VisualServer.viewport_create()
myCanvas = VisualServer.canvas_create()
VisualServer.viewport_attach_canvas(myViewport, myCanvas)
VisualServer.viewport_set_size(myViewport, 64, 64)
## "active" instructs it to be drawn
VisualServer.viewport_set_active(myViewport, true)
## create CanvasItem and add to canvas just as in servers tutorial
var ci_rid = VisualServer.canvas_item_create()
VisualServer.viewport_set_canvas_transform(myViewport, myCanvas, Transform())
VisualServer.canvas_item_set_parent(ci_rid, myCanvas)
var sprite = load("res://icon.png")
VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(Vector2(0, 0), sprite.get_size()), sprite)
## draw it once, this should be bundled into a function
VisualServer.viewport_set_update_mode(myViewport, VisualServer.VIEWPORT_UPDATE_ONCE)
VisualServer.viewport_set_vflip(myViewport, true)
VisualServer.force_draw(false)
var image = VisualServer.texture_get_data(VisualServer.viewport_get_texture(myViewport))
var texture = ImageTexture.new()
texture.create_from_image(image)
$Sprite2.texture = texture
#exact same as above
VisualServer.viewport_set_update_mode(myViewport, VisualServer.VIEWPORT_UPDATE_ONCE)
VisualServer.viewport_set_vflip(myViewport, false)
VisualServer.force_draw(false)
var image2 = VisualServer.texture_get_data(VisualServer.viewport_get_texture(myViewport))
var texture2 = ImageTexture.new()
texture2.create_from_image(image2)
$Sprite3.texture = texture2
#prints 0 as no frames have actually drawn
print(Engine.get_frames_drawn())
#reset the main viewport so everything actually draws to screen
VisualServer.viewport_set_active(get_viewport().get_viewport_rid(), true)
@SIsilicon
Copy link

SIsilicon commented Mar 1, 2021

If that's the case, the solution would be to normalize and dilate the color data of the viewport texture. Where alpha is less than one, but greater than zero, divide the color by the alpha. If the alpha is close to or equal to zero though, take the color information from the surrounding pixels.

@Flarkk
Copy link

Flarkk commented Apr 9, 2021

Hi @clayjohn and thanks for this snippet I’ve successfully used many times.
I have one question though : it seems not working well when executed from a thread. Sometimes a get the texture, sometimes I get an empty image.
Seems like force_draw misses some thread sync logic.
Have you experienced this, and would you have any suggestion ?

@clayjohn
Copy link
Author

clayjohn commented Apr 9, 2021

@Flarkk When writing this I assumed it would be run from the main thread. There is currently no logic to synchronize multiple threads. You will need to check that drawing has actually finished before retrieving the texture.

You can try using VisualServer.request_frame_drawn_callback or the frame_post_draw signal

@Flarkk
Copy link

Flarkk commented Apr 9, 2021

@clayjohn thanks for the hint. But something still sounds wrong from my understanding: If force_draw guarantees that all viewports have been drawn afterwards, when called on the main thread (whatever the underlying mechanism : semaphore, condition_variable, etc ... to sync with the visual server thread), what’s different to call from another thread ?
I’ve also spotted the force_sync method, but the doc is really light on it. I wonder whether it can help or not.
Note that I’m developing in c++ NativeScript, so I’m a bit reluctant to use godot style signals or callbacks (preferring good old c++ style :-)

@clayjohn
Copy link
Author

clayjohn commented Apr 9, 2021

@Flarkk force_draw does not guarantee that all viewports have been drawn. It just submits the draw commands to the GPU immediately instead of at the end of the frame.

Unfortunately, I don't think force_sync() is going to help you much :(
https://github.com/godotengine/godot/blob/5fe89e8ccda8f46c811e6f3fd601ca09ae138e17/servers/visual/visual_server_raster.cpp#L130-L131

Again, I didn't write this code assuming it would be run from another thread, so I can't do much troubleshooting for you. You can try running the code with different thread models to see if that helps. I understand that Godot, by default, tries to run all VisualServer commands on the main thread so you could be getting a bit out of sync, maybe running in a different thread model will help. Otherwise, you are just going to have to figure out a way to wait until the draw commands have finished executing before requesting the data from the GPU.

@Flarkk
Copy link

Flarkk commented Apr 11, 2021

Ok got it, many thanks.
For now I’ll stick in waiting a bit the draw command complete (as the work is done on a thread, it doesn’t freeze the entire game).
I’ll rewrite the whole thing with compute shaders when Godot 4.0 is out, as it should allow to completely separate textures rendering from the draw process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment