-
-
Notifications
You must be signed in to change notification settings - Fork 21.8k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Implement 2D vector graphics draw command #75278
base: master
Are you sure you want to change the base?
Conversation
fe797db
to
436a948
Compare
3038558
to
1786430
Compare
I support this as someone who assisted with adding thorvg vector graphics to godot engine. Don't see anything obviously wrong, but didn't review. |
The Bentley-Ottmann implementation here is still imperfect. Various mathematical invariants the algorithm depends on simply do not apply in floating point. I'm testing various ways to mitigate issues stemming from rounding errors. If I don't get a satisfactory result, I might test an implementation based on fixed point instead. |
Remember godot has a 64.64 fixed point in the thirdparty misc folder |
f532c53
to
59c87fd
Compare
Okay, I achieved all that can be done using floating points. This implementation is guaranteed to execute in finite time with no assertion failures 100% of the time, and give a correct result 99.999% of the time. Short of using fixed point, and a whole bunch of tricks to handle every tiny edge case, this is the best that can be achieved. |
Okay, I have a slight dilemma. After a lot of effort, I was able to make an implementation based on fixed point that's 100% fool-proof. But I'm hesitating to push it because it's noticeably slower. By an order of magnitude. There are a few points that could maybe be made faster. But it's not certain that the gap can be reasonably closed. The question becomes: Is it better to have a solution that works 99% of the time and is fast, or should I make the effort to optimize the solution that works 100% of the time, but may not run as fast? |
It depends on the performance metrics. Anything merged into the engine needs to be fast enough to be used in games (ideally on all target platforms as well). It would be helpful to see a comparison (visual and performance) between this and the previous approaches to drawing curves to get a feel for the tradeoffs being made |
@clayjohn Missing the intersection causes it to get the shape wrong. A "wrong" shape will still use the same original points, but have different triangles filled. There may be other set-ups that cause similar issues due to floating points being rounded in some direction, but this is the one that I could create consistently. Here's the same shape in the new version. The same visual bug can't be created: Now for a performance comparison. Performance heavily depends on the complexity of the objects, so I used a few scenes for testing. Here's the old version: And here's the new one after the first round of optimizations: There's still more room to optimize, but the new version will never be as fast or faster than the old one. At best, it will reach the point of a small slowdown. Of course, my desktop PC might not be the best representation for performance. And I also didn't jump through the hoops of trying to build other exports to see how much performance is harmed on web or mobile (in which the low memory footprint of vector should give the most advantage over raster). For the time being, I uploaded the fixed point version to a new fork, so anyone can test both versions: The easiest way to test this PR without needing to mess with code is using my SVG import plugin: |
Also, I'm noticing there's been a trivial conflict introduced since I pushed this PR. Should I rebase? Now that I've put the new version in a separate branch, I can rebase the old version more easily. |
59c87fd
to
6344550
Compare
I'd lean towards the one that performs better but has 99% accuracy, from a practical standpoint. You could add code to I have a question about p_types, how are you allowing it to define holes? Like using Z or M to separate subpaths in SVG. Or is that not coded at the moment? |
I created a project to benchmark what I consider "typical usage" of vector graphics. It doesn't have tweening/skeleton, but the performance of those would be independent of the PR anyway. The important part is that it's game-like, and has a game-like object count and update rate: I've also made an improved version using fixed-point. It still has some areas that can be improved, but the optimization surface is dwindling. I clocked the various options on my (relatively powerful) machine, and got the following:
I'll try to do a similar benchmark in HTML5, to see how a "professional implementation" compares. Setting aside how much slower the fixed point solution is, the fact that none of the options are able to handle more than a few dozen bunnies before facing a frame drop, even on a desktop computer, is somewhat alarming. @Giwayume To make a multi-path ( Note that since this draws a filled curve, each shape is assumed to be closed, i.e. there's no notion of an "open shape". Every |
Hmm, I still think this effort is useful for things like path morphing, specifically because it can be mixed with other techniques that perform better for static assets. I did have another question, any way you can think this could be combined with custom shaders, as opposed to just being able to render a raster texture? For example, COLOR would have the shape alpha already stored in its vec4 so you could use a vector path like a mask over drawing any effect you like. Without this, certain things like drawing radial gradients with sharp stops could look bad with a low res raster texture, whereas it could be implemented perfectly in shader code. |
@Giwayume There's no issue putting a shader on a canvas item that uses If you want to use it as a mask, setting "clip children" on the containing canvas item would do the trick. You can look at the SVG import plugin I linked above, and import an SVG with gradients, to see how linear and radial gradients are implemented via a shader. |
6344550
to
657c3ae
Compare
80a2280
to
5994bbd
Compare
I finally managed to create a fixed point (well, big rational, technically) solution that outperforms the floating point one. It cleverly uses the added accuracy to reduce the edge cases, and cleanly resolve the remaining ones. To simply the math and prevent overflow, the fixed point numbers used are 20 bits. The floating points are normalized to the largest exponent, in the canvas item's local space. So, while rounding errors may occur if the vector is far from the origin of the canvas item (the canvas item can be as far from the origin of the scene/camera as desired, however), from largest detail to smallest, it gives roughly the accuracy of a sprite that's about one million by one million pixels. Put differently, a full-screen sprite at 4k can be magnified 25,000% without being even a single pixel off. I've also tested the performance of equivalent drawing using HTML5 canvas on Brave. While the canvas version is not perfectly minimal, I doubt sub-optimal JS is any more of a bottleneck than GDScript. The Brave version also ran at slightly lower, but roughly similar resolution, although it's impossible to tell if Brave subdivides to the same accuracy. Nevertheless, my version managed to outperform the browser, meaning it's as fast as would be standard for this sort of graphics. Here are the final benchmark results:
Since the biggest bottleneck is CPU, and the algorithm is running on a single core, performance can be increased by using a worker pool to divide the work to multiple cores. However, this would potentially take away resources from other services (physics, navigation), so I'm leaving this as a possible future improvement. At any rate, this PR is now merge-ready. It has reached perfect correctness without sacrificing performance. |
5994bbd
to
fe64877
Compare
Update to anyone following: Input on this would be welcome. |
|
||
static void tessellate_cubic_bezier_in_rect(Vector<Vector2> &r_out, Vector2 p_start, Vector2 p_control1, Vector2 p_control2, Vector2 p_end, Vector2 p_start_transformed, Vector2 p_control1_transformed, Vector2 p_control2_transformed, Vector2 p_end_transformed, const Rect2 &p_limit) { | ||
while (true) { | ||
// Stop condition - Completely out of bounds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Stop condition - Completely out of bounds | |
// Stop condition - Completely out of bounds. |
As per the comment style, same elsewhere (including code you've just copied)
fe64877
to
2a7d7bd
Compare
Fixed comment formatting |
Any update on this? Given the great, hard work that has been put on this, it would be a shame not to integrate it. And it seems to be a necessary step to solve godotengine/godot-proposals#9846 |
It's been rejected in a rendering meeting a few months ago. Here are a few reasons:
So, there are currently no plans to merge this, and alternative features are mostly in long-term planning status. |
Closes godotengine/godot-proposals#2924
Adds a
draw_filled_curve
method toCanvasItem
, as well as a few methods used in its implementation. Supports cubic beziers, elliptical arcs, and, optionally, order-5 beziers (for easy C2 continuity).Maintains smoothness at any level of zoom or curvature.