-
Notifications
You must be signed in to change notification settings - Fork 900
Improve rendering performances of strokes #6385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
I don't think so. The rendering output would probably only look a bit more natural.
That's the harder question. I would probably go for the cheaper rendering. The impact looks more signficant and more in spirit of our goals:
So method 2 looks like the way to go for me. I will take a look how PDF sizes are affected for exports of the documents I write in class though. I'm wondering how this will work out in Gtk 4 in case we eventua
8000
lly move to render node based rendering (using |
I agree with you about that "the rendering in 2 seems to make more sense in most situations" (hence my implementing it). However, it looks like the PDF size change is quite big (>= x2) for handwritten notes. This is IMO the biggest obstruction with this method...
Well, there is a gtk_snapshot_append_fill taking in a |
model/MathVect.h.
Strokes are now implemented by constructing their contour and calling cairo_fill() only once (as opposed to once per segment). This results in huge performance improvements, depending on the ratio average distance btw points / average width
Alright. I went for the half/half solution involving 1. and 3. Even if 2. feels more natural, I think the export size of PDF files was just way too big. On an implementation level, I kept debugging functions in. We may want to remove them. And in terms of performances, this implementation is much faster than master's current one. In benchmarks, the FPS improvement seemed to depend on the ratio I think this is now ready for review and testing. |
That sounds reasonable. There are no drawbacks compared to master and most of the rendering performance gain from 2 is there in 3 as well.
I'm particularly interested how this works out on MacOS, where rendering performance is still below par compared to Linux and Windows. |
\action create-installers macos windows |
Caution The following targets are invalid: ["macos"]. Usage is |
\action create-installers mac-x64 mac-arm windows |
Started 3 build(s) (see details) |
CI successful! |
Look forward to latency improvements. Wonder what fps could be obtained by disabling pressure sensitivity and smoothing? |
@rolandlo I made a new benchmarking tool that should work on every supported platform. It is based on gtk-demo's benchmark app. It allows to benchmark the three options of the OP (as a translation dictionary: "XOPP original" means option 1. in the OP, "XOPP contour" means option 3. and "Conic contour" means option 2.). |
Disabling pressure sensitivity is already possible in the settings. The rendering performances should indeed improve significantly.
It should allow for a more efficient use of hardware acceleration on some platforms (MacOS for instance). But this will only be possible once Xournal++ is fully ported to gtk4. Then we'll be able to benchmark this. |
@bhennion Would like to keep pressure sensitivity for complete stroke renders(once per whole stroke) but turn it off for real-time display of ink. Could this be arranged? Will this give 50fps? When do you see gtk4 port coming out roughly? |
cairo_line_to(cr, p2.x, p2.y); | ||
cairo_arc(cr, p2.x, p2.y, .5 * p2.z, a1, a3 - M_PI_2); |
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.
Can you please explain this important case? I see where the path must head to, but why in this precise way?
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.
Imagine you were to draw the contour of a single segment. You would make two half circles and two lines. In this contour, you have 4 junction vertices where a line meets a half circle.
The case we have here corresponds to: the junction vertices for the first segment are in the second segment's (This rarely happens -- we need a big width-variation and a small motion -- but it happens).
It this case, we fallback to a conservative (but suboptimal) approach:
- The cairo cursor is currently at one of the junction vertices of the first segment, so within the second segment. (the top of the small red half circle on the left)
- We go to the center of the disk (draw the red oblique line on the left)
- Then we draw the full quarter circle that corresponds to half of the the second's segment first half circle (the large red quarter circle on the left). Doing that also add a ray (the horizontal blue line on the left).
This is the simplest solution I could think of to ensure everything is painted upon cairo_fill. It is obviously not optimal (in the above example, we could actually optimize out the last point of the stroke), but it is provably doing the right thing: locally, we are always turning clockwise, so the winding number always increases and cannot accidentally drop back to 0 where is should not.
You can see other examples of this case at the bottom of this file
example-pp.pdf
This PR tries to improve rendering performances of strokes, following #4662.
As explained in #4662, the idea is to compute the contour of a stroke and call cairo_fill() only once per stroke (the current implementation calls cairo_stroke() - and hence cairo_fill() - once per point in the stroke).
The key is hence to compute the contour of a stroke. The contour means "some cairo_path on which to call cairo_fill() to obtain the desired stroke".
There are many different ways of getting a stroke from a sequence of points (x, y, pressure). I tried out two, so we have 3 rendering methods to discuss.
Make a contour out of that idea and call cairo_fill() only once.
Make a contour out of that idea and call cairo_fill() only once. This one gives the exact same output as current master's implementation.
You can see a bunch of unnatural test cases in this example file example-v2.pdf example-v3.pdf.
Version 2. is somehow more intuitive, but 3. (and 1.) is what we've been rendering so far. Of course, for a "normal" stroke, the difference is not really visible.
I benchmarked the three versions (you can try the benchmarks on linux). I get (on a modest laptop with integrated GPU).
stroke-demo --xopp
)stroke-demo
) (edit: latest version 24fps)stroke-demo --pixel-precise
) (edit: latest version 19.3ps)Importantly, the way we render strokes also has an effect on Exports. On an handwritten page, I get:
To summarize, we have 3 rendering methods:
I tend to favor option 2. (for the efficiency and clean logic of the rendering) but a mix of 1. (for PDF exports) and 3. (for other renderings) could also be a good choice. @xournalpp/core I opened this draft PR with the following questions in mind: