Review polyline/polygon usage to reduce overhead when drawing simple shapes #8618
Replies: 1 comment
-
Below are some supplementary benchmarks. All done in Forward+. Grid DrawingDrawing a 10000x10000 grid (20002 lines, one per column and row + the ends) with lines at width -1.
draw_multiline being very performant if you have a cached grid of points and call it once, but all of the extra stuff (the overhead) that draw_multiline introduces per call slows it down a lot which (I assume) happens with pretty much any separate draw_rect, draw_circle, draw_arc, etc call. Shape DrawingI will be adding more benchmarks over time. 10,000 Rectangles with Line Width -1
10,000 Rectangles with Line Width 1
Notably the polyline calculations seem to affect CPU time a lot here which is interesting since they don't seem to affect draw_rect. Not sure what to conclude about this benchmark. 1,000 Arcs width Line Width -1 64 Points
If the arcs don't need to be recalculated constantly then draw_multiline is again the clear winner. Less likely that you have static arcs, but in theory if you updated your arcs directly into a PackedVector2Array when they change you could still get very good performance. 1,000 Arcs width Line Width 1 64 Points
Of note, draw_arc here outperforms a single draw_multiline! I can't say why that is considering draw_multiline outperforms so many other things. Polyline calculations killing performance again. Not surprising when you have 64 lines per arc. Of course normally you would update these once in a canvas_item and not recalculate the lines over and over so this isn't that big of a deal in most uses cases. After seeing the performance of draw_arc beating draw_multiline here I decided to write a benchmark that uses a single colored polyline with each arc separated by transparent points: 1,000 Arcs width Line Width 1 64 Points
The performance is much better for what is effectively drawing the same thing (there is a very slight difference because the first point of each arc is drawn twice.) 10,000 Arcs width Line Width -1 64 PointsTo further emphasize the disparity of multiple polylines (many draw_ calls vs one) here's 10,000 arcs with 64 points -1 line width.
At 64 points per arc there are approximately 640,000 segments being drawn in that single polyline and godot still does it 60fps with room to breathe. multiline is still pretty close in performance and doesn't require transparent connecting lines. A Side NoteIt's worth mentioning that one of the reasons this is something to consider is because controls as a single CanvasItem tend to redraw everything even if it doesn't need to be redrawn. If, for example, you instead implement a grid for your TileMap using a second CanvasItem that only needs to be redrawn when the grid actually changed, or if you could draw a control's (such a GPUParticles2D) visibility rect as a separate CanvasItem these times wouldn't matter as much because they could be infrequent. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Disclaimer: I have no expertise in the rendering department. These are just observations.
There seems to be a large overhead when submitting many (~1000+) polygons to be rendered. This is relevant mostly because of the amount of things in the engine that utilize polylines and their usage in seemingly simple functions. I think they're being used in place of what could be very simple and performant alternatives.
The problem does not seem to be the amount of lines being drawn or the time spent calculating the polylines themselves, but the amount of time spent in preparing polygons to be drawn. I won't pretend to understand the call stack, but if this much time of the time is being spent simply iterating through data then this is probably a misuse of polygons.
For reference here are some things that use polylines:
draw_rect (unfilled)
,draw_arc
,draw_multiline
, debug drawing, collision shapes. Line2D uses polygons but does so using a triangle array.If you submit one polyline with many (50k+) points the performance is basically the same as one Line2D (very good) but if you break these calls up then performance tanks pretty heavily. If, for example, you wanted to draw a bunch of rect outlines for a grid (godotengine/godot#84963) you would find that performance is truly awful; much worse than drawing with draw_line.
request_polygon goes over my head but it looks like a lot of effort to draw the simple shapes used in these contexts.
In my opinion if you're going to be using a call to something named
draw_rect
ordraw_multiline
(which uses polylines when width is less than 0) you wouldn't expect the amount of work that's being done behind the scenes.Below is a video of different methods one might take to draw a simple 100x100 grid and how it looks when zoomed out.
x264.godot.windows.editor.x86_64_2023-12-08_07-23-54.mp4
DrawLine is so much faster because it is not drawing rects, it is just drawing the lines necessary to draw that grid, so it's not really relevant but I've listed it below for reference.
Times are the result of
Time.get_ticks_usec() - start_time
so this doesn't necessarily include viewport CPU and GPU stats which can be seen in the video.Of note:
4 draw_line calls is superior to any polyline usage and one draw_multiline call takes longer than 4 draw_line calls.
I couldn't find a way to make draw_multiline faster than 4 draw_line's per rect (even when calling only 1 draw_multiline for the whole grid vs 202 draw_line calls.)
Even the overhead of simply calling draw_lines a bunch (for each cell) is pretty bad, but I think that's more of a GDScript method call overhead issue.
Getting godot to draw the grid efficiently (columns and rows only) with draw_polyline calls takes ~17x longer than with draw_line calls.
This isn't advocating for going through the effort of making polygons faster by changing the render pipeline, but to point out that using polygons for all of these simple things is not a very good decision performance wise. It was probably the lowest effort implementation for these things, but godot has a bit of a "performance only needs to be okay" culture which leads to things like draw_circle defaulting to a resolution of 64 points (because who uses draw_circle?)
Below is a comparison between one polyline (50k points) vs many polylines (5 points * 10000 lines) vs the setup time (array slicing) for the many polylines benchmark.
x264.godot.windows.editor.x86_64_2023-12-08_08-09-23.mp4
VerySleepy for BigPolyline

VerySleepy for ManyPolylines

Beta Was this translation helpful? Give feedback.
All reactions