Hi, i'm making a 2D game with 100% vectorial graphics to accommodate to all screen resolutions but because they take a lot of time to render them every frames i wanted to render then to a texture, save them in memory then display the texture instead of the vectors but i still can't find how to render a draw method on an empty image file
#(unsolved) How to render primitives/draw_* to a texture ?
61 messages · Page 1 of 1 (latest)
Image.load_svg_from_buffer() should be what you're looking for.
no i'm using draw functions and i want to render them on an image that you save as a .png instead of rendering it to the screen
but thank you i didn't know that you could load SVG, it might be useful for me for other games
then you could use a subviewport to render the primitives to it and and the use get_texture() to recieve the last frame of it.
So it would work like this ?
view = SubViewport.new()
view.draw_circle(???, ???, ???, ...)
view.get_texture().get_image().save_png("path...")
no, subviewport is a node and it has to be part of the scene tree. you put the nodes you want to render as its children, wait a frame and then do get_texture().
you also might have to adjust some settings so it works without needing to actually be rendered to the screen.
I just did that but i just get a black screen
i can wait 0, 1 or 10 frames but i still get a pitch black image instead of a transparent one with the object draw on it
what do i do is when i have 6 objects to render, i do an array of 6 dictionaries (subviewport; node; path) and a timer
the first pass (timer = 0),
spawn a SubViewport node as the subviewport key's value,
then spawn a node2D node as the node key's value,
set the (relative) path name as the path key's value,
set the node2D node as the child of the SubViewport node,
then render on the node2D
after that i raise the timer by one, wait another frame then
the second pass (timer = 1),
get the texture, image, then save_png of each viewports
then delete all node2D and subviewport nodes
okay to start it would probably be simplest to just put the Subviewport inside a Viewportcontainer. that way it should definetly render with its default settings.
I've tried both "one container per subviewport" and "one container for all subviewports" and nothing has changed, sorry i'm not good with viewports 😅
(unsolved)How to render primitives/draw_* to a texture ?
(unsolved) How to render primitives/draw_* to a texture ?
the subviewportcontainer would only affect a single subviewport. But if you set SubViewport.render_target_update_mode to UPDATE_ALWAYS it should also work without containers. Alternatively UPDATE_ONCE can be used if you only want to render the next frame.
Yes i've tried this and when i use get_viewport() it does save the main viewport image (so the problem isn't at the saving step) but what ever i do, i can change every settings but i still can't use the draw functions on them (Viewport or Node2D) it's just a black screen 🤷♂️
hm, could you show your code?
ok ! i'm essencially doing this (it's a simplified version, in the normal version i'm rendering 17 different notes with a pipeline made for that and it's almost 1000 lines long)
`
var pass = 0
var size: float = 88.
var subv := SubViewport.new()
var nd2d := Node2D.new()
func _ready():
subv.add_child(nd2d)
subv.set_disable_3d(true)
subv.set_transparent_background(true)
subv.set_size(Vector2i(size, size))
func _process():
queue_redraw()
func _draw():
if pass == 0:
draw_note_A(nd2d, Vector2(size, size) / 2., size)
if pass == 1:
subv.get_texture().get_image().save_png("a_note.png")
func draw_note_A(node:Node2D, position:=Vector2(0, 0), size:= 1.):
node.draw_circle(position, size, Color.WHITE, true, -1.0, gl.win.aa)
node.draw_circle(position, size * 0.9375, Color.WHITE, false, size * .125, gl.win.aa)
var sVze = Vector2(size, size) * 56./128.
var sAze = Vector2(size,-size) * 56./128.
node.draw_line(position-sVze, position+sVze, Color.WHITE, size/(5.7)*sqrt(2), gl.win.aa)
node.draw_line(position-sAze, position+sAze, Color.WHITE, size/(5.7)*sqrt(2), gl.win.aa)`
It just doesn't draw to the viewport and i have no idea why
You're never calling your draw method?
Also subv seems to never be added to the tree.
oh right isn't is _draw()?
you have to use _draw()
draw() is the signal that gets emitted on a redraw
i did, i just forgot to rewrite the "_" in the discord message
oh my bad
it's not automatically added to the main scene ? and yes in the full one i'm calling draw in the process function this is just the essentials
add_child() exists for a reason
also if you want to draw the node this script is attached to, it's not ideal to create the viewport here, as it needs to be a child of it.
also also, you can't draw on a different node inside _draw() as far as i know.
well, i have my main control node with nothing else manually added in the scenetree, and i do everything by code (i come from game maker studio sorry if i don't get the node system very well)
Of course it's not, after var subv := SubViewport.new() the subv is parentless.
well you are pretty ambitious for someone just starting out.
i first did my project in game maker but because i couldn't use thread i did it in C, but SDL was a bit glitchy (and it's quite time consuming to do big projects in C) so i used Godot
But i've been making games for quite some time, this is just the node system that i don't really get
well i would suggest to first learn how to properly use the systems godot has to offer, like the node system.
Ok i'll try adding subv to the main scene and i'll see if it'll work
also like i said, _draw() allows you to use the draw-methods for this node. node.draw_circle() won't work.
i did that because in the function to draw the note is not in the same node but in the art autoload node (where all the code to draw the vector sprites are) so i have to pass the node i'm using to draw on the right node and not on the art node
well in that case you can pass the whole node that's supposed to be drawn, using change_parent() reparent(), or just add_child() if the node doesn't have a parent yet.
although reparent is more intended for changing a parent while keeping the position, so i would suggest sticking to remove/add child.
If you want to draw with/using a different node (CanvasItem) then you can connect to its draw signal and use such other CanvasItem for drawing. Something like:
(...)
other_node.draw.connect(draw_something.bind(other_node))
(...)
func draw_something(drawer: CanvasItem) -> void:
drawer.draw_circle(...)
...
oh didn't know that's possible. good to know!
So "other_node" should be nd2d (the node 2D in the subviewport) and draw_something is draw_note_A(...) ?
but in that case i should use a CanvasItem alone and draw on it instead of a SubViewport and a Node2D ?
Technically you could use RenderingServer methods to add draw commands for any canvas item from anywhere. The thing is before NOTIFICATION_DRAW, draw signal, and _draw method such drawing commands are cleared, so you'd need to be careful about synchronizing it properly. CanvasItem.draw_... methods are restricted to be called only within this specific chunk because of that. https://github.com/godotengine/godot/blob/da4f9339ea7f614f5965e6a6b918b8c8285f2e1d/scene/main/canvas_item.cpp#L139-L149
If I get your code right, yeah, something like that. But nd2d would of course also need to be within the SceneTree (soemwhere within your SubViewport?
).
it already a child of the viewport, but then i tried making the viewport the child of the current scene and it didn't changed anything 😅
I'm getting to this
nd2d.draw.connect(gl.gp.tex.note.bind(nd2d).call(nd2d, pos, 2, size, 1))
which looks very convoluted and crashes lmao
i'm going to try with a mock-up function, maybe ".bind" can't handle functions in dictionaries
No idea about your specific details, but I doubt you're returning a Callable (like a method) from gl.gp.tex.note (you're calling its "version with bind").
(you can't connect anything else than a Callable to a signal)
before i was using
gl.gp.tex.note.call(nd2d, pos, 2, size, 1)
gl = global autoload
gp = gameplay dictionary
tex = texture dictionary (it's the vector data)
note = the function to draw a note
nd2d = node to draw to (: Node2D)
pos = position (: Vector2)
2 = note type (no written types)
size = well.... the size (: float)
1 = alpha (: float)
You don't want .call(nd2d, pos, 2, size, 1)
Guessing you might want nd2d.draw.connect(gl.gp.tex.note.bind(nd2d, pos, 2, size, 1))
then how to i pass arguments to my function ? it's the only way i've found it pass arguments to a function in a dictionary
ok thank you i'll try !!
That's assuming gl.gp.tex.note takes relevant arguments.
it does, it displays correctly when i have 'self' for the Node2D argument
i'm still getting a black image when i'm saving it 😅
Tons of potential causes. E.g. you could be saving it too early, see: https://docs.godotengine.org/en/4.3/classes/class_renderingserver.html#signals.
E.g. this should ensure the whole frame was drawn:
...
await RenderingServer.frame_pre_draw
await RenderingServer.frame_post_draw
...
Still doesn't change anything (it freezes for 1 frame before saving but that's it)
i think i'll do an empty project and try doing a basic subviewport off-screen rendering system then if i manage to get it to work i'll back port it into the main game