Compare commits

...

117 Commits

Author SHA1 Message Date
Benjamin Otte
e390921500 vulkan: Make winding number computation more stable
Instead of just counting the roots, evaluate the y value between the
roots to ensure that the sign really did switch.

Gets rid of spurious winding number changes causing horizontal lines.

What is still causing horizontal lines is that the root solver misses
roots from time to time.
2023-07-31 16:51:04 +02:00
Benjamin Otte
78e3024924 vulkan: Split out color node special casing
It's done in multiple places, and because apps tend to use opacity +
color, we try to detect that, too.

Theoretically, other things, like color-matrix or crossfades, could be
added here, too.
2023-07-31 16:51:04 +02:00
Benjamin Otte
1382a2beaa vulkan: Make sure to create framebuffer-compatible images
When we want to render alpha, we need to create a
framebuffer-compatible format (ie, not do alpha-only).

Technically, we could create 2 different image views, one for rendering
and one for later usage, but I'm not going to go there today.
2023-07-31 16:51:04 +02:00
Benjamin Otte
f2c20b7ca9 contour: Compute stroke bounds correctly
We need to set the minimum miterwidth to sqrt(2) * line_width to catch
the square joins/butts of diagonal lines, because the rectangle spanned
by the square is rotated by 90 degrees.
2023-07-31 16:51:04 +02:00
Benjamin Otte
538ca8e0d4 vulkan: Add support for stroke nodes 2023-07-31 16:51:04 +02:00
Benjamin Otte
85a3931d41 vulkan: Use device for allocations
Big refactor - because suddenly everything cares about devices instead
of contexts.

But nothing really changed.
2023-07-31 16:51:04 +02:00
Benjamin Otte
560d42cf62 vulkan: Add GskVulkanDevice
This is the per-device global thing that is holding all the relevant
per-device stuff, like doing memory management, caching, and so on.

This is just the plumbing, and it's written so that it works without
GdkVulkanContext, which is one thing I want to get rid of.
2023-07-31 16:51:04 +02:00
Benjamin Otte
965b8c26bb vulkan: Add a cache allocation to buddy allocator
Instead of immediately freeing memory, keep one chunk around, because
surely somebody is gonna do another allocation soon.
2023-07-31 16:51:04 +02:00
Benjamin Otte
baf62c8923 vulkan: Add a simple buddy allocator 2023-07-31 16:51:04 +02:00
Benjamin Otte
d6c095a18b vulkan: Handle optimal tiling better
1. Request optimal tiling for offscreens
2. Don't use  host-mappable memory for optimal tiling.

Optimal tiling does not support memory mapping.
2023-07-31 16:51:04 +02:00
Benjamin Otte
9a6b1ff82b vulkan: Fix storage buffer handling
Instead of keeping a 256MB storage buffer around just to be safe, create
a 32kB one and handle enlarging the bfufer by 32kB when necessary.

The 32kB is quite arbitrarily chosen.
2023-07-31 16:51:04 +02:00
Benjamin Otte
5d30b347a9 vulkan: Get rid of GskVulkanMemory
Make buffers and images use GskVulkanAllocation instead and use the
allocator directly.

Also simplify the map/unmap into a simple get_data() call now that we
know we can keep everything mapped forever.

While doing this, pass alignment requirements to the allocator.

And copy/paste the memory type selection code from the Vulkan
documentation, so we do the same thing everybody else does.
2023-07-31 16:51:04 +02:00
Benjamin Otte
0ebcfc8bde vulkan: Add a dumb allocator that spams stout with stats 2023-07-31 16:51:04 +02:00
Benjamin Otte
dfb0d18290 vulkan: Introduce a memory allocator infrastructure
The only big difference (apart from lots more vfuncs) is that we map
mappable memory automatically and there is no longer a map/unmap step
involved.

According to various Vulkan docs, it is not necessary to do that
map/unmap shuffling, so we don't do it anymore.
2023-07-31 16:51:04 +02:00
Benjamin Otte
22590d24e1 vulkan: Implement support for all fill children
If the fill is not with a color, render the path to an alpha mask and
then mask the child with it.
2023-07-31 16:51:04 +02:00
Benjamin Otte
66e3082e2e vulkan: Rewrite fill shader a bit
- Split up distance in winding computation.
- Keep track of max interesting distance and exit early if stuff is
  further away
- don't compute winding for cubics entirely above/below the ray
2023-07-31 16:51:04 +02:00
Benjamin Otte
d2aa139e86 vulkan: Make fill node handle multiple contours
We still only handle standard contours, but now we can do multiple.
2023-07-31 16:51:04 +02:00
Benjamin Otte
9f12c5df07 XXX: hack node-editor to not die with paris-30k
Disable GL and Cairo renderer and turn off hilighting
2023-07-31 16:51:04 +02:00
Benjamin Otte
6fb33c775e node-editor: Don't reparse node when scaling
Makes scaling complex nodes bearable.
2023-07-31 16:51:04 +02:00
Benjamin Otte
ff9903d47f XXX: vulkan: Ignore stroke nodes
Useful for benchmarking.
2023-07-31 16:51:04 +02:00
Benjamin Otte
5eb0fa8b09 vulkan: Make the fill node use the contour output
For now, it only does a single standard contour.
And it can only do lines and cubics.
2023-07-31 16:51:04 +02:00
Benjamin Otte
7057f96c78 contour: Add gsk_contour_to_shader() function
This converts the contour into the format we pass to the GLSL shaders.

FIXME: Someone document that format maybe.
2023-07-31 16:51:04 +02:00
Benjamin Otte
8b6dffa5d2 vulkan: Add a naive fill node implementation 2023-07-31 16:51:04 +02:00
Benjamin Otte
19798883c4 xxx: stroke node bounds 2023-07-31 16:51:04 +02:00
Benjamin Otte
a7c842ee9d xxx debug-printf
Doesn't work yet, booo!
2023-07-31 16:51:04 +02:00
Matthias Clasen
802216e7c1 Add gsk vulkan tests
Add a testsuite called gsk-compare-vulkan to run
the gsk renderer tests with the Vulkan renderer.

The current stats:

Ok:                 184
Expected Fail:      0
Fail:               204
Unexpected Pass:    0
Skipped:            2
Timeout:            0

For now, we mark all the tests as failing to
avoid ci breakage. To run the tests locally,
you can do:

meson test -C_build --suite gsk-compare-vulkan
2023-07-31 16:51:04 +02:00
Benjamin Otte
30ed2603a9 node-editor: Add initial comparison UI
And yes, I'm aware of the icon I'm using.
2023-07-31 16:51:04 +02:00
Benjamin Otte
6631df684f node-editor: Add some form of undo stack
I have no idea about the UI for this yet, but there's a list of nodes
now that you can click on to get previous ones back.

Ultimately I want to have some way to compare nodes or do transitions,
but so far, this is an experiment.
2023-07-31 16:51:04 +02:00
Benjamin Otte
bc67bb3830 testsuite: Add a test for repeat node offscreen scaling
Ensure that the offscreens for repeat nodes pick the right xscale and
yscale so that they render pixel-aligned.
2023-07-31 16:51:04 +02:00
Benjamin Otte
1d02c78e1b testsuite: Add another test
This test ensures that offscreens for cross-fade children are properly
clipped and that the renderers can deal with the two not overlapping.
2023-07-31 16:51:04 +02:00
Benjamin Otte
29bd22025d testsuite: Add test for gradients
Test that it can do 64 color stops.

Should ensure that renderers either can do unlimited amounts or have
fallbacks in place.
2023-07-31 16:51:04 +02:00
Benjamin Otte
930ea4e019 testsuite: Test we don't crash with overly large nodes
... when these nodes are used as children of a complex transform nodes
and we lose the clip.
2023-07-31 16:51:04 +02:00
Benjamin Otte
7f5cca028a testsuite: Increase tolerances to make the path test work
I dislike the corner cases being so low (lower than 1% and off by a
whole point).
2023-07-31 16:51:04 +02:00
Benjamin Otte
e88d753f64 path: Handle segments of degenerate curves via lines
When curves have long straight parts that decompose into a single line,
don't try to split them. Instead, split at a well-known position and a
the part of that long straight line as a line.

This gives way more accurate numbers and is more accurate than 1% for
everything but contours with very small weight and
start_point == end_point, where the error is closer to 1.5%.
2023-07-31 16:51:04 +02:00
Benjamin Otte
34c52b4683 curve: Add a reason to decomposition
When decomposing curves that are too straight, we may emit lines for
long parts of the curve. These lines do not properly map
  t => distance
and it is better to treat them as a regular line than a curve.

This reason argument gives that information.

No users so far, that will happen in followup commits.
2023-07-31 16:51:04 +02:00
Benjamin Otte
ed737beda1 path: Move gsk_path_new_from_cairo() away
This is a regular path creation API, so treat it that way.

On top, it is rather awkward if the only constructor for a path that is
immediately visible to people reading the docs is the one that takes a
Cairo path - when we want to deprecate Cairo.
2023-07-31 16:51:04 +02:00
Matthias Clasen
5c7c452ea3 gtk-demo: Rewrite the text mask demo
Use GtkSnapshot and GskPath instead of cairo for this.
2023-07-31 16:51:04 +02:00
Benjamin Otte
25277d4d9e gtk-demo: Make path-text demo use gsk_builder_add_layout() 2023-07-31 16:51:04 +02:00
Matthias Clasen
a11d9cb942 path fill demo: Use gsk_path_builder_add_layout
We have an api now to hide the cairo use.
2023-07-31 16:51:04 +02:00
Matthias Clasen
49e3e8242a gsk: Add gsk_path_builder_add_layout
This api makes it easy to turn text into a path that
can be further manipulated. The implementation currently
goes via cairo.
2023-07-31 16:51:04 +02:00
Benjamin Otte
bae72fd737 path: Add gsk_path_builder_add_ellipse() 2023-07-31 16:51:04 +02:00
Benjamin Otte
4d4f4791e4 path: Change semantics of gtk_path_builder_add_segment()
Allow start >= end to mean that the path continues at the beginning
after reaching the end until it reaches the point at @end.
2023-07-31 16:51:04 +02:00
Benjamin Otte
ea3983b9ab path: Add gsk_path_measure_is_closed () 2023-07-31 16:51:04 +02:00
Benjamin Otte
c410e0510d path: Add gsk_path_measure_restrict_to_contour() 2023-07-31 16:51:04 +02:00
Matthias Clasen
67ee3ba0a3 Add gsk_path_measure_get_{path,tolerance}
These are just nice apis to have and avoid having to carry
these around as extra arguments in many places.

This was showing up as inconvenience in writing tests
for the measure apis.
2023-07-31 16:51:04 +02:00
Benjamin Otte
2f7ef4832e xxx path)_fill 2023-07-31 16:51:04 +02:00
Matthias Clasen
f728418d22 Add gsk_path_get_stroke_bounds
A relatively cheap way to get bounds for the area
that would be affected by stroking a path.
2023-07-31 16:51:04 +02:00
Benjamin Otte
5328e6d1ca testsuite: Add tests for the dasher 2023-07-31 16:51:04 +02:00
Benjamin Otte
70319b0316 path: Add a foreach function that dashes a path 2023-07-31 16:51:04 +02:00
Benjamin Otte
eb543bc58b path: Deal with non-uniformness of progress parameter
The progress is non-uniform, so simple translation of progress doesn't work.
So check if larger and smaller values inch closer towards minimal distance.
2023-07-31 16:51:04 +02:00
Benjamin Otte
e277bcdd12 path: Always decompose conics into at least 2 segments
Conics are evil in that their parameter skews towards the center, and if
it's a very flat conic (weight almost equal to 0), then we'd approximate
it with a single segment and not subdivide, which would cause the
parameter to be wildly off around 0.25 or 0.75.

And that would cause offset calculations to fail.
2023-07-31 16:51:04 +02:00
Matthias Clasen
e52d9d55a8 testsuite Add curve tangent tests 2023-07-31 16:51:04 +02:00
Benjamin Otte
b66935f168 testsuite: Add a test for the conic that got us segment() 2023-07-31 16:51:04 +02:00
Benjamin Otte
947e813d4b path: Add gsk_curve_segment()
Using split() twice with scaled t values does not work with conics.
2023-07-31 16:51:04 +02:00
Benjamin Otte
673d85ff16 testsuite: Add a test for gsk_curve_decompose() 2023-07-31 16:51:04 +02:00
Benjamin Otte
5a85046c09 testuite: Add tests for gsk_curve_get_tangent() 2023-07-31 16:51:04 +02:00
Matthias Clasen
887c1ff416 testuite: Add tests for gsk_curve_get_point()
Add a few tests for gsk_curve_get_point().

Since GskCurve is not public api, we add gskcurve.c
as source to the test binary.
2023-07-31 16:51:04 +02:00
Benjamin Otte
5877155c40 curve: Split eval() into get_point() and get_tangent()
That's more in line with the get_start/end_point/tangent() functions.

Plus, those calls are independent and we usually want one or the other.
2023-07-31 16:51:04 +02:00
Matthias Clasen
041b9170cc Add gsk_curve_get_{start,end}_tangent
Add a way to get the tangents at the start and end of the curve.
This will be used in stroking.
2023-07-31 16:51:04 +02:00
Benjamin Otte
968f5aea61 testsuite: Add conics to the random paths 2023-07-31 16:51:04 +02:00
Benjamin Otte
2badf27114 path: Add GskCurve
GskCurve is an abstraction for path operations. It's essentially a
collection of vfuncs per GskPathOperation.

GskStandardContour has been ported to use it where appropriate.
2023-07-31 16:51:04 +02:00
Benjamin Otte
0a05d864d1 path: Introduce gskpathop
A gskpathop is a pointer to a graphene_point_t* with the low bits used
to encode the GskPathOperation. It's an easy way to introduce API for
operations.

So far it's just used to replace GskStandardOperation.
2023-07-31 16:51:04 +02:00
Benjamin Otte
46fe43c5df WIP: css: Replace border rendering code with GskPath
The weight is wrong still, I need to compute the correct one to get real
45deg circle corners and not just roughly correct ones.
2023-07-31 16:51:04 +02:00
Benjamin Otte
74164440f6 WIP: pathbuilder: Add gsk_path_builder_add_rounded_rect()
It works, but does not use a custom contour yet.
2023-07-31 16:51:04 +02:00
Benjamin Otte
0cea93777a path: Add conic curves
So far this just adds the API, if you use it, you'll get lots of
g_warnings().

This will be fixed in future commits.
2023-07-31 16:51:04 +02:00
Benjamin Otte
70077d9a2a path: Rename to gtk_path_builder_add_segment()
It's about bulding paths, not about measuring them.
2023-07-31 16:51:04 +02:00
Benjamin Otte
df34aceee1 path: Split contours into their own file
I'm not sure I want to keep all contours in one file, but for now that's
how it is.
2023-07-31 16:51:04 +02:00
Benjamin Otte
70c6756639 path: Make all private contour APIs take a GskContour
... instead of a path, index tuple.
2023-07-31 16:51:04 +02:00
Benjamin Otte
63398ea869 stroke: Add support for dashes
... and hook it up in the node parser and for Cairo rendering.
2023-07-31 16:51:04 +02:00
Matthias Clasen
d315e0687e gsk: Implement parsing fill and stroke nodes
Make serialization and deserialization work for stroke and
fill nodes.
2023-07-31 16:51:04 +02:00
Benjamin Otte
89c63d1517 path: Add flags to gsk_path_foreach()
This way we can default to the siplest possible foreach() output - like
cairo_copy_path_flat() decomposing everything into lines - and add flags
to get more and more fancy.

This will be useful to have conics automatically decomposed for Cairo
drawing or if we want to add more line types in the future.
2023-07-31 16:51:04 +02:00
Benjamin Otte
3c5369a9ff testsuite: Add an in_fill() test 2023-07-31 16:51:04 +02:00
Matthias Clasen
e81e80e069 Implement gsk_path_measure_in_fill
Implement this in the obvious way, using the decomposed form
of standard contours. Since the decomposed form is part of the
measure object, this api moves from gsk_path_in_fill to
gsk_path_measure_in_fill.
2023-07-31 16:51:04 +02:00
Benjamin Otte
6dba706a9a testsuite: Add a parsing test
This test includes an implementation of a gsk_path_equal() func with
a tolerance that is necessary because parsing does not always work
100% exactly due to floating point rounding, so we can't just
compare the to_string() output.
2023-07-31 16:51:04 +02:00
Matthias Clasen
673e92645a path: Special-case rects and circles
Write out the commands for rects and circles in a special
way, and add code in the parser to recognize this, so we
can successfully round-trip these through the SVG path format.

The special way - for people who want to use it for debugging -
for now is that we use uppercase "Z" to close standard paths, but
lowercase "z" to close our special paths.

A test is included, but the random path serializations should take care
of it, too.
2023-07-31 16:51:04 +02:00
Matthias Clasen
8ef4b40e97 path: Fix serialization for circles
The svg A can not do a full circle, since it is a two point
parametrization - if the start and end point are the same,
it draws nothing. So, use two arcs.
2023-07-31 16:51:04 +02:00
Benjamin Otte
0ab25f0580 testsuite: Add librsvg path tests 2023-07-31 16:51:04 +02:00
Matthias Clasen
192bf4bbbe path: Implement gsk_path_parse
Implement the SVG path syntax to read back the strings
that we generate when serializing paths. The tests for
this code are taken from librsvg.

This includes an elliptical arc implementation according
to the SVG spec. The code is mostly taken from librsvg,
but pretty directly follows the SVG spec implementation
notes. We don't export this, since the parametrization
is inconvenient. We do want an arc_to API, but
these are not the arcs we are looking for.
2023-07-31 16:51:04 +02:00
Matthias Clasen
bddffb08cc path: Implement SVG arcs
This is elliptical arc implementation according to the SVG spec.
The code is mostly taken from librsvg, but pretty directly
follows the SVG spec implementation notes.

We don't export this, since the parametrization is inconvenient.
We do want an arc_to API, but these are not the arcs we are
looking for.

It will be used in parsing SVG path syntax.
2023-07-31 16:51:04 +02:00
Matthias Clasen
84dbd7c46f stroke: Add miter limit
Add a miter limit to GskStroke. This will be needed to
fully implement line joins.

Also introduce the GSK_LINE_JOIN_MITER_CLIP value,
following SVG 2.0. cairo does not have it, so translate
it to plain miter when using cairo.
2023-07-31 16:51:04 +02:00
Matthias Clasen
4487cafd40 Documentation typo fixes 2023-07-31 16:51:04 +02:00
Benjamin Otte
2f930f93cc testsuite: Add relative path functions
They're making the paths slightly weirder, but they test public API, so
woohoo!
2023-07-31 16:51:04 +02:00
Benjamin Otte
0a947d9a6a pathbuilder: Add relative path commands
And gsk_path_builder_get_current_point().

They will be needed by the string parser.
2023-07-31 16:51:04 +02:00
Benjamin Otte
46f405ac7d path: Add GSK_CIRCLE_POINT_INIT() to initialize points on the circle
This is just splitting out a commonly done operation into a macro.
2023-07-31 16:51:04 +02:00
Benjamin Otte
4f03537a28 pathbuilder: Redo semantics for starting curves
We now always have a "current point" which is either the last point an
operation was made to, or (0, 0) if no drawing operation has
been made yet.

Adding a contour of any kind to the builder will always update the
current point to that contour's end point.
2023-07-31 16:51:04 +02:00
Benjamin Otte
1621b53570 xxx: demo 2023-07-31 16:51:04 +02:00
Benjamin Otte
7e66dfe902 pathbuilder: Rename "builder" variables to "self"
This is a pure find/replace that is now possible after
the split in the previous commit.
2023-07-31 16:51:04 +02:00
Benjamin Otte
7bc6fabbf7 path: Split GskPathBuilder into its own file
... and add missing API docs.
2023-07-31 16:51:04 +02:00
Benjamin Otte
26c8702400 testsuite: Add a test using get_point() and get_closest_point() 2023-07-31 16:51:04 +02:00
Benjamin Otte
1b7c4420dc testsuite: Add a test for get_point() 2023-07-31 16:51:04 +02:00
Benjamin Otte
ece3153b1f testsuite: Update create_random_path()
1. Allow specifying the max number of contours
2. Be smarter about creating the paths:
   With 10% chance, create a "weird" path like the empty one or only
   points or things like that.
   Otherwise create a bunch of contours, with 2/3 a standard contour,
   with 1/3 a predetermined one.
2023-07-31 16:51:04 +02:00
Benjamin Otte
74dc039c7a gtk-demo: Add cute maze demo 2023-07-31 16:51:04 +02:00
Benjamin Otte
3e1c3aca2a testsuite: Add tests for gsk_path_measure_get_closest_point() 2023-07-31 16:51:04 +02:00
Benjamin Otte
51bc26b314 path: Add gsk_path_measure_get_closest_point()
... and gsk_path_measure_get_closest_point_full().

Those 2 functions allow finding the closest point on a path to a given
point.
2023-07-31 16:51:04 +02:00
Benjamin Otte
51a1adf312 spline: Use Skia's tolerance checks
This avoids measuring being too far off (it's still off, but it's less
than a percent now.
2023-07-31 16:51:04 +02:00
Benjamin Otte
d84132824f testsuite: Add tests for gsk_path_measure_add_segment() 2023-07-31 16:51:03 +02:00
Benjamin Otte
ffecc89352 gtk-demo: Add a text-on-path demo 2023-07-31 16:51:03 +02:00
Benjamin Otte
77518895ea demos: Add a simple demo filling a path 2023-07-31 16:51:03 +02:00
Benjamin Otte
9685a47693 path: Add gsk_path_measure_get_point()
Allows querying the coordinates and direction of any specific point on a
path.
2023-07-31 16:51:03 +02:00
Matthias Clasen
7f9825919c path: Add gsk_path_add_circle()
Adds a circle contour, too.
2023-07-31 16:51:03 +02:00
Benjamin Otte
2795fc53c1 pathmeasure: Implement support for beziers
Instead of treating bezier curves as lines, we properly decompose them
into line segments now so that we can treat those as lines.
2023-07-31 16:51:03 +02:00
Benjamin Otte
20ba4a225c path: Implement gsk_path_to_cairo() using foreach() 2023-07-31 16:51:03 +02:00
Benjamin Otte
eadbfc3433 path: Add gsk_path_foreach() 2023-07-31 16:51:03 +02:00
Benjamin Otte
b8ee48de63 path: Collect flags
We don't need them yet, but maybe later.
2023-07-31 16:51:03 +02:00
Benjamin Otte
25a5a8a85d testsuite: Add path tests 2023-07-31 16:51:03 +02:00
Benjamin Otte
5e1170bdb2 pathmeasure: Add gsk_path_measure_add_segment()
This allows chunking paths, weeee.
2023-07-31 16:51:03 +02:00
Benjamin Otte
f6bf74e2c9 path: Add gsk_path_builder_add_path() 2023-07-31 16:51:03 +02:00
Benjamin Otte
fa51c2eb0e gsk: Add GskPathMeasure
An object to do measuring operations on paths - determining their
length, cutting off subpaths, things like that.
2023-07-31 16:51:03 +02:00
Benjamin Otte
d34f1bbd6c path: Change data structure for standard path
Instead of the Cairo method and imitating cairo_path_data_t, use the
Skia method and keep points and operations separate.

That way we get a points array that includes the starting point -
because it's always the end point of the previous operation.
2023-07-31 16:51:03 +02:00
Benjamin Otte
8fd55e9f89 snapshot: Add gtk_snapshot_push_stroke() 2023-07-31 16:51:03 +02:00
Benjamin Otte
be4beeb8f2 gsk: Add GskStrokeNode 2023-07-31 16:51:03 +02:00
Benjamin Otte
09288f621a gsk: Add GskStroke
It's unused in this commit. This just prepares the new object.
2023-07-31 16:51:03 +02:00
Benjamin Otte
3ec3727575 snapshot: Add gtk_snapshot_push_fill() 2023-07-31 16:51:03 +02:00
Benjamin Otte
77e58d4463 rendernode: Implement fast-path for solid fills
They happen so often that it's worth avoiding the clip + paint path
there.
2023-07-31 16:51:03 +02:00
Benjamin Otte
bb58b504a6 gsk: Add GskFillNode
Take a rendernode as source and a GskPath and fill the region in the
path just like cairo_fill() would.
2023-07-31 16:51:03 +02:00
Benjamin Otte
f6bb568e91 gsk: Add GskPath 2023-07-31 16:51:03 +02:00
Benjamin Otte
efbb3ab535 xxx: benchmarking 2023-07-31 16:51:03 +02:00
93 changed files with 15293 additions and 672 deletions

View File

@@ -335,6 +335,9 @@
<file>paintable_symbolic.c</file>
<file>panes.c</file>
<file>password_entry.c</file>
<file>path_fill.c</file>
<file>path_maze.c</file>
<file>path_text.c</file>
<file>peg_solitaire.c</file>
<file>pickers.c</file>
<file>printing.c</file>
@@ -420,6 +423,9 @@
<gresource prefix="/fontrendering">
<file>fontrendering.ui</file>
</gresource>
<gresource prefix="/path_text">
<file>path_text.ui</file>
</gresource>
<gresource prefix="/org/gtk/Demo4">
<file>icons/16x16/actions/application-exit.png</file>
<file>icons/16x16/actions/document-new.png</file>

View File

@@ -72,6 +72,9 @@ demos = files([
'paintable_symbolic.c',
'panes.c',
'password_entry.c',
'path_fill.c',
'path_maze.c',
'path_text.c',
'peg_solitaire.c',
'pickers.c',
'printing.c',

366
demos/gtk-demo/path_fill.c Normal file
View File

@@ -0,0 +1,366 @@
/* Path/Fill
*
* This demo shows how to use PangoCairo to draw text with more than
* just a single color.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#include "gsk/gskpathdashprivate.h"
#define GTK_TYPE_PATH_PAINTABLE (gtk_path_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathPaintable, gtk_path_paintable, GTK, PATH_PAINTABLE, GObject)
struct _GtkPathPaintable
{
GObject parent_instance;
int width;
int height;
GskPath *path;
GdkPaintable *background;
};
struct _GtkPathPaintableClass
{
GObjectClass parent_class;
};
static int
gtk_path_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return MAX (gdk_paintable_get_intrinsic_width (self->background), self->width);
else
return self->width;
}
static int
gtk_path_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return MAX (gdk_paintable_get_intrinsic_height (self->background), self->height);
else
return self->height;
}
static void
gtk_path_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
#if 0
gtk_snapshot_push_fill (snapshot, self->path, GSK_FILL_RULE_WINDING);
#else
GskStroke *stroke = gsk_stroke_new (2.0);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
#endif
if (self->background)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
else
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_pop (snapshot);
}
static GdkPaintableFlags
gtk_path_paintable_get_flags (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return gdk_paintable_get_flags (self->background);
else
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_path_paintable_paintable_init (GdkPaintableInterface *iface)
{
iface->get_intrinsic_width = gtk_path_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_path_paintable_get_intrinsic_height;
iface->snapshot = gtk_path_paintable_snapshot;
iface->get_flags = gtk_path_paintable_get_flags;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkPathPaintable, gtk_path_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_path_paintable_paintable_init))
/* Here's the boilerplate for the GObject declaration.
* We need to disconnect the signals here that we set up elsewhere
*/
static void
gtk_path_paintable_dispose (GObject *object)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (object);
if (self->background)
{
g_signal_handlers_disconnect_by_func (self->background, gdk_paintable_invalidate_contents, self);
g_signal_handlers_disconnect_by_func (self->background, gdk_paintable_invalidate_size, self);
g_clear_object (&self->background);
}
G_OBJECT_CLASS (gtk_path_paintable_parent_class)->dispose (object);
}
static void
gtk_path_paintable_class_init (GtkPathPaintableClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_paintable_dispose;
}
static void
gtk_path_paintable_init (GtkPathPaintable *self)
{
}
/* And finally, we add a simple constructor.
* It is declared in the header so that the other examples
* can use it.
*/
GdkPaintable *
gtk_path_paintable_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkPathPaintable *self;
self = g_object_new (GTK_TYPE_PATH_PAINTABLE, NULL);
self->path = path;
self->background = background;
if (self->background)
{
g_object_ref (self->background);
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
}
self->width = width;
self->height = height;
return GDK_PAINTABLE (self);
}
void
gtk_path_paintable_set_path (GtkPathPaintable *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
self->path = gsk_path_ref (path);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
}
static GskPath *
create_hexagon (GtkWidget *widget)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 120, 0);
gsk_path_builder_line_to (builder, 360, 0);
gsk_path_builder_line_to (builder, 480, 208);
gsk_path_builder_line_to (builder, 360, 416);
gsk_path_builder_line_to (builder, 120, 416);
gsk_path_builder_line_to (builder, 0, 208);
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskPath *
create_path_from_text (GtkWidget *widget)
{
PangoLayout *layout;
PangoFontDescription *desc;
GskPathBuilder *builder;
layout = gtk_widget_create_pango_layout (widget, "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
return gsk_path_builder_free_to_path (builder);
}
static gboolean
build_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_CURVE:
gsk_path_builder_curve_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
static gboolean
update_path (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer measure)
{
float progress = gdk_frame_clock_get_frame_time (frame_clock) % (60 * G_USEC_PER_SEC) / (float) (30 * G_USEC_PER_SEC);
GskPathBuilder *builder;
GskPath *path;
graphene_point_t pos;
graphene_vec2_t tangent;
GskStroke *stroke;
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder,
measure,
#if 1
0.0, gsk_path_measure_get_length (measure));
#else
progress > 1 ? (progress - 1) * gsk_path_measure_get_length (measure) : 0.0,
(progress < 1 ? progress : 1.0) * gsk_path_measure_get_length (measure));
#endif
path = gsk_path_builder_free_to_path (builder);
stroke = gsk_stroke_new (1);
gsk_stroke_set_dash (stroke, (float[2]) { 10, 5 }, 2);
gsk_stroke_set_dash_offset (stroke, - (gdk_frame_clock_get_frame_time (frame_clock) % G_USEC_PER_SEC) * 15. / G_USEC_PER_SEC);
builder = gsk_path_builder_new ();
gsk_path_dash (path, stroke, 0.2, build_path, builder);
gsk_path_unref (path);
gsk_path_measure_get_point (measure,
(progress > 1 ? (progress - 1) : progress) * gsk_path_measure_get_length (measure),
&pos,
&tangent);
gsk_path_builder_move_to (builder, pos.x + 5 * graphene_vec2_get_x (&tangent), pos.y + 5 * graphene_vec2_get_y (&tangent));
gsk_path_builder_line_to (builder, pos.x + 3 * graphene_vec2_get_y (&tangent), pos.y + 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_line_to (builder, pos.x - 3 * graphene_vec2_get_y (&tangent), pos.y - 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_close (builder);
path = gsk_path_builder_free_to_path (builder);
gtk_path_paintable_set_path (GTK_PATH_PAINTABLE (gtk_picture_get_paintable (GTK_PICTURE (widget))),
path);
gsk_path_unref (path);
return G_SOURCE_CONTINUE;
}
GtkWidget *
do_path_fill (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *picture;
GdkPaintable *paintable;
GtkMediaStream *stream;
GskPath *path;
graphene_rect_t bounds;
GskPathMeasure *measure;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Path Fill");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_hexagon (window);
path = create_path_from_text (window);
gsk_path_get_bounds (path, &bounds);
paintable = gtk_path_paintable_new (path,
GDK_PAINTABLE (stream),
bounds.origin.x + bounds.size.width,
bounds.origin.y + bounds.size.height);
picture = gtk_picture_new_for_paintable (paintable);
measure = gsk_path_measure_new (path);
gtk_widget_add_tick_callback (picture, update_path, measure, (GDestroyNotify) gsk_path_measure_unref);
gtk_picture_set_content_fit (GTK_PICTURE (picture), GTK_CONTENT_FIT_CONTAIN);
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
g_object_unref (paintable);
gtk_window_set_child (GTK_WINDOW (window), picture);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

338
demos/gtk-demo/path_maze.c Normal file
View File

@@ -0,0 +1,338 @@
/* Path/Maze
*
* This demo shows how to use a GskPath to create a maze and use
* gsk_path_measure_get_closest_point() to check the mouse stays
* on the path.
*
* It also shows off the performance of GskPath (or not) as this
* is a rather complex path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#define MAZE_GRID_SIZE 20
#define MAZE_STROKE_SIZE_ACTIVE (MAZE_GRID_SIZE - 4)
#define MAZE_STROKE_SIZE_INACTIVE (MAZE_GRID_SIZE - 12)
#define MAZE_WIDTH 31
#define MAZE_HEIGHT 21
#define GTK_TYPE_MAZE (gtk_maze_get_type ())
G_DECLARE_FINAL_TYPE (GtkMaze, gtk_maze, GTK, MAZE, GtkWidget)
struct _GtkMaze
{
GtkWidget parent_instance;
int width;
int height;
GskPath *path;
GskPathMeasure *measure;
GdkPaintable *background;
gboolean active;
};
struct _GtkMazeClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (GtkMaze, gtk_maze, GTK_TYPE_WIDGET)
static void
gtk_maze_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkMaze *self = GTK_MAZE (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = self->width;
else
*minimum = *natural = self->height;
}
static void
gtk_maze_snapshot (GtkWidget *widget,
GdkSnapshot *snapshot)
{
GtkMaze *self = GTK_MAZE (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskStroke *stroke;
stroke = gsk_stroke_new (MAZE_STROKE_SIZE_INACTIVE);
if (self->active)
gsk_stroke_set_line_width (stroke, MAZE_STROKE_SIZE_ACTIVE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
if (self->background)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
else
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_pop (snapshot);
}
static void
gtk_maze_dispose (GObject *object)
{
GtkMaze *self = GTK_MAZE (object);
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
if (self->background)
{
g_signal_handlers_disconnect_matched (self->background, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
g_clear_object (&self->background);
}
G_OBJECT_CLASS (gtk_maze_parent_class)->dispose (object);
}
static void
gtk_maze_class_init (GtkMazeClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_maze_dispose;
widget_class->measure = gtk_maze_measure;
widget_class->snapshot = gtk_maze_snapshot;
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkMaze *self)
{
if (!self->active)
return;
if (gsk_path_measure_get_closest_point (self->measure, &GRAPHENE_POINT_INIT (x, y), NULL) <= MAZE_STROKE_SIZE_ACTIVE / 2.0f)
return;
self->active = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkMaze *self)
{
if (!self->active)
{
self->active = TRUE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
gtk_maze_init (GtkMaze *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->active = TRUE;
}
static void
gtk_maze_set_path (GtkMaze *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
self->path = gsk_path_ref (path);
self->measure = gsk_path_measure_new (path);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
GtkWidget *
gtk_maze_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkMaze *self;
self = g_object_new (GTK_TYPE_MAZE, NULL);
gtk_maze_set_path (self, path);
gsk_path_unref (path);
self->background = background;
if (self->background)
{
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gtk_widget_queue_draw), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gtk_widget_queue_resize), self);
}
self->width = width;
self->height = height;
return GTK_WIDGET (self);
}
static void
add_point_to_maze (GtkBitset *maze,
GskPathBuilder *builder,
guint x,
guint y)
{
gboolean set[4] = { FALSE, FALSE, FALSE, FALSE };
guint dir;
gtk_bitset_add (maze, y * MAZE_WIDTH + x);
while (TRUE)
{
set[0] = set[0] || x == 0 || gtk_bitset_contains (maze, y * MAZE_WIDTH + x - 1);
set[1] = set[1] || y == 0 || gtk_bitset_contains (maze, (y - 1) * MAZE_WIDTH + x);
set[2] = set[2] || x + 1 == MAZE_WIDTH || gtk_bitset_contains (maze, y * MAZE_WIDTH + x + 1);
set[3] = set[3] || y + 1 == MAZE_HEIGHT || gtk_bitset_contains (maze, (y + 1) * MAZE_WIDTH + x);
if (set[0] && set[1] && set[2] && set[3])
return;
do
{
dir = g_random_int_range (0, 4);
}
while (set[dir]);
switch (dir)
{
case 0:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x - 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x - 1, y);
break;
case 1:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y - 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y - 1);
break;
case 2:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 1.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x + 1, y);
break;
case 3:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 1.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y + 1);
break;
default:
g_assert_not_reached ();
break;
}
}
}
static GskPath *
create_path_for_maze (GtkWidget *widget)
{
GskPathBuilder *builder;
GtkBitset *maze;
builder = gsk_path_builder_new ();
maze = gtk_bitset_new_empty ();
/* make sure the outer lines are unreachable:
* Set the full range, then remove the center again. */
gtk_bitset_add_range (maze, 0, MAZE_WIDTH * MAZE_HEIGHT);
gtk_bitset_remove_rectangle (maze, MAZE_WIDTH + 1, MAZE_WIDTH - 2, MAZE_HEIGHT - 2, MAZE_WIDTH);
/* Fill the maze */
add_point_to_maze (maze, builder, MAZE_WIDTH / 2, MAZE_HEIGHT / 2);
/* Add start and stop lines */
gsk_path_builder_move_to (builder, 1.5 * MAZE_GRID_SIZE, -0.5 * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, 1.5 * MAZE_GRID_SIZE, 1.5 * MAZE_GRID_SIZE);
gsk_path_builder_move_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT - 1.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT + 0.5) * MAZE_GRID_SIZE);
gtk_bitset_unref (maze);
return gsk_path_builder_free_to_path (builder);
}
GtkWidget *
do_path_maze (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *maze;
GtkMediaStream *stream;
GskPath *path;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Follow the maze with the mouse");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_path_for_maze (window);
maze = gtk_maze_new (path,
GDK_PAINTABLE (stream),
MAZE_WIDTH * MAZE_GRID_SIZE,
MAZE_HEIGHT * MAZE_GRID_SIZE);
gtk_window_set_child (GTK_WINDOW (window), maze);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

588
demos/gtk-demo/path_text.c Normal file
View File

@@ -0,0 +1,588 @@
/* Path/Text
*
* This demo shows how to use GskPath to animate a path along another path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#define GTK_TYPE_PATH_WIDGET (gtk_path_widget_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathWidget, gtk_path_widget, GTK, PATH_WIDGET, GtkWidget)
#define POINT_SIZE 8
enum {
PROP_0,
PROP_TEXT,
PROP_EDITABLE,
N_PROPS
};
struct _GtkPathWidget
{
GtkWidget parent_instance;
char *text;
gboolean editable;
graphene_point_t points[4];
guint active_point;
float line_closest;
GskPath *line_path;
GskPathMeasure *line_measure;
GskPath *text_path;
GdkPaintable *background;
};
struct _GtkPathWidgetClass
{
GtkWidgetClass parent_class;
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkPathWidget, gtk_path_widget, GTK_TYPE_WIDGET)
static GskPath *
create_path_from_text (GtkWidget *widget,
const char *text,
graphene_point_t *out_offset)
{
PangoLayout *layout;
PangoFontDescription *desc;
GskPathBuilder *builder;
GskPath *result;
layout = gtk_widget_create_pango_layout (widget, text);
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
result = gsk_path_builder_free_to_path (builder);
if (out_offset)
graphene_point_init (out_offset, 0, - pango_layout_get_baseline (layout) / (double) PANGO_SCALE);
g_object_unref (layout);
return result;
}
typedef struct
{
GskPathMeasure *measure;
GskPathBuilder *builder;
graphene_point_t offset;
double scale;
} GtkPathTransform;
static void
gtk_path_transform_point (GskPathMeasure *measure,
const graphene_point_t *pt,
const graphene_point_t *offset,
float scale,
graphene_point_t *res)
{
graphene_vec2_t tangent;
gsk_path_measure_get_point (measure, (pt->x + offset->x) * scale, res, &tangent);
res->x -= (pt->y + offset->y) * scale * graphene_vec2_get_y (&tangent);
res->y += (pt->y + offset->y) * scale * graphene_vec2_get_x (&tangent);
}
static gboolean
gtk_path_transform_op (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GtkPathTransform *transform = data;
switch (op)
{
case GSK_PATH_MOVE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[0], &transform->offset, transform->scale, &res);
gsk_path_builder_move_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_LINE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res);
gsk_path_builder_line_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_CURVE:
{
graphene_point_t res[3];
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], &transform->offset, transform->scale, &res[1]);
gtk_path_transform_point (transform->measure, &pts[3], &transform->offset, transform->scale, &res[2]);
gsk_path_builder_curve_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, res[2].x, res[2].y);
}
break;
case GSK_PATH_CONIC:
{
graphene_point_t res[2];
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], &transform->offset, transform->scale, &res[1]);
gsk_path_builder_conic_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, weight);
}
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (transform->builder);
break;
default:
g_assert_not_reached();
return FALSE;
}
return TRUE;
}
static GskPath *
gtk_path_transform (GskPathMeasure *measure,
GskPath *path,
const graphene_point_t *offset)
{
GtkPathTransform transform = { measure, gsk_path_builder_new (), *offset };
graphene_rect_t bounds;
gsk_path_get_bounds (path, &bounds);
if (bounds.origin.x + bounds.size.width > 0)
transform.scale = gsk_path_measure_get_length (measure) / (bounds.origin.x + bounds.size.width);
else
transform.scale = 1.0f;
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CURVE, gtk_path_transform_op, &transform);
return gsk_path_builder_free_to_path (transform.builder);
}
static void
gtk_path_widget_clear_text_path (GtkPathWidget *self)
{
g_clear_pointer (&self->text_path, gsk_path_unref);
}
static void
gtk_path_widget_clear_paths (GtkPathWidget *self)
{
gtk_path_widget_clear_text_path (self);
g_clear_pointer (&self->line_path, gsk_path_unref);
g_clear_pointer (&self->line_measure, gsk_path_measure_unref);
}
static void
gtk_path_widget_create_text_path (GtkPathWidget *self)
{
GskPath *path;
graphene_point_t offset;
gtk_path_widget_clear_text_path (self);
if (self->line_measure == NULL)
return;
path = create_path_from_text (GTK_WIDGET (self), self->text, &offset);
self->text_path = gtk_path_transform (self->line_measure, path, &offset);
gsk_path_unref (path);
}
static void
gtk_path_widget_create_paths (GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
GskPathBuilder *builder;
gtk_path_widget_clear_paths (self);
if (width <= 0 || height <= 0)
return;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder,
self->points[0].x * width, self->points[0].y * height);
gsk_path_builder_curve_to (builder,
self->points[1].x * width, self->points[1].y * height,
self->points[2].x * width, self->points[2].y * height,
self->points[3].x * width, self->points[3].y * height);
self->line_path = gsk_path_builder_free_to_path (builder);
self->line_measure = gsk_path_measure_new (self->line_path);
gtk_path_widget_create_text_path (self);
}
static void
gtk_path_widget_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
GTK_WIDGET_CLASS (gtk_path_widget_parent_class)->size_allocate (widget, width, height, baseline);
gtk_path_widget_create_paths (self);
}
static void
gtk_path_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskPath *path;
GskStroke *stroke;
gsize i;
/* frosted glass the background */
gtk_snapshot_push_blur (snapshot, 100);
gdk_paintable_snapshot (self->background, snapshot, width, height);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 0.6 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the text */
if (self->text_path)
{
gtk_snapshot_push_fill (snapshot, self->text_path, GSK_FILL_RULE_WINDING);
gdk_paintable_snapshot (self->background, snapshot, width, height);
/* ... with an emboss effect */
stroke = gsk_stroke_new (2.0);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT(1, 1));
gtk_snapshot_push_stroke (snapshot, self->text_path, stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 0.2 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gsk_stroke_free (stroke);
gtk_snapshot_pop (snapshot);
gtk_snapshot_pop (snapshot);
}
if (self->editable && self->line_path)
{
GskPathBuilder *builder;
/* draw the control line */
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, self->line_path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the points */
builder = gsk_path_builder_new ();
for (i = 0; i < 4; i++)
{
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), POINT_SIZE);
}
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
if (self->line_closest >= 0)
{
GskPathBuilder *builder;
graphene_point_t closest;
builder = gsk_path_builder_new ();
gsk_path_measure_get_point (self->line_measure, self->line_closest, &closest, NULL);
gsk_path_builder_add_circle (builder, &closest, POINT_SIZE);
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
}
static void
gtk_path_widget_set_text (GtkPathWidget *self,
const char *text)
{
if (g_strcmp0 (self->text, text) == 0)
return;
g_free (self->text);
self->text = g_strdup (text);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT]);
}
static void
gtk_path_widget_set_editable (GtkPathWidget *self,
gboolean editable)
{
if (self->editable == editable)
return;
self->editable = editable;
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDITABLE]);
}
static void
gtk_path_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
gtk_path_widget_set_text (self, g_value_get_string (value));
break;
case PROP_EDITABLE:
gtk_path_widget_set_editable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
g_value_set_string (value, self->text);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, self->editable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_dispose (GObject *object)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
gtk_path_widget_clear_paths (self);
G_OBJECT_CLASS (gtk_path_widget_parent_class)->dispose (object);
}
static void
gtk_path_widget_class_init (GtkPathWidgetClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_widget_dispose;
object_class->set_property = gtk_path_widget_set_property;
object_class->get_property = gtk_path_widget_get_property;
widget_class->size_allocate = gtk_path_widget_allocate;
widget_class->snapshot = gtk_path_widget_snapshot;
properties[PROP_TEXT] =
g_param_spec_string ("text",
"text",
"Text transformed along a path",
NULL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_EDITABLE] =
g_param_spec_boolean ("editable",
"editable",
"If the path can be edited by the user",
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
drag_begin (GtkGestureDrag *gesture,
double x,
double y,
GtkPathWidget *self)
{
graphene_point_t mouse = GRAPHENE_POINT_INIT (x, y);
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
gsize i;
for (i = 0; i < 4; i++)
{
if (graphene_point_distance (&GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), &mouse, NULL, NULL) <= POINT_SIZE)
{
self->active_point = i;
break;
}
}
if (i == 4)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
drag_update (GtkGestureDrag *drag,
double offset_x,
double offset_y,
GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
double start_x, start_y;
gtk_gesture_drag_get_start_point (drag, &start_x, &start_y);
self->points[self->active_point] = GRAPHENE_POINT_INIT ((start_x + offset_x) / width,
(start_y + offset_y) / height);
self->points[self->active_point].x = CLAMP (self->points[self->active_point].x, 0, 1);
self->points[self->active_point].y = CLAMP (self->points[self->active_point].y, 0, 1);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkPathWidget *self)
{
gsk_path_measure_get_closest_point_full (self->line_measure,
&GRAPHENE_POINT_INIT (x, y),
INFINITY,
&self->line_closest,
NULL, NULL, NULL);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkPathWidget *self)
{
self->line_closest = -1;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_path_widget_init (GtkPathWidget *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
g_signal_connect (controller, "drag-end", G_CALLBACK (drag_update), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "enter", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->line_closest = -1;
self->points[0] = GRAPHENE_POINT_INIT (0.1, 0.9);
self->points[1] = GRAPHENE_POINT_INIT (0.3, 0.1);
self->points[2] = GRAPHENE_POINT_INIT (0.7, 0.1);
self->points[3] = GRAPHENE_POINT_INIT (0.9, 0.9);
self->background = GDK_PAINTABLE (gdk_texture_new_from_resource ("/sliding_puzzle/portland-rose.jpg"));
gtk_path_widget_set_text (self, "It's almost working");
}
GtkWidget *
gtk_path_widget_new (void)
{
GtkPathWidget *self;
self = g_object_new (GTK_TYPE_PATH_WIDGET, NULL);
return GTK_WIDGET (self);
}
GtkWidget *
do_path_text (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_PATH_WIDGET);
builder = gtk_builder_new_from_resource ("/path_text/path_text.ui");
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Text along a Path</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<child type="end">
<object class="GtkToggleButton" id="edit-toggle">
<property name="icon-name">document-edit-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkRevealer">
<property name="reveal-child" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<child>
<object class="GtkEntry" id="text">
<property name="text">Through the looking glass</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkPathWidget" id="view">
<property name="editable" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<property name="text" bind-source="text" bind-property="text" bind-flags="sync-create"></property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -7,68 +7,186 @@
#include <glib/gi18n.h>
#include <gtk/gtk.h>
static void
draw_text (GtkDrawingArea *da,
cairo_t *cr,
int width,
int height,
gpointer data)
#define TEXT_TYPE_MASK_DEMO (text_mask_demo_get_type ())
G_DECLARE_FINAL_TYPE (TextMaskDemo, text_mask_demo, TEXT, MASK_DEMO, GtkWidget)
struct _TextMaskDemo
{
cairo_pattern_t *pattern;
PangoLayout *layout;
GtkWidget parent_instance;
char *text;
PangoFontDescription *desc;
GskPath *path;
};
cairo_save (cr);
struct _TextMaskDemoClass
{
GtkWidgetClass parent_class;
};
layout = gtk_widget_create_pango_layout (GTK_WIDGET (da), "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("sans bold 34");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
G_DEFINE_TYPE (TextMaskDemo, text_mask_demo, GTK_TYPE_WIDGET)
cairo_move_to (cr, 30, 20);
pango_cairo_layout_path (cr, layout);
g_object_unref (layout);
static void
update_path (TextMaskDemo *demo)
{
PangoLayout *layout;
GskPathBuilder *builder;
pattern = cairo_pattern_create_linear (0.0, 0.0, width, height);
cairo_pattern_add_color_stop_rgb (pattern, 0.0, 1.0, 0.0, 0.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.2, 1.0, 0.0, 0.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.3, 1.0, 1.0, 0.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.4, 0.0, 1.0, 0.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.6, 0.0, 1.0, 1.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.7, 0.0, 0.0, 1.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.8, 1.0, 0.0, 1.0);
cairo_pattern_add_color_stop_rgb (pattern, 1.0, 1.0, 0.0, 1.0);
g_clear_pointer (&demo->path, gsk_path_unref);
cairo_set_source (cr, pattern);
cairo_fill_preserve (cr);
layout = gtk_widget_create_pango_layout (GTK_WIDGET (demo), demo->text);
pango_layout_set_font_description (layout, demo->desc);
cairo_pattern_destroy (pattern);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
demo->path = gsk_path_builder_free_to_path (builder);
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
cairo_set_line_width (cr, 0.5);
cairo_stroke (cr);
gtk_widget_queue_draw (GTK_WIDGET (demo));
}
cairo_restore (cr);
static void
text_mask_demo_init (TextMaskDemo *demo)
{
demo->text = g_strdup ("No text. No fun");
demo->desc = pango_font_description_from_string ("Cantarell 20");
update_path (demo);
}
static void
text_mask_demo_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
TextMaskDemo *demo = TEXT_MASK_DEMO (widget);
float width, height;
GskColorStop stops[4];
GskStroke *stroke;
gdk_rgba_parse (&stops[0].color, "red");
gdk_rgba_parse (&stops[1].color, "green");
gdk_rgba_parse (&stops[2].color, "yellow");
gdk_rgba_parse (&stops[3].color, "blue");
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
stops[0].offset = 0;
stops[1].offset = 0.25;
stops[2].offset = 0.50;
stops[3].offset = 0.75;
gtk_snapshot_push_fill (snapshot, demo->path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
stops, 4);
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, demo->path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 1 },
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
}
void
text_mask_demo_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum_size,
int *natural_size,
int *minimum_baseline,
int *natural_baseline)
{
TextMaskDemo *demo = TEXT_MASK_DEMO (widget);
graphene_rect_t rect;
gsk_path_get_bounds (demo->path, &rect);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum_size = *natural_size = rect.size.width;
else
*minimum_size = *natural_size = rect.size.height;
}
static void
text_mask_demo_finalize (GObject *object)
{
TextMaskDemo *demo = TEXT_MASK_DEMO (object);
g_free (demo->text);
pango_font_description_free (demo->desc);
gsk_path_unref (demo->path);
G_OBJECT_CLASS (text_mask_demo_parent_class)->finalize (object);
}
static void
text_mask_demo_class_init (TextMaskDemoClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->finalize = text_mask_demo_finalize;
widget_class->snapshot = text_mask_demo_snapshot;
widget_class->measure = text_mask_demo_measure;
}
static GtkWidget *
text_mask_demo_new (void)
{
return g_object_new (text_mask_demo_get_type (), NULL);
}
static void
text_mask_demo_set_text (TextMaskDemo *demo,
const char *text)
{
g_free (demo->text);
demo->text = g_strdup (text);
update_path (demo);
}
static void
text_mask_demo_set_font (TextMaskDemo *demo,
PangoFontDescription *desc)
{
pango_font_description_free (demo->desc);
demo->desc = pango_font_description_copy (desc);
update_path (demo);
}
GtkWidget *
do_textmask (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
static GtkWidget *da;
static GtkWidget *demo;
if (!window)
{
PangoFontDescription *desc;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_widget_set_size_request (window, 400, 240);
gtk_window_set_title (GTK_WINDOW (window), "Text Mask");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
da = gtk_drawing_area_new ();
demo = text_mask_demo_new ();
text_mask_demo_set_text (TEXT_MASK_DEMO (demo), "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("Sans Bold 34");
text_mask_demo_set_font (TEXT_MASK_DEMO (demo), desc);
pango_font_description_free (desc);
gtk_window_set_child (GTK_WINDOW (window), da);
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_text, NULL, NULL);
gtk_window_set_child (GTK_WINDOW (window), demo);
}
if (!gtk_widget_get_visible (window))

View File

@@ -3,6 +3,7 @@ node_editor_sources = [
'main.c',
'node-editor-application.c',
'node-editor-window.c',
'rendernodeutils.c'
]
node_editor_resources = gnome.compile_resources('node_editor_resources',

View File

@@ -22,6 +22,7 @@
#include "node-editor-window.h"
#include "gtkrendererpaintableprivate.h"
#include "rendernodeutilsprivate.h"
#include "gsk/gskrendernodeparserprivate.h"
#include "gsk/gl/gskglrenderer.h"
@@ -61,8 +62,10 @@ struct _NodeEditorWindow
GtkWidget *scale_scale;
GtkWidget *renderer_listbox;
GListStore *saved_nodes;
GListStore *renderers;
GskRenderNode *node;
GtkAdjustment *compare_progress;
GFile *file;
GFileMonitor *file_monitor;
@@ -130,6 +133,7 @@ deserialize_error_func (const GskParseLocation *start_location,
g_array_append_val (self->errors, text_view_error);
}
#if 0
static void
text_iter_skip_alpha_backward (GtkTextIter *iter)
{
@@ -165,33 +169,22 @@ text_iter_skip_whitespace_backward (GtkTextIter *iter)
gtk_text_iter_backward_char (iter);
}
}
#endif
static void
text_changed (GtkTextBuffer *buffer,
NodeEditorWindow *self)
node_changed (NodeEditorWindow *self)
{
char *text;
GBytes *bytes;
GtkTextIter iter;
GtkTextIter start, end;
float scale;
GskRenderNode *big_node;
g_array_remove_range (self->errors, 0, self->errors->len);
text = get_current_text (self->text_buffer);
text_buffer_remove_all_tags (self->text_buffer);
bytes = g_bytes_new_take (text, strlen (text));
g_clear_pointer (&self->node, gsk_render_node_unref);
/* If this is too slow, go fix the parser performance */
self->node = gsk_render_node_deserialize (bytes, deserialize_error_func, self);
scale = gtk_scale_button_get_value (GTK_SCALE_BUTTON (self->scale_scale));
if (self->node && scale != 0.)
{
GskTransform *transform;
scale = pow (2., scale);
big_node = gsk_transform_node_new (self->node, gsk_transform_scale (NULL, scale, scale));
transform = gsk_transform_scale (NULL, scale, scale);
big_node = gsk_transform_node_new (self->node, transform);
gsk_transform_unref (transform);
}
else if (self->node)
{
@@ -202,14 +195,13 @@ text_changed (GtkTextBuffer *buffer,
big_node = NULL;
}
g_bytes_unref (bytes);
if (self->node)
{
/* XXX: Is this code necessary or can we have API to turn nodes into paintables? */
GtkSnapshot *snapshot;
GdkPaintable *paintable;
graphene_rect_t bounds;
guint i;
guint i, n;
snapshot = gtk_snapshot_new ();
gsk_render_node_get_bounds (big_node, &bounds);
@@ -225,6 +217,8 @@ text_changed (GtkTextBuffer *buffer,
gtk_snapshot_append_node (snapshot, self->node);
paintable = gtk_snapshot_free_to_paintable (snapshot, &bounds.size);
n = g_list_model_get_n_items (G_LIST_MODEL (self->saved_nodes));
g_list_store_splice (self->saved_nodes, MAX (n, 1) - 1, MIN (n, 1), (gpointer[1]) { paintable }, 1);
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->renderers)); i++)
{
gpointer item = g_list_model_get_item (G_LIST_MODEL (self->renderers), i);
@@ -240,7 +234,30 @@ text_changed (GtkTextBuffer *buffer,
}
g_clear_pointer (&big_node, gsk_render_node_unref);
}
static void
text_changed (GtkTextBuffer *buffer,
NodeEditorWindow *self)
{
char *text;
GBytes *bytes;
g_array_remove_range (self->errors, 0, self->errors->len);
text = get_current_text (self->text_buffer);
text_buffer_remove_all_tags (self->text_buffer);
bytes = g_bytes_new_take (text, strlen (text));
g_clear_pointer (&self->node, gsk_render_node_unref);
/* If this is too slow, go fix the parser performance */
self->node = gsk_render_node_deserialize (bytes, deserialize_error_func, self);
node_changed (self);
g_bytes_unref (bytes);
#if 0
gtk_text_buffer_get_start_iter (self->text_buffer, &iter);
while (!gtk_text_iter_is_end (&iter))
@@ -307,6 +324,18 @@ text_changed (GtkTextBuffer *buffer,
gtk_text_buffer_get_bounds (self->text_buffer, &start, &end);
gtk_text_buffer_apply_tag_by_name (self->text_buffer, "no-hyphens",
&start, &end);
#endif
}
static void
stash_current_node (NodeEditorWindow *self)
{
GdkPaintable *last;
last = g_list_model_get_item (G_LIST_MODEL (self->saved_nodes),
g_list_model_get_n_items (G_LIST_MODEL (self->saved_nodes)) - 1);
g_list_store_append (self->saved_nodes, last);
g_object_unref (last);
}
static void
@@ -314,7 +343,7 @@ scale_changed (GObject *object,
GParamSpec *pspec,
NodeEditorWindow *self)
{
text_changed (self->text_buffer, self);
node_changed (self);
}
static gboolean
@@ -378,7 +407,8 @@ text_view_query_tooltip_cb (GtkWidget *widget,
static gboolean
load_bytes (NodeEditorWindow *self,
GBytes *bytes);
GBytes *bytes,
gboolean stash);
static void
load_error (NodeEditorWindow *self,
@@ -396,7 +426,7 @@ load_error (NodeEditorWindow *self,
node = gtk_snapshot_free_to_node (snapshot);
bytes = gsk_render_node_serialize (node);
load_bytes (self, bytes);
load_bytes (self, bytes, TRUE);
gsk_render_node_unref (node);
g_object_unref (layout);
@@ -404,7 +434,8 @@ load_error (NodeEditorWindow *self,
static gboolean
load_bytes (NodeEditorWindow *self,
GBytes *bytes)
GBytes *bytes,
gboolean stash)
{
if (!g_utf8_validate (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), NULL))
{
@@ -412,6 +443,9 @@ load_bytes (NodeEditorWindow *self,
g_bytes_unref (bytes);
return FALSE;
}
if (stash)
stash_current_node (self);
gtk_text_buffer_set_text (self->text_buffer,
g_bytes_get_data (bytes, NULL),
@@ -437,7 +471,29 @@ load_file_contents (NodeEditorWindow *self,
return FALSE;
}
return load_bytes (self, bytes);
return load_bytes (self, bytes, TRUE);
}
static void
saved_node_activate_cb (GtkListView *listview,
guint pos,
NodeEditorWindow *self)
{
GdkPaintable *paintable;
GtkSnapshot *snapshot;
GskRenderNode *node;
GBytes *bytes;
paintable = g_list_model_get_item (G_LIST_MODEL (self->saved_nodes), pos);
snapshot = gtk_snapshot_new ();
gdk_paintable_snapshot (paintable, snapshot, gdk_paintable_get_intrinsic_width (paintable), gdk_paintable_get_intrinsic_height (paintable));
node = gtk_snapshot_free_to_node (snapshot);
bytes = gsk_render_node_serialize (node);
load_bytes (self, bytes, TRUE);
g_object_unref (paintable);
}
static GdkContentProvider *
@@ -466,7 +522,7 @@ on_picture_drop_read_done_cb (GObject *source,
if (g_output_stream_splice_finish (stream, res, NULL) >= 0)
{
bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream));
if (load_bytes (self, bytes))
if (load_bytes (self, bytes, TRUE))
action = GDK_ACTION_COPY;
}
@@ -650,6 +706,10 @@ save_response_cb (GObject *source,
g_object_unref (alert);
g_error_free (error);
}
else
{
stash_current_node (self);
}
g_free (text);
g_object_unref (file);
@@ -1075,6 +1135,8 @@ testcase_save_clicked_cb (GtkWidget *button,
gtk_editable_set_text (GTK_EDITABLE (self->testcase_name_entry), "");
gtk_popover_popdown (GTK_POPOVER (self->testcase_popover));
stash_current_node (self);
out:
g_free (text);
g_free (png_file);
@@ -1083,8 +1145,54 @@ out:
}
static void
dark_mode_cb (GtkToggleButton *button,
GParamSpec *pspec,
update_compare (NodeEditorWindow *self)
{
GdkPaintable *from, *to;
GtkSnapshot *snapshot;
GskRenderNode *node, *node_from, *node_to;
GBytes *bytes;
guint n;
n = g_list_model_get_n_items (G_LIST_MODEL (self->saved_nodes));
from = g_list_model_get_item (G_LIST_MODEL (self->saved_nodes), n - 3);
snapshot = gtk_snapshot_new ();
gdk_paintable_snapshot (from, snapshot, gdk_paintable_get_intrinsic_width (from), gdk_paintable_get_intrinsic_height (from));
node_from = gtk_snapshot_free_to_node (snapshot);
g_object_unref (from);
to = g_list_model_get_item (G_LIST_MODEL (self->saved_nodes), n - 2);
snapshot = gtk_snapshot_new ();
gdk_paintable_snapshot (to, snapshot, gdk_paintable_get_intrinsic_width (to), gdk_paintable_get_intrinsic_height (to));
node_to = gtk_snapshot_free_to_node (snapshot);
g_object_unref (to);
node = render_node_interpolate (node_from, node_to, gtk_adjustment_get_value (self->compare_progress));
bytes = gsk_render_node_serialize (node);
load_bytes (self, bytes, FALSE);
gsk_render_node_unref (node);
gsk_render_node_unref (node_to);
gsk_render_node_unref (node_from);
}
static void
compare_toggled_cb (GtkToggleButton *button,
GParamSpec *pspec,
NodeEditorWindow *self)
{
if (!gtk_toggle_button_get_active (button))
return;
stash_current_node (self);
update_compare (self);
}
static void
dark_mode_cb (GtkToggleButton *button,
GParamSpec *pspec,
NodeEditorWindow *self)
{
g_object_set (gtk_widget_get_settings (GTK_WIDGET (self)),
@@ -1115,6 +1223,7 @@ node_editor_window_finalize (GObject *object)
G_OBJECT_CLASS (node_editor_window_parent_class)->finalize (object);
}
#if 0
static void
node_editor_window_add_renderer (NodeEditorWindow *self,
GskRenderer *renderer,
@@ -1149,11 +1258,9 @@ node_editor_window_realize (GtkWidget *widget)
GTK_WIDGET_CLASS (node_editor_window_parent_class)->realize (widget);
#if 0
node_editor_window_add_renderer (self,
NULL,
"Default");
#endif
node_editor_window_add_renderer (self,
gsk_gl_renderer_new (),
"OpenGL");
@@ -1171,6 +1278,7 @@ node_editor_window_realize (GtkWidget *widget)
gsk_cairo_renderer_new (),
"Cairo");
}
#endif
static void
node_editor_window_unrealize (GtkWidget *widget)
@@ -1553,11 +1661,12 @@ node_editor_window_class_init (NodeEditorWindowClass *class)
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gtk/gtk4/node-editor/node-editor-window.ui");
widget_class->realize = node_editor_window_realize;
//widget_class->realize = node_editor_window_realize;
widget_class->unrealize = node_editor_window_unrealize;
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, text_view);
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, picture);
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, compare_progress);
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, renderer_listbox);
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_popover);
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_error_label);
@@ -1565,18 +1674,22 @@ node_editor_window_class_init (NodeEditorWindowClass *class)
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_name_entry);
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_save_button);
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, scale_scale);
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, saved_nodes);
gtk_widget_class_bind_template_callback (widget_class, text_view_query_tooltip_cb);
gtk_widget_class_bind_template_callback (widget_class, saved_node_activate_cb);
gtk_widget_class_bind_template_callback (widget_class, open_cb);
gtk_widget_class_bind_template_callback (widget_class, save_cb);
gtk_widget_class_bind_template_callback (widget_class, export_image_cb);
gtk_widget_class_bind_template_callback (widget_class, clip_image_cb);
gtk_widget_class_bind_template_callback (widget_class, testcase_save_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, testcase_name_entry_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, compare_toggled_cb);
gtk_widget_class_bind_template_callback (widget_class, dark_mode_cb);
gtk_widget_class_bind_template_callback (widget_class, on_picture_drag_prepare_cb);
gtk_widget_class_bind_template_callback (widget_class, on_picture_drop_cb);
gtk_widget_class_bind_template_callback (widget_class, click_gesture_pressed);
gtk_widget_class_bind_template_callback (widget_class, update_compare);
gtk_widget_class_install_action (widget_class, "smart-edit", NULL, edit_action_cb);

View File

@@ -147,6 +147,11 @@
<property name="popover">testcase_popover</property>
</object>
</child>
<child type="start">
<object class="GtkSpinner">
<property name="spinning">1</property>
</object>
</child>
<child type="end">
<object class="GtkMenuButton" id="gear_menu_button">
<property name="focus-on-click">0</property>
@@ -165,6 +170,16 @@
<signal name="notify::active" handler="dark_mode_cb" swapped="0"/>
</object>
</child>
<child type="end">
<object class="GtkToggleButton" id="compare_button">
<property name="focus-on-click">0</property>
<property name="valign">center</property>
<property name="has-frame">0</property>
<property name="icon-name">emblem-synchronizing-symbolic</property>
<property name="tooltip-text" translatable="yes">Compare the last 2 items</property>
<signal name="notify::active" handler="compare_toggled_cb" swapped="0"/>
</object>
</child>
<child type="end">
<object class="GtkScaleButton" id="scale_scale">
<property name="focus-on-click">0</property>
@@ -185,85 +200,157 @@
</object>
</child>
<child>
<object class="GtkPaned">
<property name="shrink-start-child">false</property>
<property name="shrink-end-child">false</property>
<property name="position">400</property>
<property name="start-child">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="wrap-mode">word</property>
<property name="monospace">1</property>
<property name="top-margin">6</property>
<property name="left-margin">6</property>
<property name="right-margin">6</property>
<property name="bottom-margin">6</property>
<property name="has-tooltip">1</property>
<property name="extra-menu">extra_menu</property>
<signal name="query-tooltip" handler="text_view_query_tooltip_cb"/>
<style>
<class name="editor" />
</style>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkListView">
<property name="orientation">horizontal</property>
<property name="single-click-activate">1</property>
<property name="model">
<object class="GtkNoSelection">
<property name="model">
<object class="GListStore" id="saved_nodes" />
</property>
</object>
</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkImage">
<property name="icon-size">large</property>
<binding name="paintable">
<lookup name="item">GtkListItem</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
<signal name="activate" handler="saved_node_activate_cb"/>
</object>
</child>
<child>
<object class="GtkPaned">
<property name="shrink-start-child">false</property>
<property name="shrink-end-child">false</property>
<property name="position">400</property>
<property name="start-child">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<child>
<object class="GtkGestureClick">
<property name="button">1</property>
<signal name="pressed" handler="click_gesture_pressed"/>
<object class="GtkTextView" id="text_view">
<property name="wrap-mode">word</property>
<property name="monospace">1</property>
<property name="top-margin">6</property>
<property name="left-margin">6</property>
<property name="right-margin">6</property>
<property name="bottom-margin">6</property>
<property name="has-tooltip">1</property>
<property name="extra-menu">extra_menu</property>
<signal name="query-tooltip" handler="text_view_query_tooltip_cb"/>
<style>
<class name="editor" />
</style>
<child>
<object class="GtkGestureClick">
<property name="button">1</property>
<signal name="pressed" handler="click_gesture_pressed"/>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
<property name="end-child">
<object class="GtkBox">
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="min-content-height">100</property>
<property name="min-content-width">100</property>
</property>
<property name="end-child">
<object class="GtkBox">
<child>
<object class="GtkViewport">
<object class="GtkOverlay">
<child>
<object class="GtkPicture" id="picture">
<property name="can-shrink">0</property>
<property name="halign">center</property>
<property name="valign">center</property>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="min-content-height">100</property>
<property name="min-content-width">100</property>
<child>
<object class="GtkDragSource">
<property name="actions">copy</property>
<signal name="prepare" handler="on_picture_drag_prepare_cb" swapped="no"/>
<object class="GtkViewport">
<child>
<object class="GtkPicture" id="picture">
<property name="can-shrink">0</property>
<property name="halign">center</property>
<property name="valign">center</property>
<child>
<object class="GtkDragSource">
<property name="actions">copy</property>
<signal name="prepare" handler="on_picture_drag_prepare_cb" swapped="no"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkDropTargetAsync">
<property name="actions">copy</property>
<property name="formats">application/x-gtk-render-node</property>
<signal name="drop" handler="on_picture_drop_cb" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkBox">
<property name="margin-top">16</property>
<property name="margin-start">16</property>
<property name="margin-end">16</property>
<property name="margin-bottom">16</property>
<property name="halign">fill</property>
<property name="valign">end</property>
<property name="visible" bind-source="compare_button" bind-property="active">0</property>
<style>
<class name="osd" />
</style>
<child>
<object class="GtkScale">
<property name="width-request">200</property>
<property name="draw-value">0</property>
<property name="adjustment">
<object class="GtkAdjustment" id="compare_progress">
<property name="upper">1</property>
<property name="value">0.5</property>
<property name="step-increment">0.01</property>
<property name="page-increment">0.1</property>
<signal name="notify::value" handler="update_compare" swapped="yes"/>
</object>
</property>
<property name="hexpand">1</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkDropTargetAsync">
<property name="actions">copy</property>
<property name="formats">application/x-gtk-render-node</property>
<signal name="drop" handler="on_picture_drop_cb" swapped="no"/>
<object class="GtkListBox" id="renderer_listbox">
<property name="selection-mode">none</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkListBox" id="renderer_listbox">
<property name="selection-mode">none</property>
</object>
</child>
</object>
</child>
</property>
</object>
</property>
</child>
</object>
</child>
</template>

View File

@@ -0,0 +1,435 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "rendernodeutilsprivate.h"
static inline double
double_interpolate (double start,
double end,
double progress)
{
return start + (end - start) * progress;
}
static inline float
float_interpolate (float start,
float end,
double progress)
{
return start + (end - start) * progress;
}
static void
rgba_interpolate (GdkRGBA *result,
const GdkRGBA *start,
const GdkRGBA *end,
double progress)
{
result->alpha = CLAMP (double_interpolate (start->alpha, end->alpha, progress), 0, 1);
if (result->alpha <= 0.0)
{
result->red = result->green = result->blue = 0.0;
}
else
{
result->red = CLAMP (double_interpolate (start->red * start->alpha, end->red * end->alpha, progress), 0, 1) / result->alpha;
result->green = CLAMP (double_interpolate (start->green * start->alpha, end->green * end->alpha, progress), 0, 1) / result->alpha;
result->blue = CLAMP (double_interpolate (start->blue * start->alpha, end->blue * end->alpha, progress), 0, 1) / result->alpha;
}
}
static void
rounded_rect_interpolate (GskRoundedRect *dest,
const GskRoundedRect *start,
const GskRoundedRect *end,
double progress)
{
guint i;
graphene_rect_interpolate (&start->bounds, &end->bounds, progress, &dest->bounds);
for (i = 0; i < 4; i++)
graphene_size_interpolate (&start->corner[i], &end->corner[i], progress, &dest->corner[i]);
}
static GskTransform *
transform_interpolate (GskTransform *start,
GskTransform *end,
double progress)
{
switch (MIN (gsk_transform_get_category (start), gsk_transform_get_category (end)))
{
case GSK_TRANSFORM_CATEGORY_IDENTITY:
return NULL;
case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
{
float startx, starty, endx, endy;
gsk_transform_to_translate (start, &startx, &starty);
gsk_transform_to_translate (end, &endx, &endy);
return gsk_transform_translate (NULL,
&GRAPHENE_POINT_INIT (float_interpolate (startx, endx, progress),
float_interpolate (starty, endy, progress)));
}
case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
case GSK_TRANSFORM_CATEGORY_2D:
{
float start_skew_x, start_skew_y, start_scale_x, start_scale_y, start_angle, start_dx, start_dy;
float end_skew_x, end_skew_y, end_scale_x, end_scale_y, end_angle, end_dx, end_dy;
GskTransform *transform;
gsk_transform_to_2d_components (start,
&start_skew_x, &start_skew_y,
&start_scale_x, &start_scale_y,
&start_angle,
&start_dx, &start_dy);
gsk_transform_to_2d_components (end,
&end_skew_x, &end_skew_y,
&end_scale_x, &end_scale_y,
&end_angle,
&end_dx, &end_dy);
transform = gsk_transform_translate (NULL,
&GRAPHENE_POINT_INIT (float_interpolate (start_dx, end_dx, progress),
float_interpolate (start_dy, end_dy, progress)));
transform = gsk_transform_rotate (transform,
float_interpolate (start_angle, end_angle, progress));
transform = gsk_transform_scale (transform,
float_interpolate (start_scale_x, end_scale_x, progress),
float_interpolate (start_scale_y, end_scale_y, progress));
transform = gsk_transform_skew (transform,
float_interpolate (start_skew_x, end_skew_x, progress),
float_interpolate (start_skew_y, end_skew_y, progress));
return transform;
}
case GSK_TRANSFORM_CATEGORY_UNKNOWN:
case GSK_TRANSFORM_CATEGORY_ANY:
case GSK_TRANSFORM_CATEGORY_3D:
{
graphene_matrix_t start_matrix, end_matrix, matrix;
gsk_transform_to_matrix (start, &start_matrix);
gsk_transform_to_matrix (end, &end_matrix);
graphene_matrix_interpolate (&start_matrix, &end_matrix, progress, &matrix);
return gsk_transform_matrix (NULL, &matrix);
}
default:
g_return_val_if_reached (NULL);
}
}
static void
color_stops_interpolate (GskColorStop *stops,
const GskColorStop *start_stops,
const GskColorStop *end_stops,
gsize n_stops,
double progress)
{
guint i;
for (i = 0; i < n_stops; i++)
{
stops[i].offset = float_interpolate (start_stops[i].offset, end_stops[i].offset, progress);
rgba_interpolate (&stops[i].color, &start_stops[i].color, &end_stops[i].color, progress);
}
}
GskRenderNode *
render_node_interpolate (GskRenderNode *start,
GskRenderNode *end,
double progress)
{
GskRenderNodeType start_type, end_type;
if (progress <= 0)
return gsk_render_node_ref (start);
else if (progress >= 1)
return gsk_render_node_ref (end);
start_type = gsk_render_node_get_node_type (start);
end_type = gsk_render_node_get_node_type (end);
if (start_type == end_type)
{
switch (start_type)
{
case GSK_COLOR_NODE:
{
GdkRGBA rgba;
graphene_rect_t start_bounds, end_bounds, bounds;
rgba_interpolate (&rgba, gsk_color_node_get_color (start), gsk_color_node_get_color (end), progress);
gsk_render_node_get_bounds (start, &start_bounds);
gsk_render_node_get_bounds (end, &end_bounds);
graphene_rect_interpolate (&start_bounds, &end_bounds, progress, &bounds);
return gsk_color_node_new (&rgba, &bounds);
}
case GSK_DEBUG_NODE:
{
GskRenderNode *result, *child;
/* xxx: do we need to interpolate the message somehow? */
child = render_node_interpolate (gsk_debug_node_get_child (start),
gsk_debug_node_get_child (end),
progress);
result = gsk_debug_node_new (child, g_strdup_printf ("progress %g", progress));
gsk_render_node_unref (child);
return result;
}
case GSK_CONTAINER_NODE:
{
GskRenderNode **nodes;
GskRenderNode *result;
gsize i, n_nodes;
if (gsk_container_node_get_n_children (start) != gsk_container_node_get_n_children (end))
break;
n_nodes = gsk_container_node_get_n_children (start);
nodes = g_new (GskRenderNode *, n_nodes);
for (i = 0; i < n_nodes; i++)
{
nodes[i] = render_node_interpolate (gsk_container_node_get_child (start, i),
gsk_container_node_get_child (end, i),
progress);
}
result = gsk_container_node_new (nodes, n_nodes);
for (i = 0; i < n_nodes; i++)
gsk_render_node_unref (nodes[i]);
g_free (nodes);
return result;
}
case GSK_TEXTURE_NODE:
{
graphene_rect_t start_bounds, end_bounds, bounds;
gsk_render_node_get_bounds (start, &start_bounds);
gsk_render_node_get_bounds (end, &end_bounds);
graphene_rect_interpolate (&start_bounds, &end_bounds, progress, &bounds);
return gsk_texture_node_new (gsk_texture_node_get_texture (progress > 0.5 ? end : start),
&bounds);
}
case GSK_TEXTURE_SCALE_NODE:
{
graphene_rect_t start_bounds, end_bounds, bounds;
gsk_render_node_get_bounds (start, &start_bounds);
gsk_render_node_get_bounds (end, &end_bounds);
graphene_rect_interpolate (&start_bounds, &end_bounds, progress, &bounds);
return gsk_texture_scale_node_new (gsk_texture_scale_node_get_texture (progress > 0.5 ? end : start),
&bounds,
gsk_texture_scale_node_get_filter (progress > 0.5 ? end : start));
}
case GSK_TRANSFORM_NODE:
{
GskRenderNode *result, *child;
GskTransform *transform;
child = render_node_interpolate (gsk_transform_node_get_child (start),
gsk_transform_node_get_child (end),
progress);
transform = transform_interpolate (gsk_transform_node_get_transform (start),
gsk_transform_node_get_transform (end),
progress);
result = gsk_transform_node_new (child, transform);
gsk_transform_unref (transform);
gsk_render_node_unref (child);
return result;
}
case GSK_CLIP_NODE:
{
GskRenderNode *result, *child;
graphene_rect_t clip;
graphene_rect_interpolate (gsk_clip_node_get_clip (start),
gsk_clip_node_get_clip (end),
progress,
&clip);
child = render_node_interpolate (gsk_clip_node_get_child (start),
gsk_clip_node_get_child (end),
progress);
result = gsk_clip_node_new (child, &clip);
gsk_render_node_unref (child);
return result;
}
case GSK_ROUNDED_CLIP_NODE:
{
GskRenderNode *result, *child;
GskRoundedRect clip;
rounded_rect_interpolate (&clip,
gsk_rounded_clip_node_get_clip (start),
gsk_rounded_clip_node_get_clip (end),
progress);
child = render_node_interpolate (gsk_rounded_clip_node_get_child (start),
gsk_rounded_clip_node_get_child (end),
progress);
result = gsk_rounded_clip_node_new (child, &clip);
gsk_render_node_unref (child);
return result;
}
case GSK_BORDER_NODE:
{
GdkRGBA color[4];
const GdkRGBA *start_color, *end_color;
float width[4];
const float *start_width, *end_width;
GskRoundedRect outline;
guint i;
rounded_rect_interpolate (&outline,
gsk_border_node_get_outline (start),
gsk_border_node_get_outline (end),
progress);
start_color = gsk_border_node_get_colors (start);
end_color = gsk_border_node_get_colors (end);
start_width = gsk_border_node_get_widths (start);
end_width = gsk_border_node_get_widths (end);
for (i = 0; i < 4; i++)
{
rgba_interpolate (&color[i], &start_color[i], &end_color[i], progress);
width[i] = float_interpolate (start_width[i], end_width[i], progress);
}
return gsk_border_node_new (&outline, width, color);
}
case GSK_MASK_NODE:
{
GskRenderNode *source, *mask, *result;
source = render_node_interpolate (gsk_mask_node_get_source (start),
gsk_mask_node_get_source (end),
progress);
mask = render_node_interpolate (gsk_mask_node_get_mask (start),
gsk_mask_node_get_mask (end),
progress);
result = gsk_mask_node_new (source,
mask,
gsk_mask_node_get_mask_mode (progress > 0.5 ? end : start));
gsk_render_node_unref (source);
gsk_render_node_unref (mask);
return result;
}
case GSK_CONIC_GRADIENT_NODE:
{
graphene_rect_t start_bounds, end_bounds, bounds;
graphene_point_t center;
float rotation;
GskColorStop *color_stops;
gsize n_color_stops;
GskRenderNode *result;
if (gsk_conic_gradient_node_get_n_color_stops (start) != gsk_conic_gradient_node_get_n_color_stops (end))
break;
n_color_stops = gsk_conic_gradient_node_get_n_color_stops (start);
color_stops = g_new (GskColorStop, n_color_stops);
gsk_render_node_get_bounds (start, &start_bounds);
gsk_render_node_get_bounds (end, &end_bounds);
graphene_rect_interpolate (&start_bounds, &end_bounds, progress, &bounds);
graphene_point_interpolate (gsk_conic_gradient_node_get_center (start),
gsk_conic_gradient_node_get_center (end),
progress,
&center);
rotation = float_interpolate (gsk_conic_gradient_node_get_rotation (start),
gsk_conic_gradient_node_get_rotation (end),
progress);
color_stops_interpolate (color_stops,
gsk_conic_gradient_node_get_color_stops (start, NULL),
gsk_conic_gradient_node_get_color_stops (end, NULL),
n_color_stops,
progress);
result = gsk_conic_gradient_node_new (&bounds,
&center,
rotation,
color_stops,
n_color_stops);
g_free (color_stops);
return result;
}
case GSK_REPEAT_NODE:
{
graphene_rect_t start_bounds, end_bounds, bounds, child_bounds;
GskRenderNode *child, *result;
gsk_render_node_get_bounds (start, &start_bounds);
gsk_render_node_get_bounds (end, &end_bounds);
graphene_rect_interpolate (&start_bounds, &end_bounds, progress, &bounds);
graphene_rect_interpolate (gsk_repeat_node_get_child_bounds (start),
gsk_repeat_node_get_child_bounds (end),
progress,
&child_bounds);
child = render_node_interpolate (gsk_repeat_node_get_child (start),
gsk_repeat_node_get_child (end),
progress);
result = gsk_repeat_node_new (&bounds, child, &child_bounds);
gsk_render_node_unref (child);
return result;
}
case GSK_LINEAR_GRADIENT_NODE:
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
case GSK_RADIAL_GRADIENT_NODE:
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
case GSK_INSET_SHADOW_NODE:
case GSK_OUTSET_SHADOW_NODE:
case GSK_OPACITY_NODE:
case GSK_COLOR_MATRIX_NODE:
case GSK_BLUR_NODE:
case GSK_SHADOW_NODE:
case GSK_BLEND_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
g_warning ("FIXME: not implemented");
break;
case GSK_GL_SHADER_NODE:
case GSK_CAIRO_NODE:
case GSK_TEXT_NODE:
break;
case GSK_NOT_A_RENDER_NODE:
default:
g_assert_not_reached ();
break;
}
}
return gsk_cross_fade_node_new (start, end, progress);
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright © 2019 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include <gsk/gsk.h>
G_BEGIN_DECLS
GskRenderNode * render_node_interpolate (GskRenderNode *start,
GskRenderNode *end,
double progress);
G_END_DECLS

View File

@@ -1390,6 +1390,7 @@ gdk_display_create_vulkan_device (GdkDisplay *display,
device_extensions = g_ptr_array_new ();
g_ptr_array_add (device_extensions, (gpointer) VK_KHR_SWAPCHAIN_EXTENSION_NAME);
g_ptr_array_add (device_extensions, (gpointer) VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME);
g_ptr_array_add (device_extensions, (gpointer) VK_KHR_MAINTENANCE_3_EXTENSION_NAME);
g_ptr_array_add (device_extensions, (gpointer) VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
if (has_incremental_present)

View File

@@ -271,6 +271,8 @@ collect_reused_child_nodes (GskRenderer *renderer,
case GSK_CROSS_FADE_NODE:
case GSK_BLUR_NODE:
case GSK_MASK_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
default:
@@ -859,6 +861,8 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer,
case GSK_CROSS_FADE_NODE:
case GSK_BLUR_NODE:
case GSK_GL_SHADER_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
default:
break; /* Fallback */
}

View File

@@ -255,6 +255,8 @@ node_supports_2d_transform (const GskRenderNode *node)
case GSK_BLEND_NODE:
case GSK_BLUR_NODE:
case GSK_MASK_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
return TRUE;
case GSK_SHADOW_NODE:
@@ -309,6 +311,8 @@ node_supports_transform (const GskRenderNode *node)
case GSK_BLEND_NODE:
case GSK_BLUR_NODE:
case GSK_MASK_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
return TRUE;
case GSK_SHADOW_NODE:
@@ -4089,6 +4093,14 @@ gsk_gl_render_job_visit_node (GskGLRenderJob *job,
gsk_gl_render_job_visit_as_fallback (job, node);
break;
case GSK_FILL_NODE:
gsk_gl_render_job_visit_as_fallback (job, node);
break;
case GSK_STROKE_NODE:
gsk_gl_render_job_visit_as_fallback (job, node);
break;
case GSK_NOT_A_RENDER_NODE:
default:
g_assert_not_reached ();

View File

@@ -20,11 +20,15 @@
#define __GSK_H_INSIDE__
#include <gsk/gskenums.h>
#include <gsk/gskglshader.h>
#include <gsk/gskpath.h>
#include <gsk/gskpathbuilder.h>
#include <gsk/gskpathmeasure.h>
#include <gsk/gskrenderer.h>
#include <gsk/gskrendernode.h>
#include <gsk/gskroundedrect.h>
#include <gsk/gskstroke.h>
#include <gsk/gsktransform.h>
#include <gsk/gskglshader.h>
#include <gsk/gskcairorenderer.h>

1895
gsk/gskcontour.c Normal file

File diff suppressed because it is too large Load Diff

107
gsk/gskcontourprivate.h Normal file
View File

@@ -0,0 +1,107 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_CONTOUR_PRIVATE_H__
#define __GSK_CONTOUR_PRIVATE_H__
#include <gskpath.h>
#include "gskpathopprivate.h"
G_BEGIN_DECLS
typedef enum
{
GSK_PATH_FLAT,
GSK_PATH_CLOSED
} GskPathFlags;
typedef struct _GskContour GskContour;
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
float radius,
float start_angle,
float end_angle);
GskContour * gsk_standard_contour_new (GskPathFlags flags,
const graphene_point_t *points,
gsize n_points,
const gskpathop *ops,
gsize n_ops,
gssize offset);
void gsk_contour_copy (GskContour * dest,
const GskContour *src);
GskContour * gsk_contour_dup (const GskContour *src);
gsize gsk_contour_get_size (const GskContour *self);
GskPathFlags gsk_contour_get_flags (const GskContour *self);
void gsk_contour_print (const GskContour *self,
GString *string);
gboolean gsk_contour_get_bounds (const GskContour *self,
graphene_rect_t *bounds);
gpointer gsk_contour_init_measure (const GskContour *self,
float tolerance,
float *out_length);
void gsk_contour_free_measure (const GskContour *self,
gpointer data);
gboolean gsk_contour_foreach (const GskContour *self,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
void gsk_contour_get_start_end (const GskContour *self,
graphene_point_t *start,
graphene_point_t *end);
void gsk_contour_get_point (const GskContour *self,
gpointer measure_data,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent);
gboolean gsk_contour_get_closest_point (const GskContour *self,
gpointer measure_data,
float tolerance,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent);
int gsk_contour_get_winding (const GskContour *self,
gpointer measure_data,
const graphene_point_t *point,
gboolean *on_edge);
void gsk_contour_add_segment (const GskContour *self,
GskPathBuilder *builder,
gpointer measure_data,
gboolean emit_move_to,
float start,
float end);
gboolean gsk_contour_get_stroke_bounds (const GskContour *self,
const GskStroke *stroke,
graphene_rect_t *bounds);
gsize gsk_contour_get_shader_size (const GskContour *self);
void gsk_contour_to_shader (const GskContour *self,
guchar *data);
G_END_DECLS
#endif /* __GSK_CONTOUR_PRIVATE_H__ */

979
gsk/gskcurve.c Normal file
View File

@@ -0,0 +1,979 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskcurveprivate.h"
#define MIN_PROGRESS (1/1024.f)
typedef struct _GskCurveClass GskCurveClass;
struct _GskCurveClass
{
void (* init) (GskCurve *curve,
gskpathop op);
void (* init_foreach) (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight);
void (* get_point) (const GskCurve *curve,
float t,
graphene_point_t *pos);
void (* get_tangent) (const GskCurve *curve,
float t,
graphene_vec2_t *tangent);
void (* split) (const GskCurve *curve,
float progress,
GskCurve *result1,
GskCurve *result2);
void (* segment) (const GskCurve *curve,
float start,
float end,
GskCurve *segment);
gboolean (* decompose) (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data);
gskpathop (* pathop) (const GskCurve *curve);
const graphene_point_t * (* get_start_point) (const GskCurve *curve);
const graphene_point_t * (* get_end_point) (const GskCurve *curve);
void (* get_start_tangent) (const GskCurve *curve,
graphene_vec2_t *tangent);
void (* get_end_tangent) (const GskCurve *curve,
graphene_vec2_t *tangent);
};
static void
get_tangent (const graphene_point_t *p0,
const graphene_point_t *p1,
graphene_vec2_t *t)
{
graphene_vec2_init (t, p1->x - p0->x, p1->y - p0->y);
graphene_vec2_normalize (t, t);
}
/** LINE **/
static void
gsk_line_curve_init_from_points (GskLineCurve *self,
GskPathOperation op,
const graphene_point_t *start,
const graphene_point_t *end)
{
self->op = op;
self->points[0] = *start;
self->points[1] = *end;
}
static void
gsk_line_curve_init (GskCurve *curve,
gskpathop op)
{
GskLineCurve *self = &curve->line;
const graphene_point_t *pts = gsk_pathop_points (op);
gsk_line_curve_init_from_points (self, gsk_pathop_op (op), &pts[0], &pts[1]);
}
static void
gsk_line_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskLineCurve *self = &curve->line;
g_assert (n_pts == 2);
gsk_line_curve_init_from_points (self, op, &pts[0], &pts[1]);
}
static void
gsk_line_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskLineCurve *self = &curve->line;
graphene_point_interpolate (&self->points[0], &self->points[1], t, pos);
}
static void
gsk_line_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskLineCurve *self = &curve->line;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_line_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const GskLineCurve *self = &curve->line;
graphene_point_t point;
graphene_point_interpolate (&self->points[0], &self->points[1], progress, &point);
if (start)
gsk_line_curve_init_from_points (&start->line, GSK_PATH_LINE, &self->points[0], &point);
if (end)
gsk_line_curve_init_from_points (&end->line, GSK_PATH_LINE, &point, &self->points[1]);
}
static void
gsk_line_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
const GskLineCurve *self = &curve->line;
graphene_point_t start_point, end_point;
graphene_point_interpolate (&self->points[0], &self->points[1], start, &start_point);
graphene_point_interpolate (&self->points[0], &self->points[1], end, &end_point);
gsk_line_curve_init_from_points (&segment->line, GSK_PATH_LINE, &start_point, &end_point);
}
static gboolean
gsk_line_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskLineCurve *self = &curve->line;
return add_line_func (&self->points[0], &self->points[1], 0.0f, 1.0f, GSK_CURVE_LINE_REASON_STRAIGHT, user_data);
}
static gskpathop
gsk_line_curve_pathop (const GskCurve *curve)
{
const GskLineCurve *self = &curve->line;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_line_curve_get_start_point (const GskCurve *curve)
{
const GskLineCurve *self = &curve->line;
return &self->points[0];
}
static const graphene_point_t *
gsk_line_curve_get_end_point (const GskCurve *curve)
{
const GskLineCurve *self = &curve->line;
return &self->points[1];
}
static void
gsk_line_curve_get_start_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskLineCurve *self = &curve->line;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static const GskCurveClass GSK_LINE_CURVE_CLASS = {
gsk_line_curve_init,
gsk_line_curve_init_foreach,
gsk_line_curve_get_point,
gsk_line_curve_get_tangent,
gsk_line_curve_split,
gsk_line_curve_segment,
gsk_line_curve_decompose,
gsk_line_curve_pathop,
gsk_line_curve_get_start_point,
gsk_line_curve_get_end_point,
gsk_line_curve_get_start_end_tangent,
gsk_line_curve_get_start_end_tangent
};
/** CURVE **/
static void
gsk_curve_curve_init_from_points (GskCurveCurve *self,
const graphene_point_t pts[4])
{
self->op = GSK_PATH_CURVE;
self->has_coefficients = FALSE;
memcpy (self->points, pts, sizeof (graphene_point_t) * 4);
}
static void
gsk_curve_curve_init (GskCurve *curve,
gskpathop op)
{
GskCurveCurve *self = &curve->curve;
gsk_curve_curve_init_from_points (self, gsk_pathop_points (op));
}
static void
gsk_curve_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskCurveCurve *self = &curve->curve;
g_assert (n_pts == 4);
gsk_curve_curve_init_from_points (self, pts);
}
static void
gsk_curve_curve_ensure_coefficients (const GskCurveCurve *curve)
{
GskCurveCurve *self = (GskCurveCurve *) curve;
const graphene_point_t *pts = &self->points[0];
if (self->has_coefficients)
return;
self->coeffs[0] = GRAPHENE_POINT_INIT (pts[3].x - 3.0f * pts[2].x + 3.0f * pts[1].x - pts[0].x,
pts[3].y - 3.0f * pts[2].y + 3.0f * pts[1].y - pts[0].y);
self->coeffs[1] = GRAPHENE_POINT_INIT (3.0f * pts[2].x - 6.0f * pts[1].x + 3.0f * pts[0].x,
3.0f * pts[2].y - 6.0f * pts[1].y + 3.0f * pts[0].y);
self->coeffs[2] = GRAPHENE_POINT_INIT (3.0f * pts[1].x - 3.0f * pts[0].x,
3.0f * pts[1].y - 3.0f * pts[0].y);
self->coeffs[3] = pts[0];
self->has_coefficients = TRUE;
}
static void
gsk_curve_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskCurveCurve *self = &curve->curve;
const graphene_point_t *c = self->coeffs;
gsk_curve_curve_ensure_coefficients (self);
*pos = GRAPHENE_POINT_INIT (((c[0].x * t + c[1].x) * t +c[2].x) * t + c[3].x,
((c[0].y * t + c[1].y) * t +c[2].y) * t + c[3].y);
}
static void
gsk_curve_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskCurveCurve *self = &curve->curve;
const graphene_point_t *c = self->coeffs;
gsk_curve_curve_ensure_coefficients (self);
graphene_vec2_init (tangent,
(3.0f * c[0].x * t + 2.0f * c[1].x) * t + c[2].x,
(3.0f * c[0].y * t + 2.0f * c[1].y) * t + c[2].y);
graphene_vec2_normalize (tangent, tangent);
}
static void
gsk_curve_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const GskCurveCurve *self = &curve->curve;
const graphene_point_t *pts = self->points;
graphene_point_t ab, bc, cd;
graphene_point_t abbc, bccd;
graphene_point_t final;
graphene_point_interpolate (&pts[0], &pts[1], progress, &ab);
graphene_point_interpolate (&pts[1], &pts[2], progress, &bc);
graphene_point_interpolate (&pts[2], &pts[3], progress, &cd);
graphene_point_interpolate (&ab, &bc, progress, &abbc);
graphene_point_interpolate (&bc, &cd, progress, &bccd);
graphene_point_interpolate (&abbc, &bccd, progress, &final);
if (start)
gsk_curve_curve_init_from_points (&start->curve, (graphene_point_t[4]) { pts[0], ab, abbc, final });
if (end)
gsk_curve_curve_init_from_points (&end->curve, (graphene_point_t[4]) { final, bccd, cd, pts[3] });
}
static void
gsk_curve_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
GskCurve tmp;
gsk_curve_curve_split (curve, start, NULL, &tmp);
gsk_curve_curve_split (&tmp, (end - start) / (1.0f - start), segment, NULL);
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_curve_curve_too_curvy (const GskCurveCurve *self,
float tolerance)
{
const graphene_point_t *pts = self->points;
graphene_point_t p;
graphene_point_interpolate (&pts[0], &pts[3], 1.0f / 3, &p);
if (ABS (p.x - pts[1].x) + ABS (p.y - pts[1].y) > tolerance)
return TRUE;
graphene_point_interpolate (&pts[0], &pts[3], 2.0f / 3, &p);
if (ABS (p.x - pts[2].x) + ABS (p.y - pts[2].y) > tolerance)
return TRUE;
return FALSE;
}
static gboolean
gsk_curce_curve_decompose_step (const GskCurve *curve,
float start_progress,
float end_progress,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskCurveCurve *self = &curve->curve;
GskCurve left, right;
float mid_progress;
if (!gsk_curve_curve_too_curvy (self, tolerance))
return add_line_func (&self->points[0], &self->points[3], start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data);
if (end_progress - start_progress <= MIN_PROGRESS)
return add_line_func (&self->points[0], &self->points[3], start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
gsk_curve_curve_split ((const GskCurve *) self, 0.5, &left, &right);
mid_progress = (start_progress + end_progress) / 2;
return gsk_curce_curve_decompose_step (&left, start_progress, mid_progress, tolerance, add_line_func, user_data)
&& gsk_curce_curve_decompose_step (&right, mid_progress, end_progress, tolerance, add_line_func, user_data);
}
static gboolean
gsk_curve_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
return gsk_curce_curve_decompose_step (curve, 0.0, 1.0, tolerance, add_line_func, user_data);
}
static gskpathop
gsk_curve_curve_pathop (const GskCurve *curve)
{
const GskCurveCurve *self = &curve->curve;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_curve_curve_get_start_point (const GskCurve *curve)
{
const GskCurveCurve *self = &curve->curve;
return &self->points[0];
}
static const graphene_point_t *
gsk_curve_curve_get_end_point (const GskCurve *curve)
{
const GskCurveCurve *self = &curve->curve;
return &self->points[3];
}
static void
gsk_curve_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskCurveCurve *self = &curve->curve;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_curve_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskCurveCurve *self = &curve->curve;
get_tangent (&self->points[2], &self->points[3], tangent);
}
static const GskCurveClass GSK_CURVE_CURVE_CLASS = {
gsk_curve_curve_init,
gsk_curve_curve_init_foreach,
gsk_curve_curve_get_point,
gsk_curve_curve_get_tangent,
gsk_curve_curve_split,
gsk_curve_curve_segment,
gsk_curve_curve_decompose,
gsk_curve_curve_pathop,
gsk_curve_curve_get_start_point,
gsk_curve_curve_get_end_point,
gsk_curve_curve_get_start_tangent,
gsk_curve_curve_get_end_tangent
};
/** CONIC **/
static void
gsk_conic_curve_init_from_points (GskConicCurve *self,
const graphene_point_t pts[4])
{
self->op = GSK_PATH_CONIC;
self->has_coefficients = FALSE;
memcpy (self->points, pts, sizeof (graphene_point_t) * 4);
}
static void
gsk_conic_curve_init (GskCurve *curve,
gskpathop op)
{
GskConicCurve *self = &curve->conic;
gsk_conic_curve_init_from_points (self, gsk_pathop_points (op));
}
static void
gsk_conic_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskConicCurve *self = &curve->conic;
g_assert (n_pts == 3);
gsk_conic_curve_init_from_points (self,
(graphene_point_t[4]) {
pts[0],
pts[1],
GRAPHENE_POINT_INIT (weight, 0),
pts[2]
});
}
static inline float
gsk_conic_curve_get_weight (const GskConicCurve *self)
{
return self->points[2].x;
}
static void
gsk_conic_curve_ensure_coefficents (const GskConicCurve *curve)
{
GskConicCurve *self = (GskConicCurve *) curve;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
graphene_point_t pw = GRAPHENE_POINT_INIT (w * pts[1].x, w * pts[1].y);
if (self->has_coefficients)
return;
self->num[2] = pts[0];
self->num[1] = GRAPHENE_POINT_INIT (2 * (pw.x - pts[0].x),
2 * (pw.y - pts[0].y));
self->num[0] = GRAPHENE_POINT_INIT (pts[3].x - 2 * pw.x + pts[0].x,
pts[3].y - 2 * pw.y + pts[0].y);
self->denom[2] = GRAPHENE_POINT_INIT (1, 1);
self->denom[1] = GRAPHENE_POINT_INIT (2 * (w - 1), 2 * (w - 1));
self->denom[0] = GRAPHENE_POINT_INIT (-self->denom[1].x, -self->denom[1].y);
self->has_coefficients = TRUE;
}
static inline void
gsk_curve_eval_quad (const graphene_point_t quad[3],
float progress,
graphene_point_t *result)
{
*result = GRAPHENE_POINT_INIT ((quad[0].x * progress + quad[1].x) * progress + quad[2].x,
(quad[0].y * progress + quad[1].y) * progress + quad[2].y);
}
static inline void
gsk_conic_curve_eval_point (const GskConicCurve *self,
float progress,
graphene_point_t *point)
{
graphene_point_t num, denom;
gsk_curve_eval_quad (self->num, progress, &num);
gsk_curve_eval_quad (self->denom, progress, &denom);
*point = GRAPHENE_POINT_INIT (num.x / denom.x, num.y / denom.y);
}
static void
gsk_conic_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskConicCurve *self = &curve->conic;
gsk_conic_curve_ensure_coefficents (self);
gsk_conic_curve_eval_point (self, t, pos);
}
static void
gsk_conic_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t tmp;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
/* The tangent will be 0 in these corner cases, just
* treat it like a line here. */
if ((t <= 0.f && graphene_point_equal (&pts[0], &pts[1])) ||
(t >= 1.f && graphene_point_equal (&pts[1], &pts[3])))
{
graphene_vec2_init (tangent, pts[3].x - pts[0].x, pts[3].y - pts[0].y);
return;
}
gsk_curve_eval_quad ((graphene_point_t[3]) {
GRAPHENE_POINT_INIT ((w - 1) * (pts[3].x - pts[0].x),
(w - 1) * (pts[3].y - pts[0].y)),
GRAPHENE_POINT_INIT (pts[3].x - pts[0].x - 2 * w * (pts[1].x - pts[0].x),
pts[3].y - pts[0].y - 2 * w * (pts[1].y - pts[0].y)),
GRAPHENE_POINT_INIT (w * (pts[1].x - pts[0].x),
w * (pts[1].y - pts[0].y))
},
t,
&tmp);
graphene_vec2_init (tangent, tmp.x, tmp.y);
graphene_vec2_normalize (tangent, tangent);
}
static void
split_bezier3d_recurse (const graphene_point3d_t *p,
int l,
float t,
graphene_point3d_t *left,
graphene_point3d_t *right,
int *lpos,
int *rpos)
{
if (l == 1)
{
left[*lpos] = p[0];
right[*rpos] = p[0];
}
else
{
graphene_point3d_t *np;
int i;
np = g_alloca (sizeof (graphene_point3d_t) * (l - 1));
for (i = 0; i < l - 1; i++)
{
if (i == 0)
{
left[*lpos] = p[i];
(*lpos)++;
}
if (i + 1 == l - 1)
{
right[*rpos] = p[i + 1];
(*rpos)--;
}
graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]);
}
split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos);
}
}
static void
split_bezier3d (const graphene_point3d_t *p,
int l,
float t,
graphene_point3d_t *left,
graphene_point3d_t *right)
{
int lpos = 0;
int rpos = l - 1;
split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos);
}
static void
gsk_conic_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const GskConicCurve *self = &curve->conic;
graphene_point3d_t p[3];
graphene_point3d_t l[3], r[3];
graphene_point_t left[4], right[4];
float w;
/* do de Casteljau in homogeneous coordinates... */
w = self->points[2].x;
p[0] = GRAPHENE_POINT3D_INIT (self->points[0].x, self->points[0].y, 1);
p[1] = GRAPHENE_POINT3D_INIT (self->points[1].x * w, self->points[1].y * w, w);
p[2] = GRAPHENE_POINT3D_INIT (self->points[3].x, self->points[3].y, 1);
split_bezier3d (p, 3, progress, l, r);
/* then project the control points down */
left[0] = GRAPHENE_POINT_INIT (l[0].x / l[0].z, l[0].y / l[0].z);
left[1] = GRAPHENE_POINT_INIT (l[1].x / l[1].z, l[1].y / l[1].z);
left[3] = GRAPHENE_POINT_INIT (l[2].x / l[2].z, l[2].y / l[2].z);
right[0] = GRAPHENE_POINT_INIT (r[0].x / r[0].z, r[0].y / r[0].z);
right[1] = GRAPHENE_POINT_INIT (r[1].x / r[1].z, r[1].y / r[1].z);
right[3] = GRAPHENE_POINT_INIT (r[2].x / r[2].z, r[2].y / r[2].z);
/* normalize the outer weights to be 1 by using
* the fact that weights w_i and c*w_i are equivalent
* for any nonzero constant c
*/
for (int i = 0; i < 3; i++)
{
l[i].z /= l[0].z;
r[i].z /= r[2].z;
}
/* normalize the inner weight to be 1 by using
* the fact that w_0*w_2/w_1^2 is a constant for
* all equivalent weights.
*/
left[2] = GRAPHENE_POINT_INIT (l[1].z / sqrt (l[2].z), 0);
right[2] = GRAPHENE_POINT_INIT (r[1].z / sqrt (r[0].z), 0);
if (start)
gsk_curve_init (start, gsk_pathop_encode (GSK_PATH_CONIC, left));
if (end)
gsk_curve_init (end, gsk_pathop_encode (GSK_PATH_CONIC, right));
}
static void
gsk_conic_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t start_num, start_denom;
graphene_point_t mid_num, mid_denom;
graphene_point_t end_num, end_denom;
graphene_point_t ctrl_num, ctrl_denom;
float mid;
if (start <= 0.0f)
return gsk_conic_curve_split (curve, end, segment, NULL);
else if (end >= 1.0f)
return gsk_conic_curve_split (curve, start, NULL, segment);
gsk_conic_curve_ensure_coefficents (self);
gsk_curve_eval_quad (self->num, start, &start_num);
gsk_curve_eval_quad (self->denom, start, &start_denom);
mid = (start + end) / 2;
gsk_curve_eval_quad (self->num, mid, &mid_num);
gsk_curve_eval_quad (self->denom, mid, &mid_denom);
gsk_curve_eval_quad (self->num, end, &end_num);
gsk_curve_eval_quad (self->denom, end, &end_denom);
ctrl_num = GRAPHENE_POINT_INIT (2 * mid_num.x - (start_num.x + end_num.x) / 2,
2 * mid_num.y - (start_num.y + end_num.y) / 2);
ctrl_denom = GRAPHENE_POINT_INIT (2 * mid_denom.x - (start_denom.x + end_denom.x) / 2,
2 * mid_denom.y - (start_denom.y + end_denom.y) / 2);
gsk_conic_curve_init_from_points (&segment->conic,
(graphene_point_t[4]) {
GRAPHENE_POINT_INIT (start_num.x / start_denom.x,
start_num.y / start_denom.y),
GRAPHENE_POINT_INIT (ctrl_num.x / ctrl_denom.x,
ctrl_num.y / ctrl_denom.y),
GRAPHENE_POINT_INIT (ctrl_denom.x / sqrtf (start_denom.x * end_denom.x),
0),
GRAPHENE_POINT_INIT (end_num.x / end_denom.x,
end_num.y / end_denom.y)
});
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_conic_curve_too_curvy (const graphene_point_t *start,
const graphene_point_t *mid,
const graphene_point_t *end,
float tolerance)
{
return fabs ((start->x + end->x) * 0.5 - mid->x) > tolerance
|| fabs ((start->y + end->y) * 0.5 - mid->y) > tolerance;
}
static gboolean
gsk_conic_curve_decompose_subdivide (const GskConicCurve *self,
float tolerance,
const graphene_point_t *start,
float start_progress,
const graphene_point_t *end,
float end_progress,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
graphene_point_t mid;
float mid_progress;
mid_progress = (start_progress + end_progress) / 2;
gsk_conic_curve_eval_point (self, mid_progress, &mid);
if (!gsk_conic_curve_too_curvy (start, &mid, end, tolerance))
return add_line_func (start, end, start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data);
if (end_progress - start_progress <= MIN_PROGRESS)
return add_line_func (start, end, start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
return gsk_conic_curve_decompose_subdivide (self, tolerance,
start, start_progress, &mid, mid_progress,
add_line_func, user_data)
&& gsk_conic_curve_decompose_subdivide (self, tolerance,
&mid, mid_progress, end, end_progress,
add_line_func, user_data);
}
static gboolean
gsk_conic_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t mid;
gsk_conic_curve_ensure_coefficents (self);
gsk_conic_curve_eval_point (self, 0.5, &mid);
return gsk_conic_curve_decompose_subdivide (self,
tolerance,
&self->points[0],
0.0f,
&mid,
0.5f,
add_line_func,
user_data)
&& gsk_conic_curve_decompose_subdivide (self,
tolerance,
&mid,
0.5f,
&self->points[3],
1.0f,
add_line_func,
user_data);
}
static gskpathop
gsk_conic_curve_pathop (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_conic_curve_get_start_point (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return &self->points[0];
}
static const graphene_point_t *
gsk_conic_curve_get_end_point (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return &self->points[3];
}
static void
gsk_conic_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_conic_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
get_tangent (&self->points[1], &self->points[3], tangent);
}
static const GskCurveClass GSK_CONIC_CURVE_CLASS = {
gsk_conic_curve_init,
gsk_conic_curve_init_foreach,
gsk_conic_curve_get_point,
gsk_conic_curve_get_tangent,
gsk_conic_curve_split,
gsk_conic_curve_segment,
gsk_conic_curve_decompose,
gsk_conic_curve_pathop,
gsk_conic_curve_get_start_point,
gsk_conic_curve_get_end_point,
gsk_conic_curve_get_start_tangent,
gsk_conic_curve_get_end_tangent
};
/** API **/
static const GskCurveClass *
get_class (GskPathOperation op)
{
const GskCurveClass *klasses[] = {
[GSK_PATH_CLOSE] = &GSK_LINE_CURVE_CLASS,
[GSK_PATH_LINE] = &GSK_LINE_CURVE_CLASS,
[GSK_PATH_CURVE] = &GSK_CURVE_CURVE_CLASS,
[GSK_PATH_CONIC] = &GSK_CONIC_CURVE_CLASS,
};
g_assert (op < G_N_ELEMENTS (klasses) && klasses[op] != NULL);
return klasses[op];
}
void
gsk_curve_init (GskCurve *curve,
gskpathop op)
{
get_class (gsk_pathop_op (op))->init (curve, op);
}
void
gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
get_class (op)->init_foreach (curve, op, pts, n_pts, weight);
}
void
gsk_curve_get_point (const GskCurve *curve,
float progress,
graphene_point_t *pos)
{
get_class (curve->op)->get_point (curve, progress, pos);
}
void
gsk_curve_get_tangent (const GskCurve *curve,
float progress,
graphene_vec2_t *tangent)
{
get_class (curve->op)->get_tangent (curve, progress, tangent);
}
void
gsk_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
get_class (curve->op)->split (curve, progress, start, end);
}
void
gsk_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
if (start <= 0 && end >= 1)
{
*segment = *curve;
return;
}
get_class (curve->op)->segment (curve, start, end, segment);
}
gboolean
gsk_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
return get_class (curve->op)->decompose (curve, tolerance, add_line_func, user_data);
}
gskpathop
gsk_curve_pathop (const GskCurve *curve)
{
return get_class (curve->op)->pathop (curve);
}
const graphene_point_t *
gsk_curve_get_start_point (const GskCurve *curve)
{
return get_class (curve->op)->get_start_point (curve);
}
const graphene_point_t *
gsk_curve_get_end_point (const GskCurve *curve)
{
return get_class (curve->op)->get_end_point (curve);
}
void
gsk_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
get_class (curve->op)->get_start_tangent (curve, tangent);
}
void
gsk_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
get_class (curve->op)->get_end_tangent (curve, tangent);
}

129
gsk/gskcurveprivate.h Normal file
View File

@@ -0,0 +1,129 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_CURVE_PRIVATE_H__
#define __GSK_CURVE_PRIVATE_H__
#include "gskpathopprivate.h"
G_BEGIN_DECLS
typedef gpointer gskpathop;
typedef union _GskCurve GskCurve;
typedef struct _GskLineCurve GskLineCurve;
typedef struct _GskCurveCurve GskCurveCurve;
typedef struct _GskConicCurve GskConicCurve;
struct _GskLineCurve
{
GskPathOperation op;
gboolean padding;
graphene_point_t points[2];
};
struct _GskCurveCurve
{
GskPathOperation op;
gboolean has_coefficients;
graphene_point_t points[4];
graphene_point_t coeffs[4];
};
struct _GskConicCurve
{
GskPathOperation op;
gboolean has_coefficients;
graphene_point_t points[4];
graphene_point_t num[3];
graphene_point_t denom[3];
};
union _GskCurve
{
GskPathOperation op;
GskLineCurve line;
GskCurveCurve curve;
GskConicCurve conic;
};
typedef enum {
GSK_CURVE_LINE_REASON_STRAIGHT,
GSK_CURVE_LINE_REASON_SHORT
} GskCurveLineReason;
typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from,
const graphene_point_t *to,
float from_progress,
float to_progress,
GskCurveLineReason reason,
gpointer user_data);
void gsk_curve_init (GskCurve *curve,
gskpathop op);
void gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight);
void gsk_curve_get_point (const GskCurve *curve,
float progress,
graphene_point_t *pos);
void gsk_curve_get_tangent (const GskCurve *curve,
float progress,
graphene_vec2_t *tangent);
void gsk_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end);
void gsk_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment);
gboolean gsk_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data);
gskpathop gsk_curve_pathop (const GskCurve *curve);
#define gsk_curve_builder_to(curve, builder) gsk_path_builder_pathop_to ((builder), gsk_curve_pathop (curve))
const graphene_point_t *gsk_curve_get_start_point (const GskCurve *curve);
const graphene_point_t *gsk_curve_get_end_point (const GskCurve *curve);
void gsk_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent);
void gsk_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent);
G_END_DECLS
#endif /* __GSK_CURVE_PRIVATE_H__ */

View File

@@ -42,6 +42,8 @@
* @GSK_REPEAT_NODE: A node that repeats the child's contents
* @GSK_CLIP_NODE: A node that clips its child to a rectangular area
* @GSK_ROUNDED_CLIP_NODE: A node that clips its child to a rounded rectangle
* @GSK_FILL_NODE: A node that fills a path
* @GSK_STROKE_NODE: A node that strokes a path
* @GSK_SHADOW_NODE: A node that draws a shadow below its child
* @GSK_BLEND_NODE: A node that blends two children together
* @GSK_CROSS_FADE_NODE: A node that cross-fades between two children
@@ -74,6 +76,8 @@ typedef enum {
GSK_REPEAT_NODE,
GSK_CLIP_NODE,
GSK_ROUNDED_CLIP_NODE,
GSK_FILL_NODE,
GSK_STROKE_NODE,
GSK_SHADOW_NODE,
GSK_BLEND_NODE,
GSK_CROSS_FADE_NODE,
@@ -170,6 +174,105 @@ typedef enum {
GSK_CORNER_BOTTOM_LEFT
} GskCorner;
/**
* GskFillRule:
* @GSK_FILL_RULE_WINDING: If the path crosses the ray from
* left-to-right, counts +1. If the path crosses the ray
* from right to left, counts -1. (Left and right are determined
* from the perspective of looking along the ray from the starting
* point.) If the total count is non-zero, the point will be filled.
* @GSK_FILL_RULE_EVEN_ODD: Counts the total number of
* intersections, without regard to the orientation of the contour. If
* the total number of intersections is odd, the point will be
* filled.
*
* #GskFillRule is used to select how paths are filled, for example in
* gsk_fill_node_new(). Whether or not a point is included in the fill is
* determined by taking a ray from that point to infinity and looking
* at intersections with the path. The ray can be in any direction,
* as long as it doesn't pass through the end point of a segment
* or have a tricky intersection such as intersecting tangent to the path.
* (Note that filling is not actually implemented in this way. This
* is just a description of the rule that is applied.)
*
* New entries may be added in future versions.
**/
typedef enum {
GSK_FILL_RULE_WINDING,
GSK_FILL_RULE_EVEN_ODD
} GskFillRule;
/**
* GskLineCap:
* @GSK_LINE_CAP_BUTT: Start and stop the line exactly at the start
* and end point
* @GSK_LINE_CAP_ROUND: Use a round ending, the center of the circle
* is the start or end point.
* @GSK_LINE_CAP_SQUARE: use squared ending, the center of the square
* is the start or end point.
*
* Specifies how to render the start and end points of contours or
* dashes when stroking.
*
* The default line cap style is %GSK_LINE_CAP_BUTT.
*/
typedef enum {
GSK_LINE_CAP_BUTT,
GSK_LINE_CAP_ROUND,
GSK_LINE_CAP_SQUARE
} GskLineCap;
/**
* GskLineJoin:
* @GSK_LINE_JOIN_MITER: Use a sharp, angled corner
* @GSK_LINE_JOIN_MITER_CLIP: Use a sharp, angled corner, at a distance
* @GSK_LINE_JOIN_ROUND: Use a round join, the center of the circle is
* the joint point
* @GSK_LINE_JOIN_BEVEL: Use a cut-off join, the join is cut off at half
* the line width from the joint point
*
* Specifies how to render the junction of two lines when stroking.
*
* See gsk_stroke_set_miter_limit() for details on the difference between
* @GSK_LINE_JOIN_MITER and @GSK_LINE_JOIN_MITER_CLIP.
*
* The default line join style is %GSK_LINE_JOIN_MITER.
**/
typedef enum {
GSK_LINE_JOIN_MITER,
GSK_LINE_JOIN_MITER_CLIP,
GSK_LINE_JOIN_ROUND,
GSK_LINE_JOIN_BEVEL
} GskLineJoin;
/**
* GskPathOperation:
* @GSK_PATH_MOVE: A move-to operation, with 1 point describing the
* target point.
* @GSK_PATH_LINE: A line-to operation, with 2 points describing the
* start and end point of a straight line.
* @GSK_PATH_CLOSE: A close operation ending the current contour with
* a line back to the starting point. Two points describe the start
* and end of the line.
* @GSK_PATH_CURVE: A curve-to operation describing a cubic Bézier curve
* with 4 points describing the start point, the two control points
* and the end point of the curve.
* @GSK_PATH_CONIC: A weighted quadratic bezier curve with 3 points
* describing the start point, control point and end point of the
* curve. A weight for the curve will be passed, too.
*
* Path operations can be used to approximate a #GskPath.
*
* More values may be added in the future.
**/
typedef enum {
GSK_PATH_MOVE,
GSK_PATH_CLOSE,
GSK_PATH_LINE,
GSK_PATH_CURVE,
GSK_PATH_CONIC,
} GskPathOperation;
/**
* GskSerializationError:
* @GSK_SERIALIZATION_UNSUPPORTED_FORMAT: The format can not be identified

1167
gsk/gskpath.c Normal file

File diff suppressed because it is too large Load Diff

115
gsk/gskpath.h Normal file
View File

@@ -0,0 +1,115 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_H__
#define __GSK_PATH_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
/**
* GskPathForeachFlags:
* @GSK_PATH_FOREACH_ALLOW_CURVE: Allow emission of %GSK_PATH_CURVE
* operations.
* @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of %GSK_PATH_CONIC
* operations.
*
* Flags that can be passed to gsk_path_foreach() to enable additional
* features.
*
* By default, gsk_path_foreach() will only emit a path with all operations
* flattened to straight lines to allow for maximum compatibility. The only
* operations emitted will be %GSK_PATH_MOVE, %GSK_PATH_LINE and %GSK_PATH_CLOSE.
*/
typedef enum
{
GSK_PATH_FOREACH_ALLOW_CURVE = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 1)
} GskPathForeachFlags;
/**
* GskPathForeachFunc:
* @op: The operation to perform
* @pts: The points of the operation
* @n_pts: The number of points
* @weight: The weight for conic curves, or unused if not a conic curve.
* @user_data: The user data provided with the function
*
* Prototype of the callback to iterate throught the operations of
* a path.
*
* Returns: %TRUE to continue evaluating the path, %FALSE to
* immediately abort and not call the function again.
*/
typedef gboolean (* GskPathForeachFunc) (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data);
#define GSK_TYPE_PATH (gsk_path_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_ref (GskPath *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_unref (GskPath *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_print (GskPath *self,
GString *string);
GDK_AVAILABLE_IN_ALL
char * gsk_path_to_string (GskPath *self);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_parse (const char *string);
GDK_AVAILABLE_IN_ALL
void gsk_path_to_cairo (GskPath *self,
cairo_t *cr);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_is_empty (GskPath *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_get_bounds (GskPath *self,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_get_stroke_bounds (GskPath *self,
const GskStroke *stroke,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_foreach (GskPath *self,
GskPathForeachFlags flags,
GskPathForeachFunc func,
gpointer user_data);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_END_DECLS
#endif /* __GSK_PATH_H__ */

1030
gsk/gskpathbuilder.c Normal file

File diff suppressed because it is too large Load Diff

136
gsk/gskpathbuilder.h Normal file
View File

@@ -0,0 +1,136 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_BUILDER_H__
#define __GSK_PATH_BUILDER_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gskroundedrect.h>
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_builder_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPathBuilder * gsk_path_builder_new (void);
GDK_AVAILABLE_IN_ALL
GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_unref (GskPathBuilder *self);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_builder_free_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT;
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_builder_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT;
GDK_AVAILABLE_IN_ALL
const graphene_point_t *gsk_path_builder_get_current_point (GskPathBuilder *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_path (GskPathBuilder *self,
GskPath *path);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_cairo_path (GskPathBuilder *self,
const cairo_path_t *path);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_layout (GskPathBuilder *self,
PangoLayout *layout);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_rect (GskPathBuilder *self,
const graphene_rect_t *rect);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
const GskRoundedRect *rect);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_circle (GskPathBuilder *self,
const graphene_point_t *center,
float radius);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_ellipse (GskPathBuilder *self,
const graphene_point_t *center,
const graphene_size_t *radius);
/* next function implemented in gskpathmeasure.c */
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_segment (GskPathBuilder *self,
GskPathMeasure *measure,
float start,
float end);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_move_to (GskPathBuilder *self,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_move_to (GskPathBuilder *self,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_line_to (GskPathBuilder *self,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_line_to (GskPathBuilder *self,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_curve_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_curve_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_close (GskPathBuilder *self);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref)
G_END_DECLS
#endif /* __GSK_PATH_BUILDER_H__ */

292
gsk/gskpathdash.c Normal file
View File

@@ -0,0 +1,292 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathdashprivate.h"
#include "gskcontourprivate.h"
#include "gskcurveprivate.h"
#include "gskpathprivate.h"
#include "gskstrokeprivate.h"
typedef struct
{
float offset; /* how much of the current dash we've spent */
gsize dash_index; /* goes from 0 to n_dash * 2, so we don't have to care about on/off
for uneven dashes */
gboolean on; /* If we're currently dashing or not */
gboolean may_close; /* TRUE if we haven't turned the dash off in this contour */
gboolean needs_move_to; /* If we have emitted the initial move_to() yet */
enum {
NORMAL, /* no special behavior required */
SKIP, /* skip the next dash */
ONLY, /* only do the first dash */
DONE /* done with the first dash */
} first_dash_behavior; /* How to handle the first dash in the loop. We loop closed contours
twice to make sure the first dash and the last dash can get joined */
GskCurve curve; /* Curve we are currently processing */
float collect_start; /* We're collecting multiple line segments when decomposing. */
float collect_length; /* No need to emit a curve for every line segment when the dash is long enough. */
/* from the stroke */
float *dash;
gsize n_dash;
float dash_length;
float dash_offset;
float tolerance;
GskPathForeachFunc func;
gpointer user_data;
} GskPathDash;
static void
gsk_path_dash_setup (GskPathDash *self)
{
self->offset = fmodf (self->dash_offset, 2 * self->dash_length);
self->dash_index = 0;
self->on = TRUE;
self->may_close = TRUE;
while (self->offset > self->dash[self->dash_index % self->n_dash])
{
self->offset -= self->dash[self->dash_index % self->n_dash];
self->dash_index++;
self->on = !self->on;
}
if (self->first_dash_behavior != ONLY)
self->needs_move_to = TRUE;
}
static gboolean
gsk_path_dash_ensure_move_to (GskPathDash *self,
const graphene_point_t *pt)
{
if (!self->needs_move_to)
return TRUE;
if (!self->func (GSK_PATH_MOVE, pt, 1, 0, self->user_data))
return FALSE;
self->needs_move_to = FALSE;
return TRUE;
}
static gboolean
gsk_path_dash_add_line_segment (const graphene_point_t *start,
const graphene_point_t *end,
float t_start,
float t_end,
GskCurveLineReason reason,
gpointer user_data)
{
GskPathDash *self = user_data;
float remaining, length, t_step;
length = graphene_point_distance (start, end, NULL, NULL);
if (self->collect_length)
{
t_start = self->collect_start;
length += self->collect_length;
self->collect_length = 0;
}
t_step = t_end - t_start;
remaining = length;
while (remaining)
{
float piece;
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
/* try collecting multiple line segments */
if (t_end < 1.0)
{
self->collect_start = t_start + t_step * (length - remaining) / length;
self->collect_length = remaining;
return TRUE;
}
piece = remaining;
}
else
piece = self->dash[self->dash_index % self->n_dash] - self->offset;
if (self->on)
{
if (self->first_dash_behavior != SKIP)
{
GskCurve segment;
if (piece)
{
gsk_curve_segment (&self->curve,
t_start + t_step * (length - remaining) / length,
t_start + t_step * (length - (remaining - piece)) / length,
&segment);
if (!gsk_path_dash_ensure_move_to (self, gsk_curve_get_start_point (&segment)))
return FALSE;
if (!gsk_pathop_foreach (gsk_curve_pathop (&segment), self->func, self->user_data))
return FALSE;
}
else
{
graphene_point_t p;
gsk_curve_get_point (&self->curve, t_start + t_step * (length - remaining) / length, &p);
if (!gsk_path_dash_ensure_move_to (self, &p))
return FALSE;
}
}
}
else
{
self->may_close = FALSE;
if (self->first_dash_behavior == ONLY)
{
self->first_dash_behavior = DONE;
return FALSE;
}
self->first_dash_behavior = NORMAL;
}
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
self->offset += remaining;
remaining = 0;
}
else
{
remaining -= piece;
self->offset = 0;
self->dash_index++;
self->dash_index %= 2 * self->n_dash;
self->on = !self->on;
self->needs_move_to = TRUE;
}
}
return TRUE;
}
static gboolean
gsk_path_dash_foreach (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathDash *self = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_dash_setup (self);
break;
case GSK_PATH_CLOSE:
if (self->may_close)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return self->func (GSK_PATH_CLOSE, pts, 2, 0, self->user_data);
}
else
op = GSK_PATH_LINE;
G_GNUC_FALLTHROUGH;
case GSK_PATH_LINE:
case GSK_PATH_CURVE:
case GSK_PATH_CONIC:
gsk_curve_init_foreach (&self->curve, op, pts, n_pts, weight);
if (!gsk_curve_decompose (&self->curve, self->tolerance, gsk_path_dash_add_line_segment, self))
return FALSE;
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
gboolean
gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
GskPathDash self = {
.offset = 0,
.dash = stroke->dash,
.n_dash = stroke->n_dash,
.dash_length = stroke->dash_length,
.dash_offset = stroke->dash_offset,
.tolerance = tolerance,
.func = func,
.user_data = user_data
};
gboolean is_closed = gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
self.first_dash_behavior = is_closed ? SKIP : NORMAL;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self))
return FALSE;
if (is_closed)
{
if (self.first_dash_behavior == NORMAL)
self.first_dash_behavior = ONLY;
else
self.first_dash_behavior = NORMAL;
self.needs_move_to = !self.on;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self) &&
self.first_dash_behavior != DONE)
return FALSE;
}
return TRUE;
}
gboolean
gsk_path_dash (GskPath *path,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
gsize i;
/* Dashing disabled, no need to do any work */
if (stroke->dash_length <= 0)
return gsk_path_foreach (path, -1, func, user_data);
for (i = 0; i < gsk_path_get_n_contours (path); i++)
{
if (!gsk_contour_dash (gsk_path_get_contour (path, i), stroke, tolerance, func, user_data))
return FALSE;
}
return TRUE;
}

50
gsk/gskpathdashprivate.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_DASH_PRIVATE_H__
#define __GSK_PATH_DASH_PRIVATE_H__
#include <gsk/gskpath.h>
G_BEGIN_DECLS
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_dash (GskPath *path,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
#ifdef GTK_COMPILATION
#include "gskcontourprivate.h"
gboolean gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
#endif /* GTK_COMPILATION */
G_END_DECLS
#endif /* __GSK_PATH_DASH_PRIVATE_H__ */

628
gsk/gskpathmeasure.c Normal file
View File

@@ -0,0 +1,628 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathmeasure.h"
#include "gskpathbuilder.h"
#include "gskpathprivate.h"
/**
* SECTION:gskpathmeasure
* @Title: PathMeasure
* @Short_description: Measuring operations on paths
* @See_also: #GskPath
*
* #GskPathMeasure is an object that allows measuring operations on #GskPaths.
* These operations are useful when implementing animations.
*/
typedef struct _GskContourMeasure GskContourMeasure;
struct _GskContourMeasure
{
float length;
gpointer contour_data;
};
struct _GskPathMeasure
{
/*< private >*/
guint ref_count;
GskPath *path;
float tolerance;
gsize first;
gsize last;
float length;
gsize n_contours;
GskContourMeasure measures[];
};
/**
* GskPathMeasure:
*
* A #GskPathMeasure struct is a reference counted struct
* and should be treated as opaque.
*/
G_DEFINE_BOXED_TYPE (GskPathMeasure, gsk_path_measure,
gsk_path_measure_ref,
gsk_path_measure_unref)
/**
* gsk_path_measure_new:
* @path: the path to measure
*
* Creates a measure object for the given @path.
*
* Returns: a new #GskPathMeasure representing @path
**/
GskPathMeasure *
gsk_path_measure_new (GskPath *path)
{
return gsk_path_measure_new_with_tolerance (path, GSK_PATH_TOLERANCE_DEFAULT);
}
/**
* gsk_path_measure_new_with_tolerance:
* @path: the path to measure
* @tolerance: the tolerance for measuring operations
*
* Creates a measure object for the given @path and @tolerance.
*
* Returns: a new #GskPathMeasure representing @path
**/
GskPathMeasure *
gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance)
{
GskPathMeasure *self;
gsize i, n_contours;
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (tolerance > 0, NULL);
n_contours = gsk_path_get_n_contours (path);
self = g_malloc0 (sizeof (GskPathMeasure) + n_contours * sizeof (GskContourMeasure));
self->ref_count = 1;
self->path = gsk_path_ref (path);
self->tolerance = tolerance;
self->n_contours = n_contours;
self->first = 0;
self->last = n_contours;
for (i = 0; i < n_contours; i++)
{
self->measures[i].contour_data = gsk_contour_init_measure (gsk_path_get_contour (path, i),
self->tolerance,
&self->measures[i].length);
self->length += self->measures[i].length;
}
return self;
}
/**
* gsk_path_measure_ref:
* @self: a #GskPathMeasure
*
* Increases the reference count of a #GskPathMeasure by one.
*
* Returns: the passed in #GskPathMeasure.
**/
GskPathMeasure *
gsk_path_measure_ref (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, NULL);
self->ref_count++;
return self;
}
/**
* gsk_path_measure_unref:
* @self: a #GskPathMeasure
*
* Decreases the reference count of a #GskPathMeasure by one.
* If the resulting reference count is zero, frees the path_measure.
**/
void
gsk_path_measure_unref (GskPathMeasure *self)
{
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
self->ref_count--;
if (self->ref_count > 0)
return;
for (i = 0; i < self->n_contours; i++)
{
gsk_contour_free_measure (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data);
}
gsk_path_unref (self->path);
g_free (self);
}
/**
* gsk_path_measure_get_path:
* @self: a #GskPathMeasure
*
* Returns the path that the measure was created for.
*
* Returns: (transfer none): the path of @self
*/
GskPath *
gsk_path_measure_get_path (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->path;
}
/**
* gsk_path_measure_get_tolerance:
* @self: a #GskPathMeasure
*
* Returns the tolerance that the measure was created with.
*
* Returns: the tolerance of @self
*/
float
gsk_path_measure_get_tolerance (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0.f);
return self->tolerance;
}
/**
* gsk_path_measure_get_n_contours:
* @self: a #GskPathMeasure
*
* Returns the number of contours in the path being measured.
*
* The returned value is independent of whether @self if restricted
* or not.
*
* Returns: The number of contours
**/
gsize
gsk_path_measure_get_n_contours (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->n_contours;
}
/**
* gsk_path_measure_restrict_to_contour:
* @self: a #GskPathMeasure
* @contour: contour to restrict to or (gsize) -1 for using the
* whole path
*
* Restricts all functions on the path to just the given @contour.
*
* If @contour >= gsk_path_measure_get_n_contours() - so in
* particular when it is set to -1 - the whole path will be used.
**/
void
gsk_path_measure_restrict_to_contour (GskPathMeasure *self,
gsize contour)
{
if (contour >= self->n_contours)
{
/* use the whole path */
self->first = 0;
self->last = self->n_contours;
}
else
{
/* use just one contour */
self->first = contour;
self->last = contour + 1;
}
self->length = 0;
for (gsize i = self->first; i < self->last; i++)
{
self->length += self->measures[i].length;
}
}
/**
* gsk_path_measure_get_length:
* @self: a #GskPathMeasure
*
* Gets the length of the path being measured.
*
* The length is cached, so this function does not do any work.
*
* Returns: The length of the path measured by @self
**/
float
gsk_path_measure_get_length (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->length;
}
/**
* gsk_path_measure_is_closed:
* @self: a #GskPathMeasure
*
* Returns if the path being measured represents a single closed
* contour.
*
* Returns: %TRUE if the current path is closed
**/
gboolean
gsk_path_measure_is_closed (GskPathMeasure *self)
{
const GskContour *contour;
g_return_val_if_fail (self != NULL, FALSE);
/* XXX: is the empty path closed? Currently it's not */
if (self->last - self->first != 1)
return FALSE;
contour = gsk_path_get_contour (self->path, self->first);
return gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
}
static float
gsk_path_measure_clamp_distance (GskPathMeasure *self,
float distance)
{
if (isnan (distance))
return 0;
return CLAMP (distance, 0, self->length);
}
/**
* gsk_path_measure_get_point:
* @self: a #GskPathMeasure
* @distance: distance into the path
* @pos: (out caller-allocates) (optional): The coordinates
* of the position at @distance
* @tangent: (out caller-allocates) (optional): The tangent
* to the position at @distance
*
* Calculates the coordinates and tangent of the point @distance
* units into the path. The value will be clamped to the length
* of the path.
*
* If the point is a discontinuous edge in the path, the returned
* point and tangent will describe the line starting at that point
* going forward.
*
* If @self describes an empty path, the returned point will be
* set to `(0, 0)` and the tangent will be the x axis or `(1, 0)`.
**/
void
gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent)
{
gsize i;
g_return_if_fail (self != NULL);
if (pos == NULL && tangent == NULL)
return;
distance = gsk_path_measure_clamp_distance (self, distance);
for (i = self->first; i < self->last; i++)
{
if (distance < self->measures[i].length)
break;
distance -= self->measures[i].length;
}
/* weird corner cases */
if (i == self->last)
{
/* the empty path goes here */
if (self->first == self->last)
{
if (pos)
graphene_point_init (pos, 0.f, 0.f);
if (tangent)
graphene_vec2_init (tangent, 1.f, 0.f);
return;
}
/* rounding errors can make this happen */
i = self->last - 1;
distance = self->measures[i].length;
}
gsk_contour_get_point (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
distance,
pos,
tangent);
}
/**
* gsk_path_measure_get_closest_point:
* @self: a #GskPathMeasure
* @point: the point to fond the closest point to
* @out_pos: (out caller-allocates) (optional): return location
* for the closest point
*
* Gets the point on the path that is closest to @point.
*
* If the path being measured is empty, return 0 and set
* @out_pos to (0, 0).
*
* This is a simpler and slower version of
* gsk_path_measure_get_closest_point_full(). Use that one if you
* need more control.
*
* Returns: The offset into the path of the closest point
**/
float
gsk_path_measure_get_closest_point (GskPathMeasure *self,
const graphene_point_t *point,
graphene_point_t *out_pos)
{
float result;
g_return_val_if_fail (self != NULL, 0.0f);
if (gsk_path_measure_get_closest_point_full (self,
point,
INFINITY,
&result,
out_pos,
NULL,
NULL))
return result;
if (out_pos)
*out_pos = GRAPHENE_POINT_INIT (0, 0);
return 0;
}
/**
* gsk_path_measure_get_closest_point_full:
* @self: a #GskPathMeasure
* @point: the point to fond the closest point to
* @threshold: The maximum allowed distance between the path and @point.
* Use INFINITY to look for any point.
* @out_distance: (out caller-allocates) (optional): The
* distance between the found closest point on the path and the given
* @point.
* @out_pos: (out caller-allocates) (optional): return location
* for the closest point
* @out_offset: (out caller-allocates) (optional): The offset into
* the path of the found point
* @out_tangent: (out caller-allocates) (optional): return location for
* the tangent at the closest point
*
* Gets the point on the path that is closest to @point. If no point on
* path is closer to @point than @threshold, return %FALSE.
*
* Returns: %TRUE if a point was found, %FALSE otherwise.
**/
gboolean
gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent)
{
gboolean result;
gsize i;
float distance, length;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (point != NULL, FALSE);
result = FALSE;
length = 0;
for (i = self->first; i < self->last; i++)
{
if (gsk_contour_get_closest_point (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
self->tolerance,
point,
threshold,
&distance,
out_pos,
out_offset,
out_tangent))
{
result = TRUE;
if (out_offset)
*out_offset += length;
if (distance < self->tolerance)
break;
threshold = distance - self->tolerance;
}
length += self->measures[i].length;
}
if (result && out_distance)
*out_distance = distance;
return result;
}
/**
* gsk_path_measure_in_fill:
* @self: a #GskPathMeasure
* @point: the point to test
* @fill_rule: the fill rule to follow
*
* Returns whether the given point is inside the area that would be
* affected if the path of @self was filled according to @fill_rule.
*
* Returns: %TRUE if @point is inside
*/
gboolean
gsk_path_measure_in_fill (GskPathMeasure *self,
const graphene_point_t *point,
GskFillRule fill_rule)
{
int winding = 0;
gboolean on_edge = FALSE;
int i;
for (i = self->first; i < self->last; i++)
{
winding += gsk_contour_get_winding (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
point,
&on_edge);
if (on_edge)
return TRUE;
}
switch (fill_rule)
{
case GSK_FILL_RULE_EVEN_ODD:
return winding & 1;
case GSK_FILL_RULE_WINDING:
return winding != 0;
default:
g_assert_not_reached ();
}
}
static void
gsk_path_builder_add_segment_chunk (GskPathBuilder *self,
GskPathMeasure *measure,
gboolean emit_move_to,
float start,
float end)
{
g_assert (start < end);
for (gsize i = measure->first; i < measure->last; i++)
{
if (measure->measures[i].length < start)
{
start -= measure->measures[i].length;
end -= measure->measures[i].length;
}
else if (start > 0 || end < measure->measures[i].length)
{
float len = MIN (end, measure->measures[i].length);
gsk_contour_add_segment (gsk_path_get_contour (measure->path, i),
self,
measure->measures[i].contour_data,
emit_move_to,
start,
len);
end -= len;
start = 0;
if (end <= 0)
break;
}
else
{
end -= measure->measures[i].length;
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (measure->path, i)));
}
emit_move_to = TRUE;
}
}
/**
* gsk_path_builder_add_segment:
* @self: a #GskPathBuilder
* @measure: the #GskPathMeasure to take the segment to
* @start: start distance into the path
* @end: end distance into the path
*
* Adds to @self the segment of @measure from @start to @end.
*
* The distances are given relative to the length of @measure's path,
* from 0 for the beginning of the path to
* gsk_path_measure_get_length() for the end of the path. The values
* will be clamped to that range.
*
* If @start >= @end after clamping, the path will first add the segment
* from @start to the end of the path, and then add the segment from
* the beginning to @end. If the path is closed, these segments will
* be connected.
**/
void
gsk_path_builder_add_segment (GskPathBuilder *self,
GskPathMeasure *measure,
float start,
float end)
{
g_return_if_fail (self != NULL);
g_return_if_fail (measure != NULL);
start = gsk_path_measure_clamp_distance (measure, start);
end = gsk_path_measure_clamp_distance (measure, end);
if (start < end)
{
gsk_path_builder_add_segment_chunk (self, measure, TRUE, start, end);
}
else
{
/* If the path is closed, we can connect the 2 subpaths. */
gboolean closed = gsk_path_measure_is_closed (measure);
gboolean need_move_to = !closed;
if (start < measure->length)
gsk_path_builder_add_segment_chunk (self, measure,
TRUE,
start, measure->length);
else
need_move_to = TRUE;
if (end > 0)
gsk_path_builder_add_segment_chunk (self, measure,
need_move_to,
0, end);
if (start == end && closed)
gsk_path_builder_close (self);
}
}

88
gsk/gskpathmeasure.h Normal file
View File

@@ -0,0 +1,88 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_MEASURE_H__
#define __GSK_PATH_MEASURE_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gskpath.h>
G_BEGIN_DECLS
#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_measure_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_new (GskPath *path);
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance);
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_unref (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
gsize gsk_path_measure_get_n_contours (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_restrict_to_contour (GskPathMeasure *self,
gsize contour);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_length (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_is_closed (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_closest_point (GskPathMeasure *self,
const graphene_point_t *point,
graphene_point_t *out_pos);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_in_fill (GskPathMeasure *self,
const graphene_point_t *point,
GskFillRule fill_rule);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)
G_END_DECLS
#endif /* __GSK_PATH_MEASURE_H__ */

179
gsk/gskpathopprivate.h Normal file
View File

@@ -0,0 +1,179 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATHOP_PRIVATE_H__
#define __GSK_PATHOP_PRIVATE_H__
#include <gsk/gskpath.h>
#include <gsk/gskpathbuilder.h>
G_BEGIN_DECLS
typedef gpointer gskpathop;
static inline
gskpathop gsk_pathop_encode (GskPathOperation op,
const graphene_point_t *pts);
static inline
const graphene_point_t *gsk_pathop_points (gskpathop pop);
static inline
GskPathOperation gsk_pathop_op (gskpathop pop);
static inline
gboolean gsk_pathop_foreach (gskpathop pop,
GskPathForeachFunc func,
gpointer user_data);
/* included inline so tests can use them */
static inline
void gsk_path_builder_pathop_to (GskPathBuilder *builder,
gskpathop op);
static inline
void gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
gskpathop op);
/* IMPLEMENTATION */
#define GSK_PATHOP_OPERATION_MASK (0x7)
static inline gskpathop
gsk_pathop_encode (GskPathOperation op,
const graphene_point_t *pts)
{
/* g_assert (op & GSK_PATHOP_OPERATION_MASK == op); */
g_assert ((GPOINTER_TO_SIZE (pts) & GSK_PATHOP_OPERATION_MASK) == 0);
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pts) | op);
}
static inline const graphene_point_t *
gsk_pathop_points (gskpathop pop)
{
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pop) & ~GSK_PATHOP_OPERATION_MASK);
}
static inline
GskPathOperation gsk_pathop_op (gskpathop pop)
{
return GPOINTER_TO_SIZE (pop) & GSK_PATHOP_OPERATION_MASK;
}
static inline gboolean
gsk_pathop_foreach (gskpathop pop,
GskPathForeachFunc func,
gpointer user_data)
{
switch (gsk_pathop_op (pop))
{
case GSK_PATH_MOVE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, 0, user_data);
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data);
case GSK_PATH_CURVE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, 0, user_data);
case GSK_PATH_CONIC:
{
const graphene_point_t *pts = gsk_pathop_points (pop);
return func (gsk_pathop_op (pop), (graphene_point_t[3]) { pts[0], pts[1], pts[3] }, 3, pts[2].x, user_data);
}
default:
g_assert_not_reached ();
return TRUE;
}
}
static inline void
gsk_path_builder_pathop_to (GskPathBuilder *builder,
gskpathop op)
{
const graphene_point_t *pts = gsk_pathop_points (op);
switch (gsk_pathop_op (op))
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_CURVE:
gsk_path_builder_curve_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[3].x, pts[3].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
}
}
static inline void
gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
gskpathop op)
{
const graphene_point_t *pts = gsk_pathop_points (op);
switch (gsk_pathop_op (op))
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_line_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_CURVE:
gsk_path_builder_curve_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
}
}
G_END_DECLS
#endif /* __GSK_PATHOP_PRIVATE_H__ */

61
gsk/gskpathprivate.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_PRIVATE_H__
#define __GSK_PATH_PRIVATE_H__
#include "gskpath.h"
#include "gskcontourprivate.h"
#include "gskpathopprivate.h"
G_BEGIN_DECLS
/* Same as Skia, so looks like a good value. ¯\_(ツ)_/¯ */
#define GSK_PATH_TOLERANCE_DEFAULT (0.5)
GskPath * gsk_path_new_from_contours (const GSList *contours);
gsize gsk_path_get_n_contours (GskPath *path);
const GskContour * gsk_path_get_contour (GskPath *path,
gsize i);
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
GskPathForeachFlags flags,
double tolerance,
GskPathForeachFunc func,
gpointer user_data);
void gsk_path_builder_add_contour (GskPathBuilder *builder,
GskContour *contour);
void gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
float rx,
float ry,
float x_axis_rotation,
gboolean large_arc,
gboolean positive_sweep,
float x,
float y);
G_END_DECLS
#endif /* __GSK_PATH_PRIVATE_H__ */

View File

@@ -158,6 +158,8 @@ GskRenderNode * gsk_render_node_deserialize (GBytes
#define GSK_TYPE_REPEAT_NODE (gsk_repeat_node_get_type())
#define GSK_TYPE_CLIP_NODE (gsk_clip_node_get_type())
#define GSK_TYPE_ROUNDED_CLIP_NODE (gsk_rounded_clip_node_get_type())
#define GSK_TYPE_FILL_NODE (gsk_fill_node_get_type())
#define GSK_TYPE_STROKE_NODE (gsk_stroke_node_get_type())
#define GSK_TYPE_SHADOW_NODE (gsk_shadow_node_get_type())
#define GSK_TYPE_BLEND_NODE (gsk_blend_node_get_type())
#define GSK_TYPE_CROSS_FADE_NODE (gsk_cross_fade_node_get_type())
@@ -186,6 +188,8 @@ typedef struct _GskColorMatrixNode GskColorMatrixNode;
typedef struct _GskRepeatNode GskRepeatNode;
typedef struct _GskClipNode GskClipNode;
typedef struct _GskRoundedClipNode GskRoundedClipNode;
typedef struct _GskFillNode GskFillNode;
typedef struct _GskStrokeNode GskStrokeNode;
typedef struct _GskShadowNode GskShadowNode;
typedef struct _GskBlendNode GskBlendNode;
typedef struct _GskCrossFadeNode GskCrossFadeNode;
@@ -459,6 +463,32 @@ GskRenderNode * gsk_rounded_clip_node_get_child (const GskRender
GDK_AVAILABLE_IN_ALL
const GskRoundedRect * gsk_rounded_clip_node_get_clip (const GskRenderNode *node) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
GType gsk_fill_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_fill_node_new (GskRenderNode *child,
GskPath *path,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_fill_node_get_child (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_fill_node_get_path (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskFillRule gsk_fill_node_get_fill_rule (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GType gsk_stroke_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_stroke_node_new (GskRenderNode *child,
GskPath *path,
const GskStroke *stroke);
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_stroke_node_get_child (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_stroke_node_get_path (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
const GskStroke * gsk_stroke_node_get_stroke (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GType gsk_shadow_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL

View File

@@ -25,8 +25,11 @@
#include "gskdebugprivate.h"
#include "gskdiffprivate.h"
#include "gl/gskglrenderer.h"
#include "gskpath.h"
#include "gskrectprivate.h"
#include "gskrendererprivate.h"
#include "gskroundedrectprivate.h"
#include "gskstrokeprivate.h"
#include "gsktransformprivate.h"
#include "gdk/gdktextureprivate.h"
@@ -4366,6 +4369,380 @@ gsk_rounded_clip_node_get_clip (const GskRenderNode *node)
return &self->clip;
}
/* }}} */
/* {{{ GSK_FILL_NODE */
struct _GskFillNode
{
GskRenderNode render_node;
GskRenderNode *child;
GskPath *path;
GskFillRule fill_rule;
};
static void
gsk_fill_node_finalize (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_FILL_NODE));
gsk_render_node_unref (self->child);
gsk_path_unref (self->path);
parent_class->finalize (node);
}
static void
gsk_fill_node_draw (GskRenderNode *node,
cairo_t *cr)
{
GskFillNode *self = (GskFillNode *) node;
cairo_save (cr);
switch (self->fill_rule)
{
case GSK_FILL_RULE_WINDING:
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
break;
case GSK_FILL_RULE_EVEN_ODD:
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
break;
default:
g_assert_not_reached ();
break;
}
gsk_path_to_cairo (self->path, cr);
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
{
gdk_cairo_set_source_rgba (cr, gsk_color_node_get_color (self->child));
cairo_fill (cr);
}
else
{
cairo_clip (cr);
gsk_render_node_draw (self->child, cr);
}
cairo_restore (cr);
}
static void
gsk_fill_node_diff (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region)
{
GskFillNode *self1 = (GskFillNode *) node1;
GskFillNode *self2 = (GskFillNode *) node2;
if (self1->path == self2->path)
{
cairo_region_t *sub;
cairo_rectangle_int_t clip_rect;
graphene_rect_t rect;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
graphene_rect_union (&node1->bounds, &node2->bounds, &rect);
rectangle_init_from_graphene (&clip_rect, &rect);
cairo_region_intersect_rectangle (sub, &clip_rect);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
}
else
{
gsk_render_node_diff_impossible (node1, node2, region);
}
}
static void
gsk_fill_node_class_init (gpointer g_class,
gpointer class_data)
{
GskRenderNodeClass *node_class = g_class;
node_class->node_type = GSK_FILL_NODE;
node_class->finalize = gsk_fill_node_finalize;
node_class->draw = gsk_fill_node_draw;
node_class->diff = gsk_fill_node_diff;
}
/**
* gsk_fill_node_new:
* @child: The node to fill the area with
* @path: The path describing the area to fill
* @fill_rule: The fill rule to use
*
* Creates a #GskRenderNode that will fill the @child in the area
* given by @path and @fill_rule.
*
* Returns: (transfer none) (type GskFillNode): A new #GskRenderNode
*/
GskRenderNode *
gsk_fill_node_new (GskRenderNode *child,
GskPath *path,
GskFillRule fill_rule)
{
GskFillNode *self;
GskRenderNode *node;
graphene_rect_t path_bounds;
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
g_return_val_if_fail (path != NULL, NULL);
self = gsk_render_node_alloc (GSK_FILL_NODE);
node = (GskRenderNode *) self;
self->child = gsk_render_node_ref (child);
self->path = gsk_path_ref (path);
self->fill_rule = fill_rule;
if (gsk_path_get_bounds (path, &path_bounds))
graphene_rect_intersection (&path_bounds, &child->bounds, &node->bounds);
else
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
return node;
}
/**
* gsk_fill_node_get_child:
* @node: (type GskFillNode): a fill #GskRenderNode
*
* Gets the child node that is getting drawn by the given @node.
*
* Returns: (transfer none): The child that is getting drawn
**/
GskRenderNode *
gsk_fill_node_get_child (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
return self->child;
}
/**
* gsk_fill_node_get_path:
* @node: (type GskFillNode): a fill #GskRenderNode
*
* Retrievs the path used to describe the area filled with the contents of
* the @node.
*
* Returns: (transfer none): a #GskPath
*/
GskPath *
gsk_fill_node_get_path (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
return self->path;
}
/**
* gsk_fill_node_get_fill_rule:
* @node: (type GskFillNode): a fill #GskRenderNode
*
* Retrievs the fill rule used to determine how the path is filled.
*
* Returns: a #GskFillRule
*/
GskFillRule
gsk_fill_node_get_fill_rule (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), GSK_FILL_RULE_WINDING);
return self->fill_rule;
}
/* }}} */
/* {{{ GSK_STROKE_NODE */
struct _GskStrokeNode
{
GskRenderNode render_node;
GskRenderNode *child;
GskPath *path;
GskStroke stroke;
};
static void
gsk_stroke_node_finalize (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_STROKE_NODE));
gsk_render_node_unref (self->child);
gsk_path_unref (self->path);
gsk_stroke_clear (&self->stroke);
parent_class->finalize (node);
}
static void
gsk_stroke_node_draw (GskRenderNode *node,
cairo_t *cr)
{
GskStrokeNode *self = (GskStrokeNode *) node;
cairo_save (cr);
gsk_cairo_rectangle (cr, &self->child->bounds);
cairo_clip (cr);
cairo_push_group (cr);
gsk_render_node_draw (self->child, cr);
cairo_pop_group_to_source (cr);
gsk_stroke_to_cairo (&self->stroke, cr);
gsk_path_to_cairo (self->path, cr);
cairo_stroke (cr);
cairo_restore (cr);
}
static void
gsk_stroke_node_diff (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region)
{
GskStrokeNode *self1 = (GskStrokeNode *) node1;
GskStrokeNode *self2 = (GskStrokeNode *) node2;
if (self1->path == self2->path &&
gsk_stroke_equal (&self1->stroke, &self2->stroke))
{
cairo_region_t *sub;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
}
else
{
gsk_render_node_diff_impossible (node1, node2, region);
}
}
static void
gsk_stroke_node_class_init (gpointer g_class,
gpointer class_data)
{
GskRenderNodeClass *node_class = g_class;
node_class->node_type = GSK_STROKE_NODE;
node_class->finalize = gsk_stroke_node_finalize;
node_class->draw = gsk_stroke_node_draw;
node_class->diff = gsk_stroke_node_diff;
}
/**
* gsk_stroke_node_new:
* @child: The node to stroke the area with
* @path: (transfer none): The path describing the area to stroke
* @stroke: (transfer none): The stroke attributes to use
*
* Creates a #GskRenderNode that will stroke the @child along the given
* @path using the attributes defined in @stroke.
*
* Returns: (transfer none) (type GskStrokeNode): A new #GskRenderNode
*/
GskRenderNode *
gsk_stroke_node_new (GskRenderNode *child,
GskPath *path,
const GskStroke *stroke)
{
GskStrokeNode *self;
GskRenderNode *node;
graphene_rect_t path_bounds;
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (stroke != NULL, NULL);
self = gsk_render_node_alloc (GSK_STROKE_NODE);
node = (GskRenderNode *) self;
self->child = gsk_render_node_ref (child);
self->path = gsk_path_ref (path);
gsk_stroke_init_copy (&self->stroke, stroke);
if (gsk_path_get_stroke_bounds (path, stroke, &path_bounds))
graphene_rect_intersection (&path_bounds, &child->bounds, &node->bounds);
else
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
return node;
}
/**
* gsk_stroke_node_get_child:
* @node: (type GskStrokeNode): a stroke #GskRenderNode
*
* Gets the child node that is getting drawn by the given @node.
*
* Returns: (transfer none): The child that is getting drawn
**/
GskRenderNode *
gsk_stroke_node_get_child (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return self->child;
}
/**
* gsk_stroke_node_get_path:
* @node: (type GskStrokeNode): a stroke #GskRenderNode
*
* Retrievs the path that will be stroked with the contents of
* the @node.
*
* Returns: (transfer none): a #GskPath
*/
GskPath *
gsk_stroke_node_get_path (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return self->path;
}
/**
* gsk_stroke_node_get_stroke:
* @node: (type GskStrokeNode): a stroke #GskRenderNode
*
* Retrievs the stroke attributes used in this @node.
*
* Returns: a #GskStroke
*/
const GskStroke *
gsk_stroke_node_get_stroke (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return &self->stroke;
}
/* }}} */
/* {{{ GSK_SHADOW_NODE */
@@ -6259,6 +6636,8 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_matrix_node, GSK_COLOR_MATRIX_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeat_node, GSK_REPEAT_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_clip_node, GSK_CLIP_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_rounded_clip_node, GSK_ROUNDED_CLIP_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_fill_node, GSK_FILL_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_stroke_node, GSK_STROKE_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_shadow_node, GSK_SHADOW_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE)
@@ -6407,6 +6786,16 @@ gsk_render_node_init_types_once (void)
sizeof (GskDebugNode),
gsk_debug_node_class_init);
gsk_render_node_types[GSK_DEBUG_NODE] = node_type;
node_type = gsk_render_node_type_register_static (I_("GskFillNode"),
sizeof (GskFillNode),
gsk_fill_node_class_init);
gsk_render_node_types[GSK_FILL_NODE] = node_type;
node_type = gsk_render_node_type_register_static (I_("GskStrokeNode"),
sizeof (GskStrokeNode),
gsk_stroke_node_class_init);
gsk_render_node_types[GSK_STROKE_NODE] = node_type;
}
static void

View File

@@ -23,9 +23,13 @@
#include "gskrendernodeparserprivate.h"
#include "gskpath.h"
#include "gskpathbuilder.h"
#include "gskroundedrectprivate.h"
#include "gskrendernodeprivate.h"
#include "gskstroke.h"
#include "gsktransformprivate.h"
#include "gskenumtypes.h"
#include "gdk/gdkrgbaprivate.h"
#include "gdk/gdktextureprivate.h"
@@ -1174,6 +1178,26 @@ create_default_render_node (void)
return gsk_color_node_new (&GDK_RGBA("FF00CC"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
}
static GskPath *
create_default_path (void)
{
GskPathBuilder *builder;
guint i;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 25, 0);
for (i = 1; i < 5; i++)
{
gsk_path_builder_line_to (builder,
sin (i * G_PI * 0.8) * 25 + 25,
-cos (i * G_PI * 0.8) * 25 + 25);
}
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskRenderNode *
parse_color_node (GtkCssParser *parser,
Context *context)
@@ -2097,6 +2121,223 @@ parse_rounded_clip_node (GtkCssParser *parser,
return result;
}
static gboolean
parse_path (GtkCssParser *parser,
Context *context,
gpointer out_path)
{
GskPath *path;
char *str = NULL;
if (!parse_string (parser, context, &str))
return FALSE;
path = gsk_path_parse (str);
g_free (str);
if (path == NULL)
{
gtk_css_parser_error_value (parser, "Invalid path");
return FALSE;
}
*((GskPath **) out_path) = path;
return TRUE;
}
static void
clear_path (gpointer inout_path)
{
g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
}
static gboolean
parse_dash (GtkCssParser *parser,
Context *context,
gpointer out_dash)
{
GArray *dash;
double d;
/* because CSS does this, too */
if (gtk_css_parser_try_ident (parser, "none"))
{
*((GArray **) out_dash) = NULL;
return TRUE;
}
dash = g_array_new (FALSE, FALSE, sizeof (float));
while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
{
if (!gtk_css_parser_consume_number (parser, &d))
{
g_array_free (dash, TRUE);
return FALSE;
}
g_array_append_vals (dash, (float[1]) { d }, 1);
}
if (dash->len == 0)
{
gtk_css_parser_error_syntax (parser, "Expected a positive number");
g_array_unref (dash);
return FALSE;
}
*((GArray **) out_dash) = dash;
return TRUE;
}
static void
clear_dash (gpointer inout_array)
{
g_clear_pointer ((GArray **) inout_array, g_array_unref);
}
static gboolean
parse_enum (GtkCssParser *parser,
GType type,
gpointer out_value)
{
GEnumClass *class;
GEnumValue *v;
char *enum_name;
enum_name = gtk_css_parser_consume_ident (parser);
if (enum_name == NULL)
return FALSE;
class = g_type_class_ref (type);
v = g_enum_get_value_by_nick (class, enum_name);
if (v == NULL)
{
gtk_css_parser_error_value (parser, "Unknown value \"%s\" for enum \"%s\"",
enum_name, g_type_name (type));
g_free (enum_name);
g_type_class_unref (class);
return FALSE;
}
*(int*)out_value = v->value;
g_free (enum_name);
g_type_class_unref (class);
gtk_css_parser_consume_token (parser);
return TRUE;
}
static gboolean
parse_fill_rule (GtkCssParser *parser,
Context *context,
gpointer out_rule)
{
return parse_enum (parser, GSK_TYPE_FILL_RULE, out_rule);
}
static GskRenderNode *
parse_fill_node (GtkCssParser *parser,
Context *context)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
int rule = GSK_FILL_RULE_WINDING;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "fill-rule", parse_fill_rule, NULL, &rule },
};
GskRenderNode *result;
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
result = gsk_fill_node_new (child, path, rule);
gsk_path_unref (path);
gsk_render_node_unref (child);
return result;
}
static gboolean
parse_line_cap (GtkCssParser *parser,
Context *context,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_CAP, out);
}
static gboolean
parse_line_join (GtkCssParser *parser,
Context *context,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_JOIN, out);
}
static GskRenderNode *
parse_stroke_node (GtkCssParser *parser,
Context *context)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
double line_width = 1.0;
int line_cap = GSK_LINE_CAP_BUTT;
int line_join = GSK_LINE_JOIN_MITER;
double miter_limit = 4.0;
GArray *dash = NULL;
double dash_offset = 0.0;
GskStroke *stroke;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "line-width", parse_positive_double, NULL, &line_width },
{ "line-cap", parse_line_cap, NULL, &line_cap },
{ "line-join", parse_line_join, NULL, &line_join },
{ "miter-limit", parse_positive_double, NULL, &miter_limit },
{ "dash", parse_dash, clear_dash, &dash },
{ "dash-offset", parse_double, NULL, &dash_offset},
};
GskRenderNode *result;
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
stroke = gsk_stroke_new (line_width);
gsk_stroke_set_line_cap (stroke, line_cap);
gsk_stroke_set_line_join (stroke, line_join);
gsk_stroke_set_miter_limit (stroke, miter_limit);
if (dash)
{
gsk_stroke_set_dash (stroke, (float *) dash->data, dash->len);
g_array_free (dash, TRUE);
}
gsk_stroke_set_dash_offset (stroke, dash_offset);
result = gsk_stroke_node_new (child, path, stroke);
gsk_path_unref (path);
gsk_stroke_free (stroke);
gsk_render_node_unref (child);
return result;
}
static GskRenderNode *
parse_shadow_node (GtkCssParser *parser,
Context *context)
@@ -2179,6 +2420,8 @@ parse_node (GtkCssParser *parser,
{ "repeating-linear-gradient", parse_repeating_linear_gradient_node },
{ "repeating-radial-gradient", parse_repeating_radial_gradient_node },
{ "rounded-clip", parse_rounded_clip_node },
{ "fill", parse_fill_node },
{ "stroke", parse_stroke_node },
{ "shadow", parse_shadow_node },
{ "text", parse_text_node },
{ "texture", parse_texture_node },
@@ -2433,6 +2676,14 @@ printer_init_duplicates_for_node (Printer *printer,
printer_init_duplicates_for_node (printer, gsk_debug_node_get_child (node));
break;
case GSK_FILL_NODE:
printer_init_duplicates_for_node (printer, gsk_fill_node_get_child (node));
break;
case GSK_STROKE_NODE:
printer_init_duplicates_for_node (printer, gsk_stroke_node_get_child (node));
break;
case GSK_BLEND_NODE:
printer_init_duplicates_for_node (printer, gsk_blend_node_get_bottom_child (node));
printer_init_duplicates_for_node (printer, gsk_blend_node_get_top_child (node));
@@ -2658,7 +2909,7 @@ append_float_param (Printer *p,
float value,
float default_value)
{
/* Don't approximate-compare here, better be topo verbose */
/* Don't approximate-compare here, better be too verbose */
if (value == default_value)
return;
@@ -2833,8 +3084,11 @@ append_escaping_newlines (GString *str,
len = strcspn (string, "\n");
g_string_append_len (str, string, len);
string += len;
g_string_append (str, "\\\n");
string++;
if (*string)
{
g_string_append (str, "\\\n");
string++;
}
} while (*string);
}
@@ -3035,6 +3289,83 @@ gsk_text_node_serialize_glyphs (GskRenderNode *node,
pango_glyph_string_free (ascii);
}
static const char *
enum_to_nick (GType type,
int value)
{
GEnumClass *class;
GEnumValue *v;
class = g_type_class_ref (type);
v = g_enum_get_value (class, value);
g_type_class_unref (class);
return v->value_nick;
}
static void
append_enum_param (Printer *p,
const char *param_name,
GType type,
int value)
{
_indent (p);
g_string_append_printf (p->str, "%s: ", param_name);
g_string_append (p->str, enum_to_nick (type, value));
g_string_append_c (p->str, ';');
g_string_append_c (p->str, '\n');
}
static void
append_path_param (Printer *p,
const char *param_name,
GskPath *path)
{
char *str, *s;
_indent (p);
g_string_append (p->str, "path: \"\\\n");
str = gsk_path_to_string (path);
/* Put each command on a new line */
for (s = str; *s; s++)
{
if (*s == ' ' &&
(s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L' || s[1] == 'O'))
*s = '\n';
}
append_escaping_newlines (p->str, str);
g_string_append (p->str, "\";\n");
g_free (str);
}
static void
append_dash_param (Printer *p,
const char *param_name,
const float *dash,
gsize n_dash)
{
_indent (p);
g_string_append (p->str, "dash: ");
if (n_dash == 0)
{
g_string_append (p->str, "none");
}
else
{
gsize i;
string_append_double (p->str, dash[0]);
for (i = 1; i < n_dash; i++)
{
g_string_append_c (p->str, ' ');
string_append_double (p->str, dash[i]);
}
}
g_string_append (p->str, ";\n");
}
static void
render_node_print (Printer *p,
GskRenderNode *node)
@@ -3204,6 +3535,42 @@ render_node_print (Printer *p,
append_rounded_rect_param (p, "clip", gsk_rounded_clip_node_get_clip (node));
append_node_param (p, "child", gsk_rounded_clip_node_get_child (node));
end_node (p);
}
break;
case GSK_FILL_NODE:
{
start_node (p, "fill", node_name);
append_node_param (p, "child", gsk_fill_node_get_child (node));
append_path_param (p, "path", gsk_fill_node_get_path (node));
append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
end_node (p);
}
break;
case GSK_STROKE_NODE:
{
const GskStroke *stroke;
const float *dash;
gsize n_dash;
start_node (p, "stroke", node_name);
append_node_param (p, "child", gsk_stroke_node_get_child (node));
append_path_param (p, "path", gsk_stroke_node_get_path (node));
stroke = gsk_stroke_node_get_stroke (node);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0f);
append_enum_param (p, "line-cap", GSK_TYPE_LINE_CAP, gsk_stroke_get_line_cap (stroke));
append_enum_param (p, "line-join", GSK_TYPE_LINE_JOIN, gsk_stroke_get_line_join (stroke));
append_float_param (p, "miter-limit", gsk_stroke_get_miter_limit (stroke), 4.0f);
dash = gsk_stroke_get_dash (stroke, &n_dash);
if (dash)
append_dash_param (p, "dash", dash, n_dash);
append_float_param (p, "dash-offset", gsk_stroke_get_dash_offset (stroke), 0.0f);
end_node (p);
}

208
gsk/gskspline.c Normal file
View File

@@ -0,0 +1,208 @@
/*
* Copyright © 2002 University of Southern California
* 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
* Carl D. Worth <cworth@cworth.org>
*/
#include "config.h"
#include "gsksplineprivate.h"
#include <math.h>
/* Spline deviation from the circle in radius would be given by:
error = sqrt (x**2 + y**2) - 1
A simpler error function to work with is:
e = x**2 + y**2 - 1
From "Good approximation of circles by curvature-continuous Bezier
curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
Design 8 (1990) 22-41, we learn:
abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)
and
abs (error) =~ 1/2 * e
Of course, this error value applies only for the particular spline
approximation that is used in _cairo_gstate_arc_segment.
*/
static float
arc_error_normalized (float angle)
{
return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
}
static float
arc_max_angle_for_tolerance_normalized (float tolerance)
{
float angle, error;
guint i;
/* Use table lookup to reduce search time in most cases. */
struct {
float angle;
float error;
} table[] = {
{ G_PI / 1.0, 0.0185185185185185036127 },
{ G_PI / 2.0, 0.000272567143730179811158 },
{ G_PI / 3.0, 2.38647043651461047433e-05 },
{ G_PI / 4.0, 4.2455377443222443279e-06 },
{ G_PI / 5.0, 1.11281001494389081528e-06 },
{ G_PI / 6.0, 3.72662000942734705475e-07 },
{ G_PI / 7.0, 1.47783685574284411325e-07 },
{ G_PI / 8.0, 6.63240432022601149057e-08 },
{ G_PI / 9.0, 3.2715520137536980553e-08 },
{ G_PI / 10.0, 1.73863223499021216974e-08 },
{ G_PI / 11.0, 9.81410988043554039085e-09 },
};
for (i = 0; i < G_N_ELEMENTS (table); i++)
{
if (table[i].error < tolerance)
return table[i].angle;
}
i++;
do {
angle = G_PI / i++;
error = arc_error_normalized (angle);
} while (error > tolerance);
return angle;
}
static guint
arc_segments_needed (float angle,
float radius,
float tolerance)
{
float max_angle;
/* the error is amplified by at most the length of the
* major axis of the circle; see cairo-pen.c for a more detailed analysis
* of this. */
max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius);
return ceil (fabs (angle) / max_angle);
}
/* We want to draw a single spline approximating a circular arc radius
R from angle A to angle B. Since we want a symmetric spline that
matches the endpoints of the arc in position and slope, we know
that the spline control points must be:
(R * cos(A), R * sin(A))
(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
(R * cos(B), R * sin(B))
for some value of h.
"Approximation of circular arcs by cubic polynomials", Michael
Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
various values of h along with error analysis for each.
From that paper, a very practical value of h is:
h = 4/3 * R * tan(angle/4)
This value does not give the spline with minimal error, but it does
provide a very good approximation, (6th-order convergence), and the
error expression is quite simple, (see the comment for
_arc_error_normalized).
*/
static gboolean
gsk_spline_decompose_arc_segment (const graphene_point_t *center,
float radius,
float angle_A,
float angle_B,
GskSplineAddCurveFunc curve_func,
gpointer user_data)
{
float r_sin_A, r_cos_A;
float r_sin_B, r_cos_B;
float h;
r_sin_A = radius * sin (angle_A);
r_cos_A = radius * cos (angle_A);
r_sin_B = radius * sin (angle_B);
r_cos_B = radius * cos (angle_B);
h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
return curve_func ((graphene_point_t[4]) {
GRAPHENE_POINT_INIT (
center->x + r_cos_A,
center->y + r_sin_A
),
GRAPHENE_POINT_INIT (
center->x + r_cos_A - h * r_sin_A,
center->y + r_sin_A + h * r_cos_A
),
GRAPHENE_POINT_INIT (
center->x + r_cos_B + h * r_sin_B,
center->y + r_sin_B - h * r_cos_B
),
GRAPHENE_POINT_INIT (
center->x + r_cos_B,
center->y + r_sin_B
)
},
user_data);
}
gboolean
gsk_spline_decompose_arc (const graphene_point_t *center,
float radius,
float tolerance,
float start_angle,
float end_angle,
GskSplineAddCurveFunc curve_func,
gpointer user_data)
{
float step = start_angle - end_angle;
guint i, n_segments;
/* Recurse if drawing arc larger than pi */
if (ABS (step) > G_PI)
{
float mid_angle = (start_angle + end_angle) / 2.0;
return gsk_spline_decompose_arc (center, radius, tolerance, start_angle, mid_angle, curve_func, user_data)
&& gsk_spline_decompose_arc (center, radius, tolerance, mid_angle, end_angle, curve_func, user_data);
}
else if (ABS (step) < tolerance)
{
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
}
n_segments = arc_segments_needed (ABS (step), radius, tolerance);
step = (end_angle - start_angle) / n_segments;
for (i = 0; i < n_segments - 1; i++, start_angle += step)
{
if (!gsk_spline_decompose_arc_segment (center, radius, start_angle, start_angle + step, curve_func, user_data))
return FALSE;
}
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
}

41
gsk/gsksplineprivate.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_SPLINE_PRIVATE_H__
#define __GSK_SPLINE_PRIVATE_H__
#include "gskpath.h"
G_BEGIN_DECLS
typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4],
gpointer user_data);
gboolean gsk_spline_decompose_arc (const graphene_point_t *center,
float radius,
float tolerance,
float start_angle,
float end_angle,
GskSplineAddCurveFunc curve_func,
gpointer user_data);
G_END_DECLS
#endif /* __GSK_SPLINE_PRIVATE_H__ */

441
gsk/gskstroke.c Normal file
View File

@@ -0,0 +1,441 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskstrokeprivate.h"
/**
* SECTION:gskstroke
* @Title: Stroke
* @Short_description: Properties of a stroke operation
* @See_also: #GskPath, gsk_stroke_node_new()
*
* This section describes the #GskStroke structure that is used to
* describe lines and curves that are more complex than simple rectangles.
*
* #GskStroke is an immutable struct. After creation, you cannot change
* the types it represents. Instead, new #GskStroke have to be created.
* The #GskStrokeBuilder structure is meant to help in this endeavor.
*/
/**
* GskStroke:
*
* A #GskStroke struct is an opaque struct that should be copied
* on use.
*/
G_DEFINE_BOXED_TYPE (GskStroke, gsk_stroke,
gsk_stroke_copy,
gsk_stroke_free)
/**
* gsk_stroke_new:
* @line_width: line width of the stroke. Must be > 0
*
* Creates a new #GskStroke with the given @line_width.
*
* Returns: a new #GskStroke
**/
GskStroke *
gsk_stroke_new (float line_width)
{
GskStroke *self;
g_return_val_if_fail (line_width > 0, NULL);
self = g_new0 (GskStroke, 1);
self->line_width = line_width;
self->line_cap = GSK_LINE_CAP_BUTT;
self->line_join = GSK_LINE_JOIN_MITER;
self->miter_limit = 4.f; /* following svg */
return self;
}
/**
* gsk_stroke_copy:
* @other: #GskStroke to copy
*
* Creates a copy of the given @other stroke.
*
* Returns: a new #GskStroke. Use gsk_stroke_free() to free it.
**/
GskStroke *
gsk_stroke_copy (const GskStroke *other)
{
GskStroke *self;
g_return_val_if_fail (other != NULL, NULL);
self = g_new (GskStroke, 1);
gsk_stroke_init_copy (self, other);
return self;
}
/**
* gsk_stroke_free:
* @self: a #GskStroke
*
* Frees a #GskStroke.
**/
void
gsk_stroke_free (GskStroke *self)
{
if (self == NULL)
return;
gsk_stroke_clear (self);
g_free (self);
}
void
gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr)
{
cairo_set_line_width (cr, self->line_width);
/* gcc can optimize that to a direct case. This catches later additions to the enum */
switch (self->line_cap)
{
case GSK_LINE_CAP_BUTT:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
break;
case GSK_LINE_CAP_ROUND:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
break;
case GSK_LINE_CAP_SQUARE:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
break;
default:
g_assert_not_reached ();
break;
}
/* gcc can optimize that to a direct case. This catches later additions to the enum */
switch (self->line_join)
{
case GSK_LINE_JOIN_MITER:
case GSK_LINE_JOIN_MITER_CLIP:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
break;
case GSK_LINE_JOIN_ROUND:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
break;
case GSK_LINE_JOIN_BEVEL:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
break;
default:
g_assert_not_reached ();
break;
}
cairo_set_miter_limit (cr, self->miter_limit);
if (self->dash_length)
{
gsize i;
double *dash = g_newa (double, self->n_dash);
for (i = 0; i < self->n_dash; i++)
{
dash[i] = self->dash[i];
}
cairo_set_dash (cr, dash, self->n_dash, self->dash_offset);
}
else
cairo_set_dash (cr, NULL, 0, 0.0);
}
/**
* gsk_stroke_equal:
* @stroke1: the first #GskStroke
* @stroke2: the second #GskStroke
*
* Checks if 2 strokes are identical.
*
* Returns: %TRUE if the 2 strokes are equal, %FALSE otherwise
**/
gboolean
gsk_stroke_equal (gconstpointer stroke1,
gconstpointer stroke2)
{
const GskStroke *self1 = stroke1;
const GskStroke *self2 = stroke2;
return self1->line_width == self2->line_width;
}
/**
* gsk_stroke_set_line_width:
* @self: a #GskStroke
* @line_width: width of the line in pixels
*
* Sets the line width to be used when stroking. The line width
* must be > 0.
**/
void
gsk_stroke_set_line_width (GskStroke *self,
float line_width)
{
g_return_if_fail (self != NULL);
g_return_if_fail (line_width > 0);
self->line_width = line_width;
}
/**
* gsk_stroke_get_line_width:
* @self: a #GskStroke
*
* Gets the line width used.
*
* Returns: The line width
**/
float
gsk_stroke_get_line_width (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_width;
}
/**
* gsk_stroke_set_line_cap:
* @self: a #GskStroke
* @line_cap: the #GskLineCap
*
* Sets the line cap to be used when stroking.
* See #GskLineCap for details.
**/
void
gsk_stroke_set_line_cap (GskStroke *self,
GskLineCap line_cap)
{
g_return_if_fail (self != NULL);
self->line_cap = line_cap;
}
/**
* gsk_stroke_get_line_cap:
* @self: a #GskStroke
*
* Gets the line cap used. See #GskLineCap for details.
*
* Returns: The line cap
**/
GskLineCap
gsk_stroke_get_line_cap (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_cap;
}
/**
* gsk_stroke_set_line_join:
* @self: a #GskStroke
* @line_join: The line join to use
*
* Sets the line join to be used when stroking.
* See #GskLineJoin for details.
**/
void
gsk_stroke_set_line_join (GskStroke *self,
GskLineJoin line_join)
{
g_return_if_fail (self != NULL);
self->line_join = line_join;
}
/**
* gsk_stroke_get_line_join:
* @self: a #GskStroke
*
* Gets the line join used. See #GskLineJoin for details.
*
* Returns: The line join
**/
GskLineJoin
gsk_stroke_get_line_join (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_join;
}
/**
* gsk_stroke_set_miter_limit:
* @self: a #GskStroke
* @limit: the miter limit, must be non-negative
*
* Sets the limit for the distance from the corner where sharp
* turns of joins get cut off. The miter limit is in units of
* line width.
*
* For joins of type %GSK_LINE_JOIN_MITER that exceed the miter
* limit, the join gets rendered as if it was of type
* %GSK_LINE_JOIN_BEVEL. For joins of type %GSK_LINE_JOIN_MITER_CLIP,
* the miter is clipped at a distance of half the miter limit.
*/
void
gsk_stroke_set_miter_limit (GskStroke *self,
float limit)
{
g_return_if_fail (self != NULL);
g_return_if_fail (limit >= 0);
self->miter_limit = limit;
}
/**
* gsk_stroke_get_miter_limit:
* @self: a #GskStroke
*
* Returns the miter limit of a #GskStroke.
*/
float
gsk_stroke_get_miter_limit (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 4.f);
return self->miter_limit;
}
/**
* gsk_stroke_set_dash:
* @self: a #GskStroke
* @dash: (array length=n_dash) (transfer none) (nullable):
* the array of dashes
* @n_dash: number of elements in @dash
*
* Sets the dash pattern to use by this stroke. A dash pattern is specified by
* an array of alternating non-negative values. Each value provides the length
* of alternate "on" and "off" portions of the stroke.
*
* Each "on" segment will have caps applied as if the segment were a separate
* contour. In particular, it is valid to use an "on" length of 0 with
* @GSK_LINE_CAP_ROUND or @GSK_LINE_CAP_SQUARE to draw dots or squares along
* a path.
*
* If @n_dash is 0, if all elements in @dash are 0, or if there are negative
* values in @dash, then dashing is disabled.
*
* If @n_dash is 1, an alternating "on" and "off" pattern with the single
* dash length provided is assumed.
*
* If @n_dash is uneven, the dash array will be used with the first element
* in @dash defining an "on" or "off" in alternating passes through the array.
*
* You can specify a starting offset into the dash with
* @gsk_stroke_set_dash_offset().
**/
void
gsk_stroke_set_dash (GskStroke *self,
const float *dash,
gsize n_dash)
{
float dash_length;
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (dash != NULL || n_dash == 0);
dash_length = 0;
for (i = 0; i < n_dash; i++)
{
if (!(dash[i] >= 0)) /* should catch NaN */
{
g_critical ("invalid value in dash array at position %zu", i);
return;
}
dash_length += dash[i];
}
self->dash_length = dash_length;
g_free (self->dash);
self->dash = g_memdup (dash, sizeof (gfloat) * n_dash);
self->n_dash = n_dash;
}
/**
* gsk_stroke_get_dash:
* @self: a #GskStroke
* @n_dash: (out caller-allocates): number of elements
* in the array returned
*
* Gets the dash array in use or %NULL if dashing is disabled.
*
* Returns: (array length=n_dash) (transfer none) (nullable):
* The dash array or %NULL if the dash array is empty.
**/
const float *
gsk_stroke_get_dash (const GskStroke *self,
gsize *n_dash)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (n_dash != NULL, NULL);
*n_dash = self->n_dash;
return self->dash;
}
/**
* gsk_stroke_set_dash_offset:
* @self: a #GskStroke
* @offset: offset into the dash pattern
*
* Sets the offset into the dash pattern set via gsk_stroke_set_dash() where
* dashing should begin.
*
* This is an offset into the length of the path, not an index into the array values of
* the dash array.
**/
void
gsk_stroke_set_dash_offset (GskStroke *self,
float offset)
{
g_return_if_fail (self != NULL);
self->dash_offset = offset;
}
/**
* gsk_stroke_get_dash_offset:
* @self: a #GskStroke
*
* Returns the dash_offset of a #GskStroke.
*/
float
gsk_stroke_get_dash_offset (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 4.f);
return self->dash_offset;
}

87
gsk/gskstroke.h Normal file
View File

@@ -0,0 +1,87 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_STROKE_H__
#define __GSK_STROKE_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
#define GSK_TYPE_STROKE (gsk_stroke_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_stroke_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskStroke * gsk_stroke_new (float line_width);
GDK_AVAILABLE_IN_ALL
GskStroke * gsk_stroke_copy (const GskStroke *other);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_free (GskStroke *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_stroke_equal (gconstpointer stroke1,
gconstpointer stroke2);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_width (GskStroke *self,
float line_width);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_line_width (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_cap (GskStroke *self,
GskLineCap line_cap);
GDK_AVAILABLE_IN_ALL
GskLineCap gsk_stroke_get_line_cap (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_join (GskStroke *self,
GskLineJoin line_join);
GDK_AVAILABLE_IN_ALL
GskLineJoin gsk_stroke_get_line_join (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_miter_limit (GskStroke *self,
float limit);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_miter_limit (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_dash (GskStroke *self,
const float *dash,
gsize n_dash);
GDK_AVAILABLE_IN_ALL
const float * gsk_stroke_get_dash (const GskStroke *self,
gsize *n_dash);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_dash_offset (GskStroke *self,
float offset);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_dash_offset (const GskStroke *self);
G_END_DECLS
#endif /* __GSK_STROKE_H__ */

63
gsk/gskstrokeprivate.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_STROKE_PRIVATE_H__
#define __GSK_STROKE_PRIVATE_H__
#include "gskstroke.h"
G_BEGIN_DECLS
struct _GskStroke
{
float line_width;
GskLineCap line_cap;
GskLineJoin line_join;
float miter_limit;
float *dash;
gsize n_dash;
float dash_length; /* sum of all dashes in the array */
float dash_offset;
};
static inline void
gsk_stroke_init_copy (GskStroke *stroke,
const GskStroke *other)
{
*stroke = *other;
stroke->dash = g_memdup (other->dash, stroke->n_dash * sizeof (float));
}
static inline void
gsk_stroke_clear (GskStroke *stroke)
{
g_clear_pointer (&stroke->dash, g_free);
stroke->n_dash = 0; /* better safe than sorry */
}
void gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr);
G_END_DECLS
#endif /* __GSK_STROKE_PRIVATE_H__ */

View File

@@ -25,6 +25,10 @@
#include <gdk/gdk.h>
#include <gsk/gskenums.h>
typedef struct _GskPath GskPath;
typedef struct _GskPathBuilder GskPathBuilder;
typedef struct _GskPathMeasure GskPathMeasure;
typedef struct _GskRenderer GskRenderer;
typedef struct _GskStroke GskStroke;
typedef struct _GskTransform GskTransform;

View File

@@ -23,23 +23,31 @@ gsk_private_gl_shaders = [
]
gsk_public_sources = files([
'gskdiff.c',
'gskcairorenderer.c',
'gskdiff.c',
'gskglshader.c',
'gskpath.c',
'gskpathbuilder.c',
'gskpathdash.c',
'gskpathmeasure.c',
'gskrenderer.c',
'gskrendernode.c',
'gskrendernodeimpl.c',
'gskrendernodeparser.c',
'gskroundedrect.c',
'gskstroke.c',
'gsktransform.c',
'gl/gskglrenderer.c',
])
gsk_private_sources = files([
'gskcairoblur.c',
'gskcontour.c',
'gskcurve.c',
'gskdebug.c',
'gskprivate.c',
'gskprofiler.c',
'gskspline.c',
'gl/gskglattachmentstate.c',
'gl/gskglbuffer.c',
'gl/gskglcommandqueue.c',
@@ -66,9 +74,13 @@ gsk_public_headers = files([
'gskcairorenderer.h',
'gskenums.h',
'gskglshader.h',
'gskpath.h',
'gskpathbuilder.h',
'gskpathmeasure.h',
'gskrenderer.h',
'gskrendernode.h',
'gskroundedrect.h',
'gskstroke.h',
'gsktransform.h',
'gsktypes.h',
])
@@ -118,7 +130,9 @@ if have_vulkan
'vulkan/gskvulkancommandpool.c',
'vulkan/gskvulkanconvertop.c',
'vulkan/gskvulkancrossfadeop.c',
'vulkan/gskvulkandevice.c',
'vulkan/gskvulkandownloadop.c',
'vulkan/gskvulkanfillop.c',
'vulkan/gskvulkanglyphcache.c',
'vulkan/gskvulkanglyphop.c',
'vulkan/gskvulkanimage.c',
@@ -135,6 +149,7 @@ if have_vulkan
'vulkan/gskvulkanrenderpassop.c',
'vulkan/gskvulkanscissorop.c',
'vulkan/gskvulkanshaderop.c',
'vulkan/gskvulkanstrokeop.c',
'vulkan/gskvulkantextureop.c',
'vulkan/gskvulkanuploadop.c',
])

View File

@@ -7,17 +7,16 @@
struct _GskVulkanBuffer
{
GdkVulkanContext *vulkan;
gsize size;
GskVulkanDevice *device;
VkBuffer vk_buffer;
GskVulkanMemory *memory;
GskVulkanAllocator *allocator;
GskVulkanAllocation allocation;
};
static GskVulkanBuffer *
gsk_vulkan_buffer_new_internal (GdkVulkanContext *context,
gsk_vulkan_buffer_new_internal (GskVulkanDevice *device,
gsize size,
VkBufferUsageFlags usage)
{
@@ -26,10 +25,9 @@ gsk_vulkan_buffer_new_internal (GdkVulkanContext *context,
self = g_new0 (GskVulkanBuffer, 1);
self->vulkan = g_object_ref (context);
self->size = size;
self->device = g_object_ref (device);
GSK_VK_CHECK (vkCreateBuffer, gdk_vulkan_context_get_device (context),
GSK_VK_CHECK (vkCreateBuffer, gsk_vulkan_device_get_vk_device (device),
&(VkBufferCreateInfo) {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = size,
@@ -40,44 +38,49 @@ gsk_vulkan_buffer_new_internal (GdkVulkanContext *context,
NULL,
&self->vk_buffer);
vkGetBufferMemoryRequirements (gdk_vulkan_context_get_device (context),
vkGetBufferMemoryRequirements (gsk_vulkan_device_get_vk_device (device),
self->vk_buffer,
&requirements);
self->allocator = gsk_vulkan_device_find_allocator (device,
requirements.memoryTypeBits,
GSK_VULKAN_MEMORY_MAPPABLE,
GSK_VULKAN_MEMORY_MAPPABLE);
gsk_vulkan_alloc (self->allocator,
requirements.size,
requirements.alignment,
&self->allocation);
self->memory = gsk_vulkan_memory_new (context,
requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
requirements.size);
GSK_VK_CHECK (vkBindBufferMemory, gdk_vulkan_context_get_device (context),
GSK_VK_CHECK (vkBindBufferMemory, gsk_vulkan_device_get_vk_device (device),
self->vk_buffer,
gsk_vulkan_memory_get_device_memory (self->memory),
0);
self->allocation.vk_memory,
self->allocation.offset);
return self;
}
GskVulkanBuffer *
gsk_vulkan_buffer_new (GdkVulkanContext *context,
gsize size)
gsk_vulkan_buffer_new (GskVulkanDevice *device,
gsize size)
{
return gsk_vulkan_buffer_new_internal (context, size,
return gsk_vulkan_buffer_new_internal (device, size,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
}
GskVulkanBuffer *
gsk_vulkan_buffer_new_storage (GdkVulkanContext *context,
gsize size)
gsk_vulkan_buffer_new_storage (GskVulkanDevice *device,
gsize size)
{
return gsk_vulkan_buffer_new_internal (context, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
return gsk_vulkan_buffer_new_internal (device, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
}
GskVulkanBuffer *
gsk_vulkan_buffer_new_map (GdkVulkanContext *context,
gsize size,
GskVulkanMapMode mode)
gsk_vulkan_buffer_new_map (GskVulkanDevice *device,
gsize size,
GskVulkanMapMode mode)
{
return gsk_vulkan_buffer_new_internal (context,
return gsk_vulkan_buffer_new_internal (device,
size,
(mode & GSK_VULKAN_READ ? VK_BUFFER_USAGE_TRANSFER_DST_BIT : 0) |
(mode & GSK_VULKAN_WRITE ? VK_BUFFER_USAGE_TRANSFER_SRC_BIT : 0));
@@ -86,13 +89,13 @@ gsk_vulkan_buffer_new_map (GdkVulkanContext *context,
void
gsk_vulkan_buffer_free (GskVulkanBuffer *self)
{
vkDestroyBuffer (gdk_vulkan_context_get_device (self->vulkan),
vkDestroyBuffer (gsk_vulkan_device_get_vk_device (self->device),
self->vk_buffer,
NULL);
gsk_vulkan_memory_free (self->memory);
gsk_vulkan_free (self->allocator, &self->allocation);
g_object_unref (self->vulkan);
g_object_unref (self->device);
g_free (self);
}
@@ -106,17 +109,12 @@ gsk_vulkan_buffer_get_buffer (GskVulkanBuffer *self)
gsize
gsk_vulkan_buffer_get_size (GskVulkanBuffer *self)
{
return self->size;
return self->allocation.size;
}
guchar *
gsk_vulkan_buffer_map (GskVulkanBuffer *self)
gsk_vulkan_buffer_get_data (GskVulkanBuffer *self)
{
return gsk_vulkan_memory_map (self->memory);
return self->allocation.map;
}
void
gsk_vulkan_buffer_unmap (GskVulkanBuffer *self)
{
gsk_vulkan_memory_unmap (self->memory);
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include <gdk/gdk.h>
#include "gskvulkandeviceprivate.h"
G_BEGIN_DECLS
@@ -13,20 +13,18 @@ typedef enum
GSK_VULKAN_READWRITE = GSK_VULKAN_READ | GSK_VULKAN_WRITE
} GskVulkanMapMode;
GskVulkanBuffer * gsk_vulkan_buffer_new (GdkVulkanContext *context,
GskVulkanBuffer * gsk_vulkan_buffer_new (GskVulkanDevice *device,
gsize size);
GskVulkanBuffer * gsk_vulkan_buffer_new_storage (GdkVulkanContext *context,
GskVulkanBuffer * gsk_vulkan_buffer_new_storage (GskVulkanDevice *device,
gsize size);
GskVulkanBuffer * gsk_vulkan_buffer_new_map (GdkVulkanContext *context,
GskVulkanBuffer * gsk_vulkan_buffer_new_map (GskVulkanDevice *device,
gsize size,
GskVulkanMapMode mode);
void gsk_vulkan_buffer_free (GskVulkanBuffer *buffer);
VkBuffer gsk_vulkan_buffer_get_buffer (GskVulkanBuffer *self);
gsize gsk_vulkan_buffer_get_size (GskVulkanBuffer *self);
guchar * gsk_vulkan_buffer_map (GskVulkanBuffer *self);
void gsk_vulkan_buffer_unmap (GskVulkanBuffer *self);
guchar * gsk_vulkan_buffer_get_data (GskVulkanBuffer *self);
G_END_DECLS

View File

@@ -0,0 +1,138 @@
#include "config.h"
#include "gskvulkandeviceprivate.h"
#include "gdk/gdkdisplayprivate.h"
#include "gdk/gdkvulkancontextprivate.h"
struct _GskVulkanDevice
{
GObject parent_instance;
GdkDisplay *display;
GskVulkanAllocator *allocators[VK_MAX_MEMORY_TYPES];
};
struct _GskVulkanDeviceClass
{
GObjectClass parent_class;
};
G_DEFINE_TYPE (GskVulkanDevice, gsk_vulkan_device, G_TYPE_OBJECT)
static void
gsk_vulkan_device_finalize (GObject *object)
{
GskVulkanDevice *self = GSK_VULKAN_DEVICE (object);
gsize i;
g_object_steal_data (G_OBJECT (self->display), "-gsk-vulkan-device");
for (i = 0; i < VK_MAX_MEMORY_TYPES; i++)
g_clear_pointer (&self->allocators[i], gsk_vulkan_allocator_free);
gdk_display_unref_vulkan (self->display);
g_object_unref (self->display);
G_OBJECT_CLASS (gsk_vulkan_device_parent_class)->finalize (object);
}
static void
gsk_vulkan_device_class_init (GskVulkanDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gsk_vulkan_device_finalize;
}
static void
gsk_vulkan_device_init (GskVulkanDevice *self)
{
}
GskVulkanDevice *
gsk_vulkan_device_get_for_display (GdkDisplay *display,
GError **error)
{
GskVulkanDevice *self;
self = g_object_get_data (G_OBJECT (display), "-gsk-vulkan-device");
if (self)
return g_object_ref (self);
if (!gdk_display_ref_vulkan (display, error))
return NULL;
self = g_object_new (GSK_TYPE_VULKAN_DEVICE, NULL);
self->display = display;
g_object_set_data (G_OBJECT (display), "-gsk-vulkan-device", self);
return self;
}
VkDevice
gsk_vulkan_device_get_vk_device (GskVulkanDevice *self)
{
return self->display->vk_device;
}
VkPhysicalDevice
gsk_vulkan_device_get_vk_physical_device (GskVulkanDevice *self)
{
return self->display->vk_physical_device;
}
static GskVulkanAllocator *
gsk_vulkan_device_get_allocator (GskVulkanDevice *self,
gsize index,
const VkMemoryType *type)
{
if (self->allocators[index] == NULL)
{
self->allocators[index] = gsk_vulkan_direct_allocator_new (gsk_vulkan_device_get_vk_device (self),
index,
type);
self->allocators[index] = gsk_vulkan_buddy_allocator_new (self->allocators[index],
1024 * 1024);
//allocators[index] = gsk_vulkan_stats_allocator_new (allocators[index]);
}
return self->allocators[index];
}
/* following code found in
* https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceMemoryProperties.html */
GskVulkanAllocator *
gsk_vulkan_device_find_allocator (GskVulkanDevice *self,
uint32_t allowed_types,
VkMemoryPropertyFlags required_flags,
VkMemoryPropertyFlags desired_flags)
{
VkPhysicalDeviceMemoryProperties properties;
uint32_t i, found;
vkGetPhysicalDeviceMemoryProperties (gsk_vulkan_device_get_vk_physical_device (self),
&properties);
found = properties.memoryTypeCount;
for (i = 0; i < properties.memoryTypeCount; i++)
{
if (!(allowed_types & (1 << i)))
continue;
if ((properties.memoryTypes[i].propertyFlags & required_flags) != required_flags)
continue;
found = MIN (i, found);
if ((properties.memoryTypes[i].propertyFlags & desired_flags) == desired_flags)
break;
}
g_assert (found < properties.memoryTypeCount);
return gsk_vulkan_device_get_allocator (self, i, &properties.memoryTypes[i]);
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "gskvulkanmemoryprivate.h"
#include <gdk/gdkvulkancontext.h>
G_BEGIN_DECLS
#define GSK_TYPE_VULKAN_DEVICE (gsk_vulkan_device_get_type ())
G_DECLARE_FINAL_TYPE(GskVulkanDevice, gsk_vulkan_device, GSK, VULKAN_DEVICE, GObject)
GskVulkanDevice * gsk_vulkan_device_get_for_display (GdkDisplay *display,
GError **error);
VkDevice gsk_vulkan_device_get_vk_device (GskVulkanDevice *self);
VkPhysicalDevice gsk_vulkan_device_get_vk_physical_device (GskVulkanDevice *self);
GskVulkanAllocator * gsk_vulkan_device_find_allocator (GskVulkanDevice *self,
uint32_t allowed_types,
VkMemoryPropertyFlags required_flags,
VkMemoryPropertyFlags desired_flags);
G_END_DECLS

View File

@@ -36,7 +36,7 @@ gsk_vulkan_download_op_command_with_area (GskVulkanOp *op,
gsize stride;
stride = area->width * gdk_memory_format_bytes_per_pixel (gsk_vulkan_image_get_format (image));
*buffer = gsk_vulkan_buffer_new_map (gsk_vulkan_render_get_context (render),
*buffer = gsk_vulkan_buffer_new_map (gsk_vulkan_render_get_device (render),
area->height * stride,
GSK_VULKAN_READ);
@@ -115,7 +115,7 @@ gsk_vulkan_download_op_finish (GskVulkanOp *op)
guchar *data;
gsize stride;
data = gsk_vulkan_buffer_map (self->buffer);
data = gsk_vulkan_buffer_get_data (self->buffer);
stride = gsk_vulkan_image_get_width (self->image) *
gdk_memory_format_bytes_per_pixel (gsk_vulkan_image_get_format (self->image));
self->func (self->user_data,
@@ -124,7 +124,6 @@ gsk_vulkan_download_op_finish (GskVulkanOp *op)
gsk_vulkan_image_get_width (self->image),
gsk_vulkan_image_get_height (self->image),
stride);
gsk_vulkan_buffer_unmap (self->buffer);
g_object_unref (self->image);
g_clear_pointer (&self->buffer, gsk_vulkan_buffer_free);

View File

@@ -0,0 +1,129 @@
#include "config.h"
#include "gskvulkanfillopprivate.h"
#include "gskvulkanprivate.h"
#include "gskvulkanshaderopprivate.h"
#include "gskpathprivate.h"
#include "vulkan/resources/fill.vert.h"
typedef struct _GskVulkanFillOp GskVulkanFillOp;
struct _GskVulkanFillOp
{
GskVulkanShaderOp op;
graphene_point_t offset;
graphene_rect_t rect;
GskPath *path;
GskFillRule fill_rule;
GdkRGBA color;
gsize buffer_offset;
};
static void
gsk_vulkan_fill_op_finish (GskVulkanOp *op)
{
GskVulkanFillOp *self = (GskVulkanFillOp *) op;
gsk_path_unref (self->path);
}
static void
gsk_vulkan_fill_op_print (GskVulkanOp *op,
GString *string,
guint indent)
{
GskVulkanFillOp *self = (GskVulkanFillOp *) op;
print_indent (string, indent);
print_rect (string, &self->rect);
g_string_append_printf (string, "fill ");
print_newline (string);
}
static void
gsk_vulkan_fill_op_collect_vertex_data (GskVulkanOp *op,
guchar *data)
{
GskVulkanFillOp *self = (GskVulkanFillOp *) op;
GskVulkanFillInstance *instance = (GskVulkanFillInstance *) (data + ((GskVulkanShaderOp *) op)->vertex_offset);
gsk_vulkan_rect_to_float (&self->rect, instance->rect);
gsk_vulkan_rgba_to_float (&self->color, instance->color);
gsk_vulkan_point_to_float (&self->offset, instance->offset);
instance->points_id = self->buffer_offset;
instance->fill_rule = self->fill_rule;
}
static void
gsk_vulkan_fill_op_reserve_descriptor_sets (GskVulkanOp *op,
GskVulkanRender *render)
{
GskVulkanFillOp *self = (GskVulkanFillOp *) op;
const GskContour *contour;
gsize size, i, n;
guchar *mem;
size = sizeof (guint32);
n = gsk_path_get_n_contours (self->path);
for (i = 0; i < n; i++)
{
contour = gsk_path_get_contour (self->path, i);
size += gsk_contour_get_shader_size (contour);
}
mem = gsk_vulkan_render_get_buffer_memory (render,
size,
G_ALIGNOF (float),
&self->buffer_offset);
*(guint32*) mem = n;
mem += sizeof (guint32);
for (i = 0; i < n; i++)
{
contour = gsk_path_get_contour (self->path, i);
gsk_contour_to_shader (contour, mem);
mem += gsk_contour_get_shader_size (contour);
}
}
static const GskVulkanShaderOpClass GSK_VULKAN_FILL_OP_CLASS = {
{
GSK_VULKAN_OP_SIZE (GskVulkanFillOp),
GSK_VULKAN_STAGE_SHADER,
gsk_vulkan_fill_op_finish,
gsk_vulkan_fill_op_print,
gsk_vulkan_shader_op_count_vertex_data,
gsk_vulkan_fill_op_collect_vertex_data,
gsk_vulkan_fill_op_reserve_descriptor_sets,
gsk_vulkan_shader_op_command
},
"fill",
0,
&gsk_vulkan_fill_info,
};
void
gsk_vulkan_fill_op (GskVulkanRender *render,
GskVulkanShaderClip clip,
const graphene_point_t *offset,
const graphene_rect_t *rect,
GskPath *path,
GskFillRule fill_rule,
const GdkRGBA *color)
{
GskVulkanFillOp *self;
self = (GskVulkanFillOp *) gsk_vulkan_shader_op_alloc (render, &GSK_VULKAN_FILL_OP_CLASS, clip, NULL);
self->offset = *offset;
self->rect = *rect;
self->path = gsk_path_ref (path);
self->fill_rule = fill_rule;
self->color = *color;
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "gskvulkanopprivate.h"
G_BEGIN_DECLS
void gsk_vulkan_fill_op (GskVulkanRender *render,
GskVulkanShaderClip clip,
const graphene_point_t *offset,
const graphene_rect_t *rect,
GskPath *path,
GskFillRule fill_rule,
const GdkRGBA *color);
G_END_DECLS

View File

@@ -36,8 +36,7 @@ typedef struct {
struct _GskVulkanGlyphCache {
GObject parent_instance;
GdkVulkanContext *vulkan;
GskRenderer *renderer;
GskVulkanDevice *device;
GHashTable *hash_table;
GPtrArray *atlases;
@@ -70,7 +69,7 @@ create_atlas (GskVulkanGlyphCache *cache)
atlas->x = 0;
atlas->image = NULL;
atlas->image = gsk_vulkan_image_new_for_atlas (cache->vulkan, atlas->width, atlas->height);
atlas->image = gsk_vulkan_image_new_for_atlas (cache->device, atlas->width, atlas->height);
return atlas;
}
@@ -245,12 +244,12 @@ add_to_cache (GskVulkanGlyphCache *cache,
}
GskVulkanGlyphCache *
gsk_vulkan_glyph_cache_new (GdkVulkanContext *vulkan)
gsk_vulkan_glyph_cache_new (GskVulkanDevice *device)
{
GskVulkanGlyphCache *cache;
cache = GSK_VULKAN_GLYPH_CACHE (g_object_new (GSK_TYPE_VULKAN_GLYPH_CACHE, NULL));
cache->vulkan = vulkan;
cache->device = device;
g_ptr_array_add (cache->atlases, create_atlas (cache));
return cache;

View File

@@ -31,7 +31,7 @@ typedef struct
guint64 timestamp;
} GskVulkanCachedGlyph;
GskVulkanGlyphCache *gsk_vulkan_glyph_cache_new (GdkVulkanContext *vulkan);
GskVulkanGlyphCache *gsk_vulkan_glyph_cache_new (GskVulkanDevice *device);
GskVulkanCachedGlyph *gsk_vulkan_glyph_cache_lookup (GskVulkanGlyphCache *cache,
GskVulkanRender *render,

View File

@@ -14,7 +14,7 @@ struct _GskVulkanImage
{
GObject parent_instance;
GdkVulkanContext *vulkan;
GskVulkanDevice *device;
GdkMemoryFormat format;
VkFormat vk_format;
@@ -31,7 +31,8 @@ struct _GskVulkanImage
VkImageLayout vk_image_layout;
VkAccessFlags vk_access;
GskVulkanMemory *memory;
GskVulkanAllocator *allocator;
GskVulkanAllocation allocation;
};
G_DEFINE_TYPE (GskVulkanImage, gsk_vulkan_image, G_TYPE_OBJECT)
@@ -316,6 +317,21 @@ gsk_memory_format_get_vk_format_infos (GdkMemoryFormat format)
#undef SWIZZLE
}
static gboolean
gsk_memory_format_info_is_framebuffer_compatible (const GskMemoryFormatInfo *format)
{
if (format->postprocess)
return FALSE;
if (format->components.r != VK_COMPONENT_SWIZZLE_R ||
format->components.g != VK_COMPONENT_SWIZZLE_G ||
format->components.b != VK_COMPONENT_SWIZZLE_B ||
format->components.a != VK_COMPONENT_SWIZZLE_A)
return FALSE;
return TRUE;
}
static GdkMemoryFormat
gsk_memory_format_get_fallback (GdkMemoryFormat format)
{
@@ -385,19 +401,22 @@ gsk_memory_format_get_fallback (GdkMemoryFormat format)
}
static gboolean
gsk_vulkan_context_supports_format (GdkVulkanContext *context,
VkFormat format,
VkImageTiling tiling,
VkImageUsageFlags usage,
gsize width,
gsize height)
gsk_vulkan_device_supports_format (GskVulkanDevice *device,
VkFormat format,
VkImageTiling tiling,
VkImageUsageFlags usage,
gsize width,
gsize height)
{
VkPhysicalDevice vk_phys_device;
VkFormatProperties properties;
VkImageFormatProperties image_properties;
VkFormatFeatureFlags features, required;
VkResult res;
vkGetPhysicalDeviceFormatProperties (gdk_vulkan_context_get_physical_device (context),
vk_phys_device = gsk_vulkan_device_get_vk_physical_device (device);
vkGetPhysicalDeviceFormatProperties (vk_phys_device,
format,
&properties);
@@ -421,7 +440,7 @@ gsk_vulkan_context_supports_format (GdkVulkanContext *context,
if ((features & required) != required)
return FALSE;
res = vkGetPhysicalDeviceImageFormatProperties (gdk_vulkan_context_get_physical_device (context),
res = vkGetPhysicalDeviceImageFormatProperties (vk_phys_device,
format,
VK_IMAGE_TYPE_2D,
tiling,
@@ -442,7 +461,7 @@ static void
gsk_vulkan_image_create_view (GskVulkanImage *self,
const GskMemoryFormatInfo *format)
{
GSK_VK_CHECK (vkCreateImageView, gdk_vulkan_context_get_device (self->vulkan),
GSK_VK_CHECK (vkCreateImageView, gsk_vulkan_device_get_vk_device (self->device),
&(VkImageViewCreateInfo) {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = self->vk_image,
@@ -462,7 +481,7 @@ gsk_vulkan_image_create_view (GskVulkanImage *self,
}
static GskVulkanImage *
gsk_vulkan_image_new (GdkVulkanContext *context,
gsk_vulkan_image_new (GskVulkanDevice *device,
GdkMemoryFormat format,
gsize width,
gsize height,
@@ -476,6 +495,7 @@ gsk_vulkan_image_new (GdkVulkanContext *context,
{
VkMemoryRequirements requirements;
GskVulkanImage *self;
VkDevice vk_device;
const GskMemoryFormatInfo *vk_format;
g_assert (width > 0 && height > 0);
@@ -489,17 +509,21 @@ gsk_vulkan_image_new (GdkVulkanContext *context,
if (vk_format->postprocess & ~allowed_postprocess)
continue;
if (gsk_vulkan_context_supports_format (context,
vk_format->format,
tiling, usage,
width, height))
if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT &&
!gsk_memory_format_info_is_framebuffer_compatible (vk_format))
continue;
if (gsk_vulkan_device_supports_format (device,
vk_format->format,
tiling, usage,
width, height))
break;
if (tiling != VK_IMAGE_TILING_OPTIMAL &&
gsk_vulkan_context_supports_format (context,
vk_format->format,
VK_IMAGE_TILING_OPTIMAL, usage,
width, height))
gsk_vulkan_device_supports_format (device,
vk_format->format,
VK_IMAGE_TILING_OPTIMAL, usage,
width, height))
{
tiling = VK_IMAGE_TILING_OPTIMAL;
break;
@@ -511,9 +535,11 @@ gsk_vulkan_image_new (GdkVulkanContext *context,
format = gsk_memory_format_get_fallback (format);
}
vk_device = gsk_vulkan_device_get_vk_device (device);
self = g_object_new (GSK_TYPE_VULKAN_IMAGE, NULL);
self->vulkan = g_object_ref (context);
self->device = g_object_ref (device);
self->format = format;
self->vk_format = vk_format->format;
self->postprocess = vk_format->postprocess;
@@ -525,7 +551,7 @@ gsk_vulkan_image_new (GdkVulkanContext *context,
self->vk_image_layout = layout;
self->vk_access = access;
GSK_VK_CHECK (vkCreateImage, gdk_vulkan_context_get_device (context),
GSK_VK_CHECK (vkCreateImage, vk_device,
&(VkImageCreateInfo) {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.flags = 0,
@@ -543,19 +569,23 @@ gsk_vulkan_image_new (GdkVulkanContext *context,
NULL,
&self->vk_image);
vkGetImageMemoryRequirements (gdk_vulkan_context_get_device (context),
vkGetImageMemoryRequirements (vk_device,
self->vk_image,
&requirements);
self->memory = gsk_vulkan_memory_new (context,
requirements.memoryTypeBits,
memory,
requirements.size);
self->allocator = gsk_vulkan_device_find_allocator (device,
requirements.memoryTypeBits,
0,
tiling == VK_IMAGE_TILING_LINEAR ? GSK_VULKAN_MEMORY_MAPPABLE : 0);
gsk_vulkan_alloc (self->allocator,
requirements.size,
requirements.alignment,
&self->allocation);
GSK_VK_CHECK (vkBindImageMemory, gdk_vulkan_context_get_device (context),
GSK_VK_CHECK (vkBindImageMemory, vk_device,
self->vk_image,
gsk_vulkan_memory_get_device_memory (self->memory),
0);
self->allocation.vk_memory,
self->allocation.offset);
gsk_vulkan_image_create_view (self, vk_format);
@@ -563,14 +593,14 @@ gsk_vulkan_image_new (GdkVulkanContext *context,
}
GskVulkanImage *
gsk_vulkan_image_new_for_upload (GdkVulkanContext *context,
gsk_vulkan_image_new_for_upload (GskVulkanDevice *device,
GdkMemoryFormat format,
gsize width,
gsize height)
{
GskVulkanImage *self;
self = gsk_vulkan_image_new (context,
self = gsk_vulkan_image_new (device,
format,
width,
height,
@@ -599,44 +629,33 @@ gsk_vulkan_image_can_map (GskVulkanImage *self)
self->vk_image_layout != VK_IMAGE_LAYOUT_GENERAL)
return FALSE;
return gsk_vulkan_memory_can_map (self->memory, TRUE);
return self->allocation.map != NULL;
}
guchar *
gsk_vulkan_image_try_map (GskVulkanImage *self,
gsize *out_stride)
gsk_vulkan_image_get_data (GskVulkanImage *self,
gsize *out_stride)
{
VkImageSubresource image_res;
VkSubresourceLayout image_layout;
guchar *result;
if (!gsk_vulkan_image_can_map (self))
return NULL;
result = gsk_vulkan_memory_map (self->memory);
if (result == NULL)
return NULL;
image_res.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_res.mipLevel = 0;
image_res.arrayLayer = 0;
vkGetImageSubresourceLayout (gdk_vulkan_context_get_device (self->vulkan),
vkGetImageSubresourceLayout (gsk_vulkan_device_get_vk_device (self->device),
self->vk_image, &image_res, &image_layout);
*out_stride = image_layout.rowPitch;
return result + image_layout.offset;
}
void
gsk_vulkan_image_unmap (GskVulkanImage *self)
{
gsk_vulkan_memory_unmap (self->memory);
return self->allocation.map + image_layout.offset;
}
GskVulkanImage *
gsk_vulkan_image_new_for_swapchain (GdkVulkanContext *context,
gsk_vulkan_image_new_for_swapchain (GskVulkanDevice *device,
VkImage image,
VkFormat format,
gsize width,
@@ -646,7 +665,7 @@ gsk_vulkan_image_new_for_swapchain (GdkVulkanContext *context,
self = g_object_new (GSK_TYPE_VULKAN_IMAGE, NULL);
self->vulkan = g_object_ref (context);
self->device = g_object_ref (device);
self->width = width;
self->height = height;
self->vk_tiling = VK_IMAGE_TILING_OPTIMAL;
@@ -670,13 +689,13 @@ gsk_vulkan_image_new_for_swapchain (GdkVulkanContext *context,
}
GskVulkanImage *
gsk_vulkan_image_new_for_atlas (GdkVulkanContext *context,
gsize width,
gsize height)
gsk_vulkan_image_new_for_atlas (GskVulkanDevice *device,
gsize width,
gsize height)
{
GskVulkanImage *self;
self = gsk_vulkan_image_new (context,
self = gsk_vulkan_image_new (device,
GDK_MEMORY_DEFAULT,
width,
height,
@@ -692,19 +711,19 @@ gsk_vulkan_image_new_for_atlas (GdkVulkanContext *context,
}
GskVulkanImage *
gsk_vulkan_image_new_for_offscreen (GdkVulkanContext *context,
GdkMemoryFormat preferred_format,
gsize width,
gsize height)
gsk_vulkan_image_new_for_offscreen (GskVulkanDevice *device,
GdkMemoryFormat preferred_format,
gsize width,
gsize height)
{
GskVulkanImage *self;
self = gsk_vulkan_image_new (context,
self = gsk_vulkan_image_new (device,
preferred_format,
width,
height,
0,
VK_IMAGE_TILING_LINEAR,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
@@ -722,7 +741,7 @@ gsk_vulkan_image_finalize (GObject *object)
GskVulkanImage *self = GSK_VULKAN_IMAGE (object);
VkDevice device;
device = gdk_vulkan_context_get_device (self->vulkan);
device = gsk_vulkan_device_get_vk_device (self->device);
if (self->vk_framebuffer != VK_NULL_HANDLE)
vkDestroyFramebuffer (device, self->vk_framebuffer, NULL);
@@ -732,12 +751,13 @@ gsk_vulkan_image_finalize (GObject *object)
/* memory is NULL for for_swapchain() images, where we don't own
* the VkImage */
if (self->memory)
vkDestroyImage (device, self->vk_image, NULL);
if (self->allocator)
{
vkDestroyImage (device, self->vk_image, NULL);
gsk_vulkan_free (self->allocator, &self->allocation);
}
g_clear_pointer (&self->memory, gsk_vulkan_memory_free);
g_object_unref (self->vulkan);
g_object_unref (self->device);
G_OBJECT_CLASS (gsk_vulkan_image_parent_class)->finalize (object);
}
@@ -760,7 +780,7 @@ gsk_vulkan_image_get_framebuffer (GskVulkanImage *self,
if (self->vk_framebuffer)
return self->vk_framebuffer;
GSK_VK_CHECK (vkCreateFramebuffer, gdk_vulkan_context_get_device (self->vulkan),
GSK_VK_CHECK (vkCreateFramebuffer, gsk_vulkan_device_get_vk_device (self->device),
&(VkFramebufferCreateInfo) {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = render_pass,

View File

@@ -4,6 +4,7 @@
#include "gskvulkanbufferprivate.h"
#include "gskvulkancommandpoolprivate.h"
#include "gskvulkandeviceprivate.h"
G_BEGIN_DECLS
@@ -18,27 +19,26 @@ typedef enum
G_DECLARE_FINAL_TYPE (GskVulkanImage, gsk_vulkan_image, GSK, VULKAN_IMAGE, GObject)
GskVulkanImage * gsk_vulkan_image_new_for_swapchain (GdkVulkanContext *context,
GskVulkanImage * gsk_vulkan_image_new_for_swapchain (GskVulkanDevice *device,
VkImage image,
VkFormat format,
gsize width,
gsize height);
GskVulkanImage * gsk_vulkan_image_new_for_atlas (GdkVulkanContext *context,
GskVulkanImage * gsk_vulkan_image_new_for_atlas (GskVulkanDevice *device,
gsize width,
gsize height);
GskVulkanImage * gsk_vulkan_image_new_for_offscreen (GdkVulkanContext *context,
GskVulkanImage * gsk_vulkan_image_new_for_offscreen (GskVulkanDevice *device,
GdkMemoryFormat preferred_format,
gsize width,
gsize height);
GskVulkanImage * gsk_vulkan_image_new_for_upload (GdkVulkanContext *context,
GskVulkanImage * gsk_vulkan_image_new_for_upload (GskVulkanDevice *device,
GdkMemoryFormat format,
gsize width,
gsize height);
guchar * gsk_vulkan_image_try_map (GskVulkanImage *self,
guchar * gsk_vulkan_image_get_data (GskVulkanImage *self,
gsize *out_stride);
void gsk_vulkan_image_unmap (GskVulkanImage *self);
gsize gsk_vulkan_image_get_width (GskVulkanImage *self);
gsize gsk_vulkan_image_get_height (GskVulkanImage *self);

View File

@@ -4,117 +4,363 @@
#include "gskvulkanprivate.h"
struct _GskVulkanMemory
/* {{{ direct allocator ***/
typedef struct _GskVulkanDirectAllocator GskVulkanDirectAllocator;
struct _GskVulkanDirectAllocator
{
GdkVulkanContext *vulkan;
gsize size;
GskVulkanAllocator allocator_class;
VkDevice device; /* no reference held */
uint32_t vk_memory_type_index;
VkMemoryType vk_memory_type;
VkDeviceMemory vk_memory;
};
GskVulkanMemory *
gsk_vulkan_memory_new (GdkVulkanContext *context,
uint32_t allowed_types,
VkMemoryPropertyFlags flags,
gsize size)
static void
gsk_vulkan_direct_allocator_free_allocator (GskVulkanAllocator *allocator)
{
VkPhysicalDeviceMemoryProperties properties;
GskVulkanMemory *self;
uint32_t i;
g_free (allocator);
}
self = g_new0 (GskVulkanMemory, 1);
static void
gsk_vulkan_direct_allocator_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize alignment,
GskVulkanAllocation *alloc)
{
GskVulkanDirectAllocator *self = (GskVulkanDirectAllocator *) allocator;
self->vulkan = g_object_ref (context);
self->size = size;
vkGetPhysicalDeviceMemoryProperties (gdk_vulkan_context_get_physical_device (context),
&properties);
for (i = 0; i < properties.memoryTypeCount; i++)
{
if (!(allowed_types & (1 << i)))
continue;
if ((properties.memoryTypes[i].propertyFlags & flags) == flags)
break;
}
g_assert (i < properties.memoryTypeCount);
self->vk_memory_type = properties.memoryTypes[i];
GSK_VK_CHECK (vkAllocateMemory, gdk_vulkan_context_get_device (context),
GSK_VK_CHECK (vkAllocateMemory, self->device,
&(VkMemoryAllocateInfo) {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = size,
.memoryTypeIndex = i
.memoryTypeIndex = self->vk_memory_type_index
},
NULL,
&self->vk_memory);
&alloc->vk_memory);
return self;
if ((self->vk_memory_type.propertyFlags & GSK_VULKAN_MEMORY_MAPPABLE) == GSK_VULKAN_MEMORY_MAPPABLE)
{
GSK_VK_CHECK (vkMapMemory, self->device,
alloc->vk_memory,
0,
size,
0,
(void **) &alloc->map);
}
alloc->offset = 0;
alloc->size = size;
}
void
gsk_vulkan_memory_free (GskVulkanMemory *self)
static void
gsk_vulkan_direct_allocator_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc)
{
vkFreeMemory (gdk_vulkan_context_get_device (self->vulkan),
self->vk_memory,
NULL);
GskVulkanDirectAllocator *self = (GskVulkanDirectAllocator *) allocator;
g_object_unref (self->vulkan);
if (alloc->map)
vkUnmapMemory (self->device, alloc->vk_memory);
vkFreeMemory (self->device,
alloc->vk_memory,
NULL);
}
GskVulkanAllocator *
gsk_vulkan_direct_allocator_new (VkDevice device,
uint32_t vk_type_index,
const VkMemoryType *vk_type)
{
GskVulkanDirectAllocator *self;
self = g_new0 (GskVulkanDirectAllocator, 1);
self->allocator_class.free_allocator = gsk_vulkan_direct_allocator_free_allocator;
self->allocator_class.alloc = gsk_vulkan_direct_allocator_alloc;
self->allocator_class.free = gsk_vulkan_direct_allocator_free;
self->device = device;
self->vk_memory_type_index = vk_type_index;
self->vk_memory_type = *vk_type;
return (GskVulkanAllocator *) self;
}
/* }}} */
/* {{{ buddy allocator ***/
#define GDK_ARRAY_NAME gsk_vulkan_allocation_list
#define GDK_ARRAY_TYPE_NAME GskVulkanAllocationList
#define GDK_ARRAY_ELEMENT_TYPE GskVulkanAllocation
#define GDK_ARRAY_BY_VALUE 1
#define GDK_ARRAY_PREALLOC 4
#define GDK_ARRAY_NO_MEMSET 1
#include "gdk/gdkarrayimpl.c"
#define N_SUBDIVISIONS 10
typedef struct _GskVulkanBuddyAllocator GskVulkanBuddyAllocator;
struct _GskVulkanBuddyAllocator
{
GskVulkanAllocator allocator_class;
GskVulkanAllocator *allocator;
gsize block_size_slot;
GskVulkanAllocation cache;
GskVulkanAllocationList free_lists[N_SUBDIVISIONS];
};
static void
gsk_vulkan_buddy_allocator_free_allocator (GskVulkanAllocator *allocator)
{
GskVulkanBuddyAllocator *self = (GskVulkanBuddyAllocator *) allocator;
gsize i;
if (self->cache.vk_memory)
gsk_vulkan_free (self->allocator, &self->cache);
for (i = 0; i < N_SUBDIVISIONS; i++)
{
gsk_vulkan_allocation_list_clear (&self->free_lists[i]);
}
gsk_vulkan_allocator_free (self->allocator);
g_free (self);
}
VkDeviceMemory
gsk_vulkan_memory_get_device_memory (GskVulkanMemory *self)
/* must not be 0:
* gets exponent for next power of 2 that's >= num.
* So num=1234 gets 11, because 2048 = 2^11 */
static gsize
find_slot (gsize num)
{
return self->vk_memory;
return g_bit_storage (num - 1);
}
gboolean
gsk_vulkan_memory_can_map (GskVulkanMemory *self,
gboolean fast)
static void
gsk_vulkan_buddy_allocator_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize align,
GskVulkanAllocation *alloc)
{
if (!(self->vk_memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT))
return FALSE;
GskVulkanBuddyAllocator *self = (GskVulkanBuddyAllocator *) allocator;
gsize slot;
int i;
/* FIXME: no support implemented for this */
if (!(self->vk_memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
return FALSE;
size = MAX (size, align);
if (!fast)
return TRUE;
slot = find_slot (size);
if (slot >= self->block_size_slot)
{
gsk_vulkan_alloc (self->allocator, size, align, alloc);
return;
}
if (!(self->vk_memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT))
return FALSE;
slot = MIN (self->block_size_slot - slot, N_SUBDIVISIONS) - 1;
for (i = slot; i >= 0; i--)
{
if (gsk_vulkan_allocation_list_get_size (&self->free_lists[i]) > 0)
break;
}
if (i < 0)
{
if (self->cache.vk_memory)
{
*alloc = self->cache;
self->cache.vk_memory = NULL;
}
else
{
/* We force alignment to our size, so that we can use offset
* to find the buddy allocation.
*/
gsk_vulkan_alloc (self->allocator, 1 << self->block_size_slot, 1 << self->block_size_slot, alloc);
}
}
else
{
gsize n = gsk_vulkan_allocation_list_get_size (&self->free_lists[i]);
*alloc = *gsk_vulkan_allocation_list_get (&self->free_lists[i], n - 1);
gsk_vulkan_allocation_list_set_size (&self->free_lists[i], n - 1);
}
return TRUE;
while (i != slot)
{
i++;
alloc->size >>= 1;
gsk_vulkan_allocation_list_append (&self->free_lists[i], alloc);
alloc->offset += alloc->size;
if (alloc->map)
alloc->map += alloc->size;
}
g_assert (alloc->size >= size);
}
guchar *
gsk_vulkan_memory_map (GskVulkanMemory *self)
static void
gsk_vulkan_buddy_allocator_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc)
{
void *data;
GskVulkanBuddyAllocator *self = (GskVulkanBuddyAllocator *) allocator;
gsize slot, i, n;
g_assert (gsk_vulkan_memory_can_map (self, FALSE));
slot = find_slot (alloc->size);
if (slot >= self->block_size_slot)
{
gsk_vulkan_free (self->allocator, alloc);
return;
}
GSK_VK_CHECK (vkMapMemory, gdk_vulkan_context_get_device (self->vulkan),
self->vk_memory,
0,
self->size,
0,
&data);
return data;
slot = MIN (self->block_size_slot - slot, N_SUBDIVISIONS) - 1;
restart:
n = gsk_vulkan_allocation_list_get_size (&self->free_lists[slot]);
for (i = 0; i < n; i++)
{
GskVulkanAllocation *maybe_buddy = gsk_vulkan_allocation_list_index (&self->free_lists[slot], i);
if (maybe_buddy->vk_memory == alloc->vk_memory &&
maybe_buddy->offset == (alloc->offset ^ alloc->size))
{
if (i < n - 1)
*maybe_buddy = *gsk_vulkan_allocation_list_get (&self->free_lists[slot], n - 1);
gsk_vulkan_allocation_list_set_size (&self->free_lists[slot], n - 1);
if (alloc->map && alloc->offset & alloc->size)
alloc->map -= alloc->size;
alloc->offset &= ~alloc->size;
alloc->size <<= 1;
if (slot == 0)
{
if (self->cache.vk_memory == NULL)
self->cache = *alloc;
else
gsk_vulkan_free (self->allocator, alloc);
return;
}
else
{
slot--;
/* no idea how to make this look good with loops */
goto restart;
}
}
}
gsk_vulkan_allocation_list_append (&self->free_lists[slot], alloc);
}
void
gsk_vulkan_memory_unmap (GskVulkanMemory *self)
GskVulkanAllocator *
gsk_vulkan_buddy_allocator_new (GskVulkanAllocator *allocator,
gsize block_size)
{
vkUnmapMemory (gdk_vulkan_context_get_device (self->vulkan),
self->vk_memory);
GskVulkanBuddyAllocator *self;
gsize i;
self = g_new0 (GskVulkanBuddyAllocator, 1);
self->allocator_class.free_allocator = gsk_vulkan_buddy_allocator_free_allocator;
self->allocator_class.alloc = gsk_vulkan_buddy_allocator_alloc;
self->allocator_class.free = gsk_vulkan_buddy_allocator_free;
self->allocator = allocator;
self->block_size_slot = find_slot (block_size);
for (i = 0; i < N_SUBDIVISIONS; i++)
{
gsk_vulkan_allocation_list_init (&self->free_lists[i]);
}
return (GskVulkanAllocator *) self;
}
/* }}} */
/* {{{ stats allocator ***/
typedef struct _GskVulkanStatsAllocator GskVulkanStatsAllocator;
struct _GskVulkanStatsAllocator
{
GskVulkanAllocator allocator_class;
GskVulkanAllocator *allocator;
gsize n_alloc;
gsize n_free;
gsize n_bytes_requested;
gsize n_bytes_allocated;
gsize n_bytes_freed;
};
static void
gsk_vulkan_stats_allocator_dump_stats (GskVulkanStatsAllocator *self,
const char *reason)
{
g_printerr ("%s\n", reason);
g_printerr (" %zu bytes requested in %zu allocations\n", self->n_bytes_requested, self->n_alloc);
g_printerr (" %zu bytes allocated (%.2f%% overhead)\n", self->n_bytes_allocated,
(self->n_bytes_allocated - self->n_bytes_requested) * 100. / self->n_bytes_requested);
g_printerr (" %zu bytes freed in %zu frees\n", self->n_bytes_freed , self->n_free);
g_printerr (" %zu bytes remaining in %zu allocations\n",
self->n_bytes_allocated - self->n_bytes_freed, self->n_alloc - self->n_free);
}
static void
gsk_vulkan_stats_allocator_free_allocator (GskVulkanAllocator *allocator)
{
GskVulkanStatsAllocator *self = (GskVulkanStatsAllocator *) allocator;
g_assert (self->n_alloc == self->n_free);
g_assert (self->n_bytes_allocated == self->n_bytes_freed);
gsk_vulkan_allocator_free (self->allocator);
g_free (self);
}
static void
gsk_vulkan_stats_allocator_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize align,
GskVulkanAllocation *alloc)
{
GskVulkanStatsAllocator *self = (GskVulkanStatsAllocator *) allocator;
gsk_vulkan_alloc (self->allocator, size, align, alloc);
self->n_alloc++;
self->n_bytes_requested += size;
self->n_bytes_allocated += alloc->size;
gsk_vulkan_stats_allocator_dump_stats (self, "alloc()");
}
static void
gsk_vulkan_stats_allocator_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc)
{
GskVulkanStatsAllocator *self = (GskVulkanStatsAllocator *) allocator;
self->n_free++;
self->n_bytes_freed += alloc->size;
gsk_vulkan_free (self->allocator, alloc);
gsk_vulkan_stats_allocator_dump_stats (self, "free()");
}
GskVulkanAllocator *
gsk_vulkan_stats_allocator_new (GskVulkanAllocator *allocator)
{
GskVulkanStatsAllocator *self;
self = g_new0 (GskVulkanStatsAllocator, 1);
self->allocator_class.free_allocator = gsk_vulkan_stats_allocator_free_allocator;
self->allocator_class.alloc = gsk_vulkan_stats_allocator_alloc;
self->allocator_class.free = gsk_vulkan_stats_allocator_free;
self->allocator = allocator;
return (GskVulkanAllocator *) self;
}
/* }}} */

View File

@@ -4,20 +4,71 @@
G_BEGIN_DECLS
#define GSK_VULKAN_MEMORY_MAPPABLE (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | \
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | \
VK_MEMORY_PROPERTY_HOST_CACHED_BIT)
typedef struct _GskVulkanAllocator GskVulkanAllocator;
typedef struct _GskVulkanAllocation GskVulkanAllocation;
typedef struct _GskVulkanMemory GskVulkanMemory;
GskVulkanMemory * gsk_vulkan_memory_new (GdkVulkanContext *context,
uint32_t allowed_types,
VkMemoryPropertyFlags properties,
gsize size);
void gsk_vulkan_memory_free (GskVulkanMemory *memory);
struct _GskVulkanAllocation
{
VkDeviceMemory vk_memory;
guchar *map;
VkDeviceSize offset;
VkDeviceSize size;
};
VkDeviceMemory gsk_vulkan_memory_get_device_memory (GskVulkanMemory *self);
struct _GskVulkanAllocator
{
void (* free_allocator) (GskVulkanAllocator *allocator);
gboolean gsk_vulkan_memory_can_map (GskVulkanMemory *self,
gboolean fast);
guchar * gsk_vulkan_memory_map (GskVulkanMemory *self);
void gsk_vulkan_memory_unmap (GskVulkanMemory *self);
void (* alloc) (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize alignment,
GskVulkanAllocation *out_alloc);
void (* free) (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc);
};
static inline void gsk_vulkan_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize alignment,
GskVulkanAllocation *out_alloc);
static inline void gsk_vulkan_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc);
static inline void gsk_vulkan_allocator_free (GskVulkanAllocator *allocator);
GskVulkanAllocator * gsk_vulkan_direct_allocator_new (VkDevice device,
uint32_t vk_type_index,
const VkMemoryType *vk_type);
GskVulkanAllocator * gsk_vulkan_buddy_allocator_new (GskVulkanAllocator *allocator,
gsize block_size);
GskVulkanAllocator * gsk_vulkan_stats_allocator_new (GskVulkanAllocator *allocator);
static inline void
gsk_vulkan_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize alignment,
GskVulkanAllocation *out_alloc)
{
allocator->alloc (allocator, size, alignment, out_alloc);
}
static inline void
gsk_vulkan_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc)
{
allocator->free (allocator, alloc);
}
static inline void
gsk_vulkan_allocator_free (GskVulkanAllocator *allocator)
{
allocator->free_allocator (allocator);
}
G_END_DECLS

View File

@@ -26,6 +26,7 @@
#define DESCRIPTOR_POOL_MAXITEMS 50000
#define VERTEX_BUFFER_SIZE_STEP 128 * 1024 /* 128kB */
#define STORAGE_BUFFER_SIZE_STEP 32 * 1024
#define GDK_ARRAY_NAME gsk_descriptor_image_infos
#define GDK_ARRAY_TYPE_NAME GskDescriptorImageInfos
@@ -49,6 +50,7 @@ struct _GskVulkanRender
{
GskRenderer *renderer;
GdkVulkanContext *vulkan;
GskVulkanDevice *device;
graphene_rect_t viewport;
cairo_region_t *clip;
@@ -193,22 +195,24 @@ gsk_vulkan_render_setup (GskVulkanRender *self,
GskVulkanRender *
gsk_vulkan_render_new (GskRenderer *renderer,
GdkVulkanContext *context)
GdkVulkanContext *context,
GskVulkanDevice *device)
{
GskVulkanRender *self;
VkDevice device;
VkDevice vk_device;
self = g_new0 (GskVulkanRender, 1);
self->vulkan = context;
self->device = device;
self->renderer = renderer;
gsk_descriptor_image_infos_init (&self->descriptor_images);
gsk_descriptor_buffer_infos_init (&self->descriptor_buffers);
device = gdk_vulkan_context_get_device (self->vulkan);
vk_device = gdk_vulkan_context_get_device (self->vulkan);
self->command_pool = gsk_vulkan_command_pool_new (self->vulkan);
GSK_VK_CHECK (vkCreateFence, device,
GSK_VK_CHECK (vkCreateFence, vk_device,
&(VkFenceCreateInfo) {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT
@@ -216,7 +220,7 @@ gsk_vulkan_render_new (GskRenderer *renderer,
NULL,
&self->fence);
GSK_VK_CHECK (vkCreateDescriptorPool, device,
GSK_VK_CHECK (vkCreateDescriptorPool, vk_device,
&(VkDescriptorPoolCreateInfo) {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT,
@@ -236,7 +240,7 @@ gsk_vulkan_render_new (GskRenderer *renderer,
NULL,
&self->descriptor_pool);
GSK_VK_CHECK (vkCreateDescriptorSetLayout, device,
GSK_VK_CHECK (vkCreateDescriptorSetLayout, vk_device,
&(VkDescriptorSetLayoutCreateInfo) {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
@@ -262,7 +266,7 @@ gsk_vulkan_render_new (GskRenderer *renderer,
NULL,
&self->descriptor_set_layouts[0]);
GSK_VK_CHECK (vkCreateDescriptorSetLayout, device,
GSK_VK_CHECK (vkCreateDescriptorSetLayout, vk_device,
&(VkDescriptorSetLayoutCreateInfo) {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
@@ -288,7 +292,7 @@ gsk_vulkan_render_new (GskRenderer *renderer,
NULL,
&self->descriptor_set_layouts[1]);
GSK_VK_CHECK (vkCreatePipelineLayout, device,
GSK_VK_CHECK (vkCreatePipelineLayout, vk_device,
&(VkPipelineLayoutCreateInfo) {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = G_N_ELEMENTS (self->descriptor_set_layouts),
@@ -299,7 +303,7 @@ gsk_vulkan_render_new (GskRenderer *renderer,
NULL,
&self->pipeline_layout);
GSK_VK_CHECK (vkCreateSampler, device,
GSK_VK_CHECK (vkCreateSampler, vk_device,
&(VkSamplerCreateInfo) {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_LINEAR,
@@ -314,7 +318,7 @@ gsk_vulkan_render_new (GskRenderer *renderer,
NULL,
&self->samplers[GSK_VULKAN_SAMPLER_DEFAULT]);
GSK_VK_CHECK (vkCreateSampler, device,
GSK_VK_CHECK (vkCreateSampler, vk_device,
&(VkSamplerCreateInfo) {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_LINEAR,
@@ -329,7 +333,7 @@ gsk_vulkan_render_new (GskRenderer *renderer,
NULL,
&self->samplers[GSK_VULKAN_SAMPLER_REPEAT]);
GSK_VK_CHECK (vkCreateSampler, device,
GSK_VK_CHECK (vkCreateSampler, vk_device,
&(VkSamplerCreateInfo) {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_NEAREST,
@@ -726,13 +730,9 @@ gsk_vulkan_render_ensure_storage_buffer (GskVulkanRender *self)
return;
if (self->storage_buffer == NULL)
{
self->storage_buffer = gsk_vulkan_buffer_new_storage (self->vulkan,
/* random */
sizeof (float) * 1024 * 1024);
}
self->storage_buffer = gsk_vulkan_buffer_new_storage (self->device, STORAGE_BUFFER_SIZE_STEP);
self->storage_buffer_memory = gsk_vulkan_buffer_map (self->storage_buffer);
self->storage_buffer_memory = gsk_vulkan_buffer_get_data (self->storage_buffer);
if (gsk_vulkan_render_get_buffer_descriptor (self, self->storage_buffer) != 0)
{
@@ -780,9 +780,20 @@ gsk_vulkan_render_get_buffer_memory (GskVulkanRender *self,
gsk_vulkan_render_ensure_storage_buffer (self);
self->storage_buffer_used = round_up (self->storage_buffer_used, alignment);
if (self->storage_buffer_used + size > gsk_vulkan_buffer_get_size (self->storage_buffer))
{
gsize larger_size = 1 << g_bit_storage (self->storage_buffer_used + size - 1);
GskVulkanBuffer *larger_buffer = gsk_vulkan_buffer_new_storage (self->device, larger_size);
guchar *larger_memory = gsk_vulkan_buffer_get_data (larger_buffer);
memcpy (larger_memory, self->storage_buffer_memory, self->storage_buffer_used);
gsk_vulkan_buffer_free (self->storage_buffer);
self->storage_buffer = larger_buffer;
self->storage_buffer_memory = larger_memory;
gsk_descriptor_buffer_infos_index (&self->descriptor_buffers, 0)->buffer = gsk_vulkan_buffer_get_buffer (larger_buffer);
}
result = self->storage_buffer_memory + self->storage_buffer_used;
*out_offset = self->storage_buffer_used / sizeof (float);
self->storage_buffer_used += size;
return result;
@@ -805,7 +816,6 @@ gsk_vulkan_render_prepare_descriptor_sets (GskVulkanRender *self)
if (self->storage_buffer_memory)
{
gsk_vulkan_buffer_unmap (self->storage_buffer);
self->storage_buffer_memory = NULL;
self->storage_buffer_used = 0;
}
@@ -878,14 +888,13 @@ gsk_vulkan_render_collect_vertex_buffer (GskVulkanRender *self)
g_clear_pointer (&self->vertex_buffer, gsk_vulkan_buffer_free);
if (self->vertex_buffer == NULL)
self->vertex_buffer = gsk_vulkan_buffer_new (self->vulkan, round_up (n_bytes, VERTEX_BUFFER_SIZE_STEP));
self->vertex_buffer = gsk_vulkan_buffer_new (self->device, round_up (n_bytes, VERTEX_BUFFER_SIZE_STEP));
data = gsk_vulkan_buffer_map (self->vertex_buffer);
data = gsk_vulkan_buffer_get_data (self->vertex_buffer);
for (op = self->first_op; op; op = op->next)
{
gsk_vulkan_op_collect_vertex_data (op, data);
}
gsk_vulkan_buffer_unmap (self->vertex_buffer);
}
static void
@@ -1095,6 +1104,12 @@ gsk_vulkan_render_get_context (GskVulkanRender *self)
return self->vulkan;
}
GskVulkanDevice *
gsk_vulkan_render_get_device (GskVulkanRender *self)
{
return self->device;
}
gpointer
gsk_vulkan_render_alloc_op (GskVulkanRender *self,
gsize size)

View File

@@ -6,7 +6,7 @@
#include "gskprivate.h"
#include "gskrendererprivate.h"
#include "gskrendernodeprivate.h"
#include "gskvulkanbufferprivate.h"
#include "gskvulkandeviceprivate.h"
#include "gskvulkanimageprivate.h"
#include "gskvulkanprivate.h"
#include "gskvulkanrenderprivate.h"
@@ -52,6 +52,7 @@ struct _GskVulkanRenderer
GskRenderer parent_instance;
GdkVulkanContext *vulkan;
GskVulkanDevice *device;
guint n_targets;
GskVulkanImage **targets;
@@ -162,7 +163,7 @@ gsk_vulkan_renderer_update_images_cb (GdkVulkanContext *context,
for (i = 0; i < self->n_targets; i++)
{
self->targets[i] = gsk_vulkan_image_new_for_swapchain (self->vulkan,
self->targets[i] = gsk_vulkan_image_new_for_swapchain (self->device,
gdk_vulkan_context_get_image (context, i),
gdk_vulkan_context_get_image_format (self->vulkan),
width, height);
@@ -184,7 +185,7 @@ gsk_vulkan_renderer_get_render (GskVulkanRenderer *self)
{
if (self->renders[i] == NULL)
{
self->renders[i] = gsk_vulkan_render_new (GSK_RENDERER (self), self->vulkan);
self->renders[i] = gsk_vulkan_render_new (GSK_RENDERER (self), self->vulkan, self->device);
return self->renders[i];
}
@@ -193,6 +194,7 @@ gsk_vulkan_renderer_get_render (GskVulkanRenderer *self)
return self->renders[i];
}
g_print ("gotta wait, oh no!\n");
GSK_VK_CHECK (vkWaitForFences, device,
G_N_ELEMENTS (fences),
fences,
@@ -216,13 +218,20 @@ gsk_vulkan_renderer_realize (GskRenderer *renderer,
if (self->vulkan == NULL)
return FALSE;
self->device = gsk_vulkan_device_get_for_display (gdk_draw_context_get_display (GDK_DRAW_CONTEXT (self->vulkan)), error);
if (self->device == NULL)
{
g_clear_object (&self->vulkan);
return FALSE;
}
g_signal_connect (self->vulkan,
"images-updated",
G_CALLBACK (gsk_vulkan_renderer_update_images_cb),
self);
gsk_vulkan_renderer_update_images_cb (self->vulkan, self);
self->glyph_cache = gsk_vulkan_glyph_cache_new (self->vulkan);
self->glyph_cache = gsk_vulkan_glyph_cache_new (self->device);
return TRUE;
}
@@ -235,6 +244,7 @@ gsk_vulkan_renderer_unrealize (GskRenderer *renderer)
guint i;
g_clear_object (&self->glyph_cache);
g_clear_object (&self->device);
for (l = self->textures; l; l = l->next)
{
@@ -299,13 +309,13 @@ gsk_vulkan_renderer_render_texture (GskRenderer *renderer,
gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time);
#endif
render = gsk_vulkan_render_new (renderer, self->vulkan);
render = gsk_vulkan_render_new (renderer, self->vulkan, self->device);
rounded_viewport = GRAPHENE_RECT_INIT (viewport->origin.x,
viewport->origin.y,
ceil (viewport->size.width),
ceil (viewport->size.height));
image = gsk_vulkan_image_new_for_offscreen (self->vulkan,
image = gsk_vulkan_image_new_for_offscreen (self->device,
gdk_vulkan_context_get_offscreen_format (self->vulkan,
gsk_render_node_get_preferred_depth (root)),
rounded_viewport.size.width,

View File

@@ -8,6 +8,7 @@
#include "gskrenderer.h"
#include "gskrendererprivate.h"
#include "gskroundedrectprivate.h"
#include "gskstrokeprivate.h"
#include "gsktransform.h"
#include "gskvulkanblendmodeopprivate.h"
#include "gskvulkanbluropprivate.h"
@@ -18,6 +19,7 @@
#include "gskvulkancoloropprivate.h"
#include "gskvulkanconvertopprivate.h"
#include "gskvulkancrossfadeopprivate.h"
#include "gskvulkanfillopprivate.h"
#include "gskvulkanglyphopprivate.h"
#include "gskvulkaninsetshadowopprivate.h"
#include "gskvulkanlineargradientopprivate.h"
@@ -30,6 +32,7 @@
#include "gskvulkanoutsetshadowopprivate.h"
#include "gskvulkanpushconstantsopprivate.h"
#include "gskvulkanscissoropprivate.h"
#include "gskvulkanstrokeopprivate.h"
#include "gskvulkantextureopprivate.h"
#include "gskvulkanuploadopprivate.h"
#include "gskprivate.h"
@@ -72,6 +75,33 @@ gsk_vulkan_render_pass_free (GskVulkanRenderPass *self)
g_free (self);
}
static gboolean
gsk_vulkan_render_pass_is_color_node (GskRenderNode *node,
GdkRGBA *out_color)
{
GskRenderNodeType node_type;
node_type = gsk_render_node_get_node_type (node);
if (node_type == GSK_COLOR_NODE)
{
*out_color = *gsk_color_node_get_color (node);
return TRUE;
}
else if (node_type == GSK_OPACITY_NODE)
{
if (!gsk_vulkan_render_pass_is_color_node (gsk_opacity_node_get_child (node),
out_color))
return FALSE;
out_color->alpha *= gsk_opacity_node_get_opacity (node);
return TRUE;
}
else
{
return FALSE;
}
}
static void
gsk_vulkan_render_pass_append_scissor (GskVulkanRender *render,
GskRenderNode *node,
@@ -158,7 +188,7 @@ gsk_vulkan_render_pass_upload_texture (GskVulkanRender *render,
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
better_image = gsk_vulkan_image_new_for_offscreen (gsk_vulkan_render_get_context (render),
better_image = gsk_vulkan_image_new_for_offscreen (gsk_vulkan_render_get_device (render),
gdk_texture_get_format (texture),
width, height);
gsk_vulkan_render_pass_begin_op (render,
@@ -1158,6 +1188,7 @@ gsk_vulkan_render_pass_add_mask_node (GskVulkanRenderPass *self,
GskVulkanImage *source_image, *mask_image;
graphene_rect_t source_tex_rect, mask_tex_rect;
GskRenderNode *source, *mask;
GdkRGBA source_color;
GskMaskMode mode;
mode = gsk_mask_node_get_mask_mode (node);
@@ -1178,7 +1209,7 @@ gsk_vulkan_render_pass_add_mask_node (GskVulkanRenderPass *self,
/* Use the glyph shader as an optimization */
if (mode == GSK_MASK_MODE_ALPHA &&
gsk_render_node_get_node_type (source) == GSK_COLOR_NODE)
gsk_vulkan_render_pass_is_color_node (source, &source_color))
{
graphene_rect_t bounds;
if (graphene_rect_intersection (&source->bounds, &mask->bounds, &bounds))
@@ -1188,7 +1219,7 @@ gsk_vulkan_render_pass_add_mask_node (GskVulkanRenderPass *self,
&bounds,
&state->offset,
&mask_tex_rect,
gsk_color_node_get_color (source));
&source_color);
return TRUE;
}
@@ -1213,6 +1244,122 @@ gsk_vulkan_render_pass_add_mask_node (GskVulkanRenderPass *self,
return TRUE;
}
static inline gboolean
gsk_vulkan_render_pass_add_fill_node (GskVulkanRenderPass *self,
GskVulkanRender *render,
const GskVulkanParseState *state,
GskRenderNode *node)
{
GskRenderNode *child;
graphene_rect_t clipped, child_tex_rect;
GskVulkanImage *child_image, *mask_image;
graphene_matrix_t projection;
GdkRGBA color;
int width, height;
child = gsk_fill_node_get_child (node);
if (gsk_vulkan_render_pass_is_color_node (child, &color))
{
gsk_vulkan_fill_op (render,
gsk_vulkan_clip_get_shader_clip (&state->clip, &state->offset, &node->bounds),
&state->offset,
&node->bounds,
gsk_fill_node_get_path (node),
gsk_fill_node_get_fill_rule (node),
&color);
return TRUE;
}
graphene_rect_offset_r (&state->clip.rect.bounds, - state->offset.x, - state->offset.y, &clipped);
graphene_rect_intersection (&clipped, &node->bounds, &clipped);
if (clipped.size.width == 0 || clipped.size.height == 0)
return TRUE;
child_image = gsk_vulkan_render_pass_get_node_as_image (self,
render,
state,
child,
&child_tex_rect);
if (child_image == NULL)
return TRUE;
width = ceil (graphene_vec2_get_x (&state->scale) * clipped.size.width);
height = ceil (graphene_vec2_get_y (&state->scale) * clipped.size.height);
mask_image = gsk_vulkan_image_new_for_offscreen (gsk_vulkan_render_get_device (render),
gdk_memory_depth_get_alpha_format (gsk_render_node_get_preferred_depth (child)),
width, height);
gsk_vulkan_render_pass_begin_op (render,
mask_image,
&(cairo_rectangle_int_t) { 0, 0, width, height },
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
gsk_vulkan_scissor_op (render, &(cairo_rectangle_int_t) { 0, 0, width, height });
graphene_matrix_init_ortho (&projection,
0, width,
0, height,
2 * ORTHO_NEAR_PLANE - ORTHO_FAR_PLANE,
ORTHO_FAR_PLANE);
gsk_vulkan_push_constants_op (render, &state->scale, &projection, &GSK_ROUNDED_RECT_INIT_FROM_RECT (clipped));
gsk_vulkan_fill_op (render,
GSK_VULKAN_SHADER_CLIP_NONE,
&GRAPHENE_POINT_INIT (- clipped.origin.x, - clipped.origin.y),
&node->bounds,
gsk_fill_node_get_path (node),
gsk_fill_node_get_fill_rule (node),
&(GdkRGBA) { 1.0, 1.0, 1.0, 1.0 });
gsk_vulkan_render_pass_end_op (render,
mask_image,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
gsk_vulkan_mask_op (render,
gsk_vulkan_clip_get_shader_clip (&state->clip, &state->offset, &node->bounds),
&state->offset,
child_image,
&child->bounds,
&child_tex_rect,
mask_image,
&node->bounds,
&clipped,
GSK_MASK_MODE_ALPHA);
g_object_unref (mask_image);
return TRUE;
}
static inline gboolean
gsk_vulkan_render_pass_add_stroke_node (GskVulkanRenderPass *self,
GskVulkanRender *render,
const GskVulkanParseState *state,
GskRenderNode *node)
{
GskRenderNode *child;
const GskStroke *stroke;
GdkRGBA color;
child = gsk_stroke_node_get_child (node);
stroke = gsk_stroke_node_get_stroke (node);
if (gsk_vulkan_render_pass_is_color_node (child, &color))
{
gsk_vulkan_stroke_op (render,
gsk_vulkan_clip_get_shader_clip (&state->clip, &state->offset, &node->bounds),
&state->offset,
&node->bounds,
gsk_stroke_node_get_path (node),
stroke->line_width,
stroke->line_cap,
stroke->line_join,
stroke->miter_limit,
&color);
return TRUE;
}
return TRUE;
}
static inline gboolean
gsk_vulkan_render_pass_add_debug_node (GskVulkanRenderPass *self,
GskVulkanRender *render,
@@ -1260,6 +1407,8 @@ static const GskVulkanRenderPassNodeFunc nodes_vtable[] = {
[GSK_GL_SHADER_NODE] = NULL,
[GSK_TEXTURE_SCALE_NODE] = gsk_vulkan_render_pass_add_texture_scale_node,
[GSK_MASK_NODE] = gsk_vulkan_render_pass_add_mask_node,
[GSK_FILL_NODE] = gsk_vulkan_render_pass_add_fill_node,
[GSK_STROKE_NODE] = gsk_vulkan_render_pass_add_stroke_node,
};
static void

View File

@@ -276,16 +276,15 @@ gsk_vulkan_render_pass_op_offscreen (GskVulkanRender *render,
GskRenderNode *node)
{
GskVulkanRenderPass *render_pass;
GdkVulkanContext *context;
GskVulkanImage *image;
int width, height;
width = ceil (graphene_vec2_get_x (scale) * viewport->size.width);
height = ceil (graphene_vec2_get_y (scale) * viewport->size.height);
context = gsk_vulkan_render_get_context (render);
image = gsk_vulkan_image_new_for_offscreen (context,
gdk_vulkan_context_get_offscreen_format (context,
image = gsk_vulkan_image_new_for_offscreen (gsk_vulkan_render_get_device (render),
gdk_vulkan_context_get_offscreen_format (
gsk_vulkan_render_get_context (render),
gsk_render_node_get_preferred_depth (node)),
width, height);

View File

@@ -24,7 +24,8 @@ typedef void (* GskVulkanDownloadFunc) (gpointe
gsize stride);
GskVulkanRender * gsk_vulkan_render_new (GskRenderer *renderer,
GdkVulkanContext *context);
GdkVulkanContext *context,
GskVulkanDevice *device);
void gsk_vulkan_render_free (GskVulkanRender *self);
gboolean gsk_vulkan_render_is_busy (GskVulkanRender *self);
@@ -38,6 +39,7 @@ void gsk_vulkan_render_render (GskVulk
GskRenderer * gsk_vulkan_render_get_renderer (GskVulkanRender *self);
GdkVulkanContext * gsk_vulkan_render_get_context (GskVulkanRender *self);
GskVulkanDevice * gsk_vulkan_render_get_device (GskVulkanRender *self);
gpointer gsk_vulkan_render_alloc_op (GskVulkanRender *self,
gsize size);

View File

@@ -0,0 +1,141 @@
#include "config.h"
#include "gskvulkanstrokeopprivate.h"
#include "gskvulkanprivate.h"
#include "gskvulkanshaderopprivate.h"
#include "gskpathprivate.h"
#include "vulkan/resources/stroke.vert.h"
typedef struct _GskVulkanStrokeOp GskVulkanStrokeOp;
struct _GskVulkanStrokeOp
{
GskVulkanShaderOp op;
graphene_point_t offset;
graphene_rect_t rect;
GskPath *path;
float line_width;
GskLineCap line_cap;
GskLineJoin line_join;
float miter_limit;
GdkRGBA color;
gsize buffer_offset;
};
static void
gsk_vulkan_stroke_op_finish (GskVulkanOp *op)
{
GskVulkanStrokeOp *self = (GskVulkanStrokeOp *) op;
gsk_path_unref (self->path);
}
static void
gsk_vulkan_stroke_op_print (GskVulkanOp *op,
GString *string,
guint indent)
{
GskVulkanStrokeOp *self = (GskVulkanStrokeOp *) op;
print_indent (string, indent);
print_rect (string, &self->rect);
g_string_append_printf (string, "stroke ");
print_newline (string);
}
static void
gsk_vulkan_stroke_op_collect_vertex_data (GskVulkanOp *op,
guchar *data)
{
GskVulkanStrokeOp *self = (GskVulkanStrokeOp *) op;
GskVulkanStrokeInstance *instance = (GskVulkanStrokeInstance *) (data + ((GskVulkanShaderOp *) op)->vertex_offset);
gsk_vulkan_rect_to_float (&self->rect, instance->rect);
gsk_vulkan_rgba_to_float (&self->color, instance->color);
gsk_vulkan_point_to_float (&self->offset, instance->offset);
instance->points_id = self->buffer_offset;
instance->line_width = self->line_width;
instance->line_cap = self->line_cap;
instance->line_join = self->line_join;
instance->miter_limit = self->miter_limit;
}
static void
gsk_vulkan_stroke_op_reserve_descriptor_sets (GskVulkanOp *op,
GskVulkanRender *render)
{
GskVulkanStrokeOp *self = (GskVulkanStrokeOp *) op;
const GskContour *contour;
gsize size, i, n;
guchar *mem;
size = sizeof (guint32);
n = gsk_path_get_n_contours (self->path);
for (i = 0; i < n; i++)
{
contour = gsk_path_get_contour (self->path, i);
size += gsk_contour_get_shader_size (contour);
}
mem = gsk_vulkan_render_get_buffer_memory (render,
size,
G_ALIGNOF (float),
&self->buffer_offset);
*(guint32*) mem = n;
mem += sizeof (guint32);
for (i = 0; i < n; i++)
{
contour = gsk_path_get_contour (self->path, i);
gsk_contour_to_shader (contour, mem);
mem += gsk_contour_get_shader_size (contour);
}
}
static const GskVulkanShaderOpClass GSK_VULKAN_STROKE_OP_CLASS = {
{
GSK_VULKAN_OP_SIZE (GskVulkanStrokeOp),
GSK_VULKAN_STAGE_SHADER,
gsk_vulkan_stroke_op_finish,
gsk_vulkan_stroke_op_print,
gsk_vulkan_shader_op_count_vertex_data,
gsk_vulkan_stroke_op_collect_vertex_data,
gsk_vulkan_stroke_op_reserve_descriptor_sets,
gsk_vulkan_shader_op_command
},
"stroke",
0,
&gsk_vulkan_stroke_info,
};
void
gsk_vulkan_stroke_op (GskVulkanRender *render,
GskVulkanShaderClip clip,
const graphene_point_t *offset,
const graphene_rect_t *rect,
GskPath *path,
float line_width,
GskLineCap line_cap,
GskLineJoin line_join,
float miter_limit,
const GdkRGBA *color)
{
GskVulkanStrokeOp *self;
self = (GskVulkanStrokeOp *) gsk_vulkan_shader_op_alloc (render, &GSK_VULKAN_STROKE_OP_CLASS, clip, NULL);
self->offset = *offset;
self->rect = *rect;
self->path = gsk_path_ref (path);
self->line_width = line_width;
self->line_cap = line_cap;
self->line_join = line_join;
self->miter_limit = miter_limit;
self->color = *color;
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "gskvulkanopprivate.h"
#include "gskenums.h"
G_BEGIN_DECLS
void gsk_vulkan_stroke_op (GskVulkanRender *render,
GskVulkanShaderClip clip,
const graphene_point_t *offset,
const graphene_rect_t *rect,
GskPath *path,
float line_width,
GskLineCap line_cap,
GskLineJoin line_join,
float miter_limit,
const GdkRGBA *color);
G_END_DECLS

View File

@@ -38,15 +38,13 @@ gsk_vulkan_upload_op_command_with_area (GskVulkanOp *op,
guchar *data;
stride = area->width * gdk_memory_format_bytes_per_pixel (gsk_vulkan_image_get_format (image));
*buffer = gsk_vulkan_buffer_new_map (gsk_vulkan_render_get_context (render),
*buffer = gsk_vulkan_buffer_new_map (gsk_vulkan_render_get_device (render),
area->height * stride,
GSK_VULKAN_WRITE);
data = gsk_vulkan_buffer_map (*buffer);
data = gsk_vulkan_buffer_get_data (*buffer);
draw_func (op, data, stride);
gsk_vulkan_buffer_unmap (*buffer);
vkCmdPipelineBarrier (command_buffer,
VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
@@ -112,12 +110,11 @@ gsk_vulkan_upload_op_command (GskVulkanOp *op,
gsize stride;
guchar *data;
data = gsk_vulkan_image_try_map (image, &stride);
data = gsk_vulkan_image_get_data (image, &stride);
if (data)
{
draw_func (op, data, stride);
gsk_vulkan_image_unmap (image);
*buffer = NULL;
return op->next;
@@ -221,7 +218,7 @@ gsk_vulkan_upload_texture_op (GskVulkanRender *render,
self = (GskVulkanUploadTextureOp *) gsk_vulkan_op_alloc (render, &GSK_VULKAN_UPLOAD_TEXTURE_OP_CLASS);
self->texture = g_object_ref (texture);
self->image = gsk_vulkan_image_new_for_upload (gsk_vulkan_render_get_context (render),
self->image = gsk_vulkan_image_new_for_upload (gsk_vulkan_render_get_device (render),
gdk_texture_get_format (texture),
gdk_texture_get_width (texture),
gdk_texture_get_height (texture));
@@ -338,7 +335,7 @@ gsk_vulkan_upload_cairo_op (GskVulkanRender *render,
self = (GskVulkanUploadCairoOp *) gsk_vulkan_op_alloc (render, &GSK_VULKAN_UPLOAD_CAIRO_OP_CLASS);
self->node = gsk_render_node_ref (node);
self->image = gsk_vulkan_image_new_for_upload (gsk_vulkan_render_get_context (render),
self->image = gsk_vulkan_image_new_for_upload (gsk_vulkan_render_get_device (render),
GDK_MEMORY_DEFAULT,
ceil (graphene_vec2_get_x (scale) * viewport->size.width),
ceil (graphene_vec2_get_y (scale) * viewport->size.height));

View File

@@ -1,4 +1,5 @@
#version 420 core
#version 450
#extension GL_EXT_debug_printf : enable
#include "clip.frag.glsl"
#include "rect.frag.glsl"

View File

@@ -6,4 +6,5 @@ layout(set = 1, binding = 0) readonly buffer FloatBuffers {
#define get_sampler(id) textures[id]
#define get_buffer(id) buffers[id]
#define get_float(id) get_buffer(0).floats[id]
#define get_int(id) floatBitsToInt (get_float (id))

View File

@@ -0,0 +1,382 @@
#version 450
#include "common.frag.glsl"
#include "clip.frag.glsl"
#include "rect.frag.glsl"
#define GSK_FILL_RULE_WINDING 0
#define GSK_FILL_RULE_EVEN_ODD 1
#define GSK_PATH_MOVE 0
#define GSK_PATH_CLOSE 1
#define GSK_PATH_LINE 2
#define GSK_PATH_CURVE 3
#define GSK_PATH_CONIC 4
layout(location = 0) in vec2 in_pos;
layout(location = 1) in flat Rect in_rect;
layout(location = 2) in flat vec4 in_color;
layout(location = 3) in flat vec2 in_offset;
layout(location = 4) in flat int in_points_id;
layout(location = 5) in flat int in_fill_rule;
layout(location = 0) out vec4 color;
vec2
get_point (inout int idx)
{
vec2 p = vec2 (get_float (idx), get_float (idx + 1));
idx += 2;
p *= push.scale;
return p;
}
float
cross2d (vec2 v0, vec2 v1)
{
return v0.x * v1.y - v0.y * v1.x;
}
float
line_distance_squared (vec2 start,
vec2 end,
float d0)
{
vec2 e = end - start;
vec2 v = - start;
vec2 pq = v - e * clamp (dot (v, e) / dot(e, e), 0.0, 1.0);
return dot (pq, pq);
}
int
line_winding (vec2 start,
vec2 end)
{
// winding number from http://geomalgorithms.com/a03-_inclusion.html
float val3 = cross2d (start, end - start); //isLeft
bvec3 cond = bvec3 (start.y <= 0.0, end.y > 0.0, val3 > 0.0);
if (all (cond))
return 1;
else if (all (not (cond)))
return -1;
else
return 0;
}
// Modified from http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c
// Credits to Doublefresh for hinting there
int
solve_quadratic (vec2 coeffs,
inout vec2 roots)
{
// normal form: x^2 + px + q = 0
float p = coeffs[1] / 2.;
float q = coeffs[0];
float D = p * p - q;
if (D < 0.)
return 0;
roots[0] = -sqrt (D) - p;
roots[1] = sqrt (D) - p;
return 2;
}
vec3
sort3 (vec3 v)
{
vec2 t = vec2 ( min(v.x, v.y), max (v.x, v.y));
return vec3 (min (t.x, v.z), min (max (v.z, t.x), t.y), max (t.y, v.z));
}
//From Trisomie21
//But instead of his cancellation fix i'm using a newton iteration
int solve_cubic(vec3 coeffs, inout vec3 r){
float a = coeffs[2];
float b = coeffs[1];
float c = coeffs[0];
float p = b - a*a / 3.0;
float q = a * (2.0*a*a - 9.0*b) / 27.0 + c;
float p3 = p*p*p;
float d = q*q + 4.0*p3 / 27.0;
float offset = -a / 3.0;
if(d >= 0.0)
{
// Single solution
float z = sqrt(d);
float u = (-q + z) / 2.0;
float v = (-q - z) / 2.0;
u = sign (u) * pow (abs (u), 1.0 / 3.0);
v = sign (v) * pow (abs (v), 1.0 / 3.0);
r[0] = offset + u + v;
//Single newton iteration to account for cancellation
float f = ((r[0] + a) * r[0] + b) * r[0] + c;
float f1 = (3. * r[0] + 2. * a) * r[0] + b;
r[0] -= f / f1;
return 1;
}
float u = sqrt (-p / 3.0);
float v = acos (-sqrt ( -27.0 / p3) * q / 2.0) / 3.0;
float m = cos (v), n = sin (v)*1.732050808;
// Single newton iteration to account for cancellation
// (once for every root)
r[0] = offset + u * (m + m);
r[1] = offset - u * (n + m);
r[2] = offset + u * (n - m);
vec3 f = ((r + a) * r + b) * r + c;
vec3 f1 = (3. * r + 2. * a) * r + b;
r -= f / f1;
r = sort3 (r);
return 3;
}
float
cubic_bezier_normal_iteration (float t,
vec2 a0,
vec2 a1,
vec2 a2,
vec2 a3)
{
// Horner's method
vec2 a_2 = a2 + t * a3;
vec2 a_1 = a1 + t * a_2;
vec2 b_2 = a_2 + t * a3;
vec2 uv_to_p = a0 + t * a_1;
vec2 tang = a_1 + t*b_2;
float l_tang = dot (tang,tang);
return t - dot (tang, uv_to_p) / l_tang;
}
float
cubic_distance_squared (vec2 p0,
vec2 p1,
vec2 p2,
vec2 p3,
float d0)
{
if (dot (p0, p0) >= d0 &&
dot (p1, p1) >= d0 &&
dot (p2, p2) >= d0 &&
dot (p3, p3) >= d0)
return d0;
vec2 a3 = (-p0 + 3. * p1 - 3. * p2 + p3);
vec2 a2 = (3. * p0 - 6. * p1 + 3. * p2);
vec2 a1 = (-3. * p0 + 3. * p1);
vec2 a0 = p0;
float t;
vec3 params = vec3 (0, .5, 1);
for (int i = 0; i < 3; i++)
{
t = params[i];
for (int j = 0; j < 3; j++)
{
t = cubic_bezier_normal_iteration (t, a0, a1, a2, a3);
}
t = clamp (t, 0., 1.);
vec2 uv_to_p = ((a3 * t + a2) * t + a1) * t + a0;
d0 = min (d0, dot (uv_to_p, uv_to_p));
}
return d0;
}
int
cubic_winding (vec2 p0,
vec2 p1,
vec2 p2,
vec2 p3)
{
vec3 roots = vec3 (1e38);
int n_roots;
int w = 0;
if ((p0.y > 0 && p1.y > 0 && p2.y > 0 && p3.y > 0) ||
(p0.y <= 0 && p1.y <= 0 && p2.y <= 0 && p3.y <= 0))
return 0;
if (0 <= min (min (p0.x, p1.x), min (p2.x, p3.x)))
{
if (p0.y <= 0 && p3.y > 0)
return 1;
else if (p0.y > 0 && p3.y <= 0)
return -1;
else
return 0;
}
vec2 a3 = (-p0 + 3. * p1 - 3. * p2 + p3);
vec2 a2 = (3. * p0 - 6. * p1 + 3. * p2);
vec2 a1 = (-3. * p0 + 3. * p1);
vec2 a0 = p0;
if (abs (a3.y) < max (max (a0.y, a1.y), a2.y) * .001)
n_roots = solve_quadratic (vec2 (a0.y / a2.y, a1.y / a2.y), roots.xy);
else
n_roots = solve_cubic (vec3 (a0.y / a3.y, a1.y / a3.y, a2.y / a3.y), roots);
bool greater = p0.y > 0.0;
float last = 0;
float last_x = p0.x;
for (int i = 0; i < n_roots; i++)
{
if (roots[i] < 0. || roots[i] > 1.)
continue;
float t = 0.5 * (roots[i] + last);
bool new_greater = fma (fma (fma (a3.y, t, a2.y), t, a1.y), t, a0.y) > 0.0;
if (new_greater != greater)
{
greater = new_greater;
if (last_x > 0.0)
w += (greater ? 1 : -1);
}
last_x = fma (fma (fma (a3.x, roots[i], a2.x), roots[i], a1.x), roots[i], a0.x);
last = roots[i];
}
bool new_greater = p3.y > 0;
if (new_greater != greater)
{
if (last_x > 0.0)
w += (new_greater ? 1 : -1);
}
return w;
}
void
standard_contour_distance (vec2 p,
inout float d,
inout int path_idx,
inout int w)
{
int n_points = get_int (path_idx);
int ops_idx = path_idx + 1;
int pts_idx = ops_idx + n_points;
vec2 start = get_point (pts_idx) - p;
int op = GSK_PATH_MOVE;
for (int i = 1; i < n_points; i++)
{
op = get_int (ops_idx + i);
switch (op)
{
case GSK_PATH_MOVE:
discard;
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
{
vec2 end = get_point (pts_idx) - p;
d = line_distance_squared (start, end, d);
w += line_winding (start, end);
start = end;
}
break;
case GSK_PATH_CURVE:
{
vec2 p1 = get_point (pts_idx) - p;
vec2 p2 = get_point (pts_idx) - p;
vec2 p3 = get_point (pts_idx) - p;
d = cubic_distance_squared (start, p1, p2, p3, d);
w += cubic_winding (start, p1, p2, p3);
start = p3;
}
break;
case GSK_PATH_CONIC:
{
pts_idx += 4;
vec2 end = get_point (pts_idx) - p;
d = line_distance_squared (start, end, d);
w += line_winding (start, end);
start = end;
}
break;
}
}
if (op != GSK_PATH_CLOSE)
{
int tmp_idx = ops_idx + n_points;
vec2 end = get_point (tmp_idx) - p;
d = line_distance_squared (start, end, d);
w += line_winding (start, end);
}
path_idx = pts_idx;
}
float
path_distance (vec2 p)
{
int path_idx = in_points_id;
int n = get_int (path_idx);
path_idx++;
int w = 0;
float d = 1e38;
while (n-- > 0)
{
standard_contour_distance (p, d, path_idx, w);
}
if (in_fill_rule == GSK_FILL_RULE_WINDING)
{
if (w == 0)
return sqrt (d);
else if (abs (w) == 1)
return - sqrt (d);
else
return -1.0; /* not really, but good enough */
}
else if (in_fill_rule == GSK_FILL_RULE_EVEN_ODD)
{
return sqrt (d) * (w % 2 == 0 ? 1.0 : -1.0);
}
else
{
discard;
}
}
float
path_coverage (vec2 p)
{
float d = path_distance (p);
return clamp (0.5 - d, 0.0, 1.0);
}
void main()
{
float cov = path_coverage (in_pos - in_offset * push.scale);
float alpha = in_color.a * cov;
color = clip_scaled (in_pos, vec4(in_color.rgb, 1) * alpha);
}

View File

@@ -0,0 +1,28 @@
#version 450
#include "common.vert.glsl"
#include "rect.vert.glsl"
layout(location = 0) in vec4 in_rect;
layout(location = 1) in vec4 in_color;
layout(location = 2) in vec2 in_offset;
layout(location = 3) in int in_points_id;
layout(location = 4) in int in_fill_rule;
layout(location = 0) out vec2 out_pos;
layout(location = 1) out flat Rect out_rect;
layout(location = 2) out flat vec4 out_color;
layout(location = 3) out flat vec2 out_offset;
layout(location = 4) out flat int out_points_id;
layout(location = 5) out flat int out_fill_rule;
void main() {
Rect r = rect_from_gsk (in_rect + vec4(in_offset, 0.0, 0.0));
vec2 pos = set_position_from_rect (r);
out_pos = pos;
out_rect = r;
out_color = in_color;
out_offset = in_offset;
out_points_id = in_points_id;
out_fill_rule = in_fill_rule;
}

View File

@@ -19,11 +19,13 @@ gsk_private_vulkan_fragment_shaders = [
'color-matrix.frag',
'convert.frag',
'cross-fade.frag',
'fill.frag',
'glyph.frag',
'inset-shadow.frag',
'linear.frag',
'mask.frag',
'outset-shadow.frag',
'stroke.frag',
'texture.frag',
]
@@ -35,11 +37,13 @@ gsk_private_vulkan_vertex_shaders = [
'color-matrix.vert',
'convert.vert',
'cross-fade.vert',
'fill.vert',
'glyph.vert',
'inset-shadow.vert',
'linear.vert',
'mask.vert',
'outset-shadow.vert',
'stroke.vert',
'texture.vert',
]

View File

@@ -0,0 +1,315 @@
#version 450
#include "common.frag.glsl"
#include "clip.frag.glsl"
#include "rect.frag.glsl"
#define GSK_LINE_CAP_BUTT 0
#define GSK_LINE_CAP_ROUND 1
#define GSK_LINE_CAP_SQUARE 2
#define GSK_LINE_JOIN_MITER 0
#define GSK_LINE_JOIN_MITER_CLIP 1
#define GSK_LINE_JOIN_ROUND 2
#define GSK_LINE_JOIN_BEVEL 3
#define GSK_PATH_MOVE 0
#define GSK_PATH_CLOSE 1
#define GSK_PATH_LINE 2
#define GSK_PATH_CURVE 3
#define GSK_PATH_CONIC 4
layout(location = 0) in vec2 in_pos;
layout(location = 1) in flat Rect in_rect;
layout(location = 2) in flat vec4 in_color;
layout(location = 3) in flat vec2 in_offset;
layout(location = 4) in flat int in_points_id;
layout(location = 5) in flat float in_half_line_width;
layout(location = 6) in flat int in_line_cap;
layout(location = 7) in flat int in_line_join;
layout(location = 8) in flat float in_miter_limit;
layout(location = 0) out vec4 color;
float scale;
float line_width;
vec2
get_point (inout int idx)
{
vec2 p = vec2 (get_float (idx), get_float (idx + 1));
idx += 2;
return p * scale;
}
float
cross2d (vec2 v0, vec2 v1)
{
return v0.x * v1.y - v0.y * v1.x;
}
float
line_distance (vec2 start,
vec2 end,
out vec2 tan)
{
tan = end - start;
float offset = dot (-start, tan) / dot (tan, tan);
if (offset < 0.0 || offset > 1.0)
return 1e38;
vec2 pq = start + tan * offset;
return length (pq) - line_width;
}
float
cubic_bezier_normal_iteration (float t,
vec2 a0,
vec2 a1,
vec2 a2,
vec2 a3)
{
// Horner's method
vec2 a_2 = a2 + t * a3;
vec2 a_1 = a1 + t * a_2;
vec2 b_2 = a_2 + t * a3;
vec2 uv_to_p = a0 + t * a_1;
vec2 tang = a_1 + t * b_2;
float l_tang = dot (tang, tang);
return t - dot (tang, uv_to_p) / l_tang;
}
void
cubic_distance (vec2 p0,
vec2 p1,
vec2 p2,
vec2 p3,
inout float d,
inout vec2 p0_tan,
inout vec2 p1_tan)
{
vec2 a3 = (-p0 + 3. * p1 - 3. * p2 + p3);
vec2 a2 = (3. * p0 - 6. * p1 + 3. * p2);
vec2 a1 = (-3. * p0 + 3. * p1);
vec2 a0 = p0;
p0_tan = a1;
p1_tan = 3 * a3 + 2 * a2 + a1;
vec2 tmp = min (min (p0, p1), min (p2, p3)) - line_width;
if (tmp.x >= d || tmp.y >= d)
return;
tmp = max (max (p0, p1), max (p2, p3)) + line_width;
if (tmp.x <= - d || tmp.y <= - d)
return;
float t;
vec3 params = vec3 (0, .5, 1);
for (int i = 0; i < 3; i++)
{
t = params[i];
for (int j = 0; j < 3; j++)
{
t = cubic_bezier_normal_iteration (t, a0, a1, a2, a3);
}
if (t < 0.0 || t > 1.0)
continue;
vec2 uv = ((a3 * t + a2) * t + a1) * t + a0;
d = min (d, length (uv) - line_width);
}
}
float
cap_distance (vec2 p,
vec2 p_tan)
{
switch (in_line_cap)
{
case GSK_LINE_CAP_BUTT:
return 1e38;
case GSK_LINE_CAP_ROUND:
return length (p) - line_width;
case GSK_LINE_CAP_SQUARE:
vec2 unused;
return line_distance (p, p - line_width * normalize (p_tan), unused);
}
return 0;
}
float
ray_distance (vec2 p,
vec2 dir,
out float offset)
{
offset = dot (-p, dir) / dot (dir, dir);
vec2 pq = p + dir * offset;
return length (pq) - line_width;
}
float
join_distance (vec2 p,
vec2 in_tan,
vec2 out_tan)
{
out_tan = - out_tan;
float in_d, out_d;
float d = ray_distance (p, in_tan, in_d);
d = max (d, ray_distance (p, out_tan, out_d));
if (in_d < 0 || out_d < 0)
return 1e38;
if (in_line_join == GSK_LINE_JOIN_ROUND)
return length (p) - line_width;
in_tan = normalize (in_tan);
out_tan = normalize (out_tan);
vec2 bisector = normalize (in_tan + out_tan);
float miter_d;
ray_distance (p, bisector, miter_d);
float bevel_d;
ray_distance (line_width * vec2(in_tan.y, -in_tan.x), bisector, bevel_d);
bevel_d = abs (bevel_d);
if (in_line_join == GSK_LINE_JOIN_BEVEL)
return max (d, miter_d - bevel_d);
else if (in_line_join == GSK_LINE_JOIN_MITER_CLIP)
return max (d, miter_d - max (bevel_d, in_miter_limit * line_width));
/* GSK_LINE_JOIN_MITER is the fallback, so no if() */
if (miter_d > in_miter_limit * line_width)
return max (d, miter_d - bevel_d);
else
return max (d, miter_d - in_miter_limit * line_width);
}
void
standard_contour_stroke_distance (vec2 p,
inout int path_idx,
inout float d)
{
int n_points = get_int (path_idx);
int ops_idx = path_idx + 1;
int pts_idx = ops_idx + n_points;
vec2 start = get_point (pts_idx) - p;
vec2 start_tan = vec2(1, 0);
vec2 p0 = start;
vec2 p0_tan;
int op = GSK_PATH_MOVE;
for (int i = 1; i < n_points && d > -1.0; i++)
{
op = get_int (ops_idx + i);
switch (op)
{
case GSK_PATH_MOVE:
discard;
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
{
vec2 p1 = get_point (pts_idx) - p;
vec2 line_tan;
d = min (d, line_distance (p0, p1, line_tan));
if (i > 1)
d = min (d, join_distance (p0, p0_tan, line_tan));
else
start_tan = line_tan;
p0_tan = line_tan;
p0 = p1;
}
break;
case GSK_PATH_CURVE:
{
vec2 p1 = get_point (pts_idx) - p;
vec2 p2 = get_point (pts_idx) - p;
vec2 p3 = get_point (pts_idx) - p;
vec2 start, end;
cubic_distance (p0, p1, p2, p3, d, start, end);
if (i > 1)
d = min (d, join_distance (p0, p0_tan, start));
else
start_tan = start;
p0 = p3;
p0_tan = end;
}
break;
case GSK_PATH_CONIC:
{
/* FIXME */
pts_idx += 4;
vec2 p1 = get_point (pts_idx) - p;
vec2 line_tan;
d = min (d, line_distance (p0, p1, line_tan));
if (i > 1)
d = min (d, join_distance (p0, p0_tan, line_tan));
else
start_tan = line_tan;
p0_tan = line_tan;
p0 = p1;
}
break;
}
}
if (op == GSK_PATH_CLOSE)
{
d = min (d, join_distance (p0, p0_tan, start_tan));
}
else
{
d = min (d, cap_distance (start, start_tan));
d = min (d, cap_distance (p0, - p0_tan));
}
path_idx = pts_idx;
}
float
stroke_distance (vec2 p)
{
int path_idx = in_points_id;
int n = get_int (path_idx);
path_idx++;
float d = 1.0;
while (n-- > 0 && d > -1.0)
{
standard_contour_stroke_distance (p, path_idx, d);
}
return d;
}
float
stroke_coverage (vec2 p)
{
float d = stroke_distance (p * scale);
return clamp (0.5 - d, 0, 1);
}
void main()
{
scale = max (push.scale.x, push.scale.y);
line_width = in_half_line_width * scale;
float alpha = in_color.a * rect_coverage (in_rect, in_pos);
alpha *= stroke_coverage (in_pos / push.scale - in_offset);
color = clip_scaled (in_pos, vec4(in_color.rgb, 1) * alpha);
}

View File

@@ -0,0 +1,37 @@
#version 450
#include "common.vert.glsl"
#include "rect.vert.glsl"
layout(location = 0) in vec4 in_rect;
layout(location = 1) in vec4 in_color;
layout(location = 2) in vec2 in_offset;
layout(location = 3) in int in_points_id;
layout(location = 4) in float in_line_width;
layout(location = 5) in int in_line_cap;
layout(location = 6) in int in_line_join;
layout(location = 7) in float in_miter_limit;
layout(location = 0) out vec2 out_pos;
layout(location = 1) out flat Rect out_rect;
layout(location = 2) out flat vec4 out_color;
layout(location = 3) out flat vec2 out_offset;
layout(location = 4) out flat int out_points_id;
layout(location = 5) out flat float out_half_line_width;
layout(location = 6) out flat int out_line_cap;
layout(location = 7) out flat int out_line_join;
layout(location = 8) out flat float out_miter_limit;
void main() {
Rect r = rect_from_gsk (in_rect + vec4(in_offset, 0.0, 0.0));
vec2 pos = set_position_from_rect (r);
out_pos = pos;
out_rect = r;
out_color = in_color;
out_offset = in_offset;
out_points_id = in_points_id;
out_half_line_width = 0.5 * in_line_width;
out_line_cap = in_line_cap;
out_line_join = in_line_join;
out_miter_limit = in_miter_limit;
}

View File

@@ -357,16 +357,16 @@ snapshot_frame_fill (GtkSnapshot *snapshot,
gtk_snapshot_append_border (snapshot, outline, border_width, colors);
}
static void
set_stroke_style (cairo_t *cr,
double line_width,
GtkBorderStyle style,
double length)
static GskStroke *
create_stroke_style (double line_width,
GtkBorderStyle style,
double length)
{
double segments[2];
GskStroke *stroke;
float segments[2];
double n;
cairo_set_line_width (cr, line_width);
stroke = gsk_stroke_new (line_width);
if (style == GTK_BORDER_STYLE_DOTTED)
{
@@ -374,12 +374,12 @@ set_stroke_style (cairo_t *cr,
segments[0] = 0;
segments[1] = n ? length / n : 2;
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
}
else
else if (style == GTK_BORDER_STYLE_DASHED)
{
n = length / line_width;
/* Optimize the common case of an integer-sized rectangle
@@ -397,32 +397,33 @@ set_stroke_style (cairo_t *cr,
segments[0] = n ? (1. / 3) * length / n : 1;
segments[1] = 2 * segments[0];
}
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_SQUARE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_MITER);
}
else
{
g_assert_not_reached ();
}
return stroke;
}
static void
render_frame_stroke (cairo_t *cr,
const GskRoundedRect *border_box,
const double border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *border_box,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
{
gboolean different_colors, different_borders;
GskRoundedRect stroke_box;
GskPathBuilder *builder;
GskPath *path;
GskStroke *stroke;
guint i;
different_colors = !gdk_rgba_equal (&colors[0], &colors[1]) ||
!gdk_rgba_equal (&colors[0], &colors[2]) ||
!gdk_rgba_equal (&colors[0], &colors[3]);
different_borders = border_width[0] != border_width[1] ||
border_width[0] != border_width[2] ||
border_width[0] != border_width[3] ;
stroke_box = *border_box;
gsk_rounded_rect_shrink (&stroke_box,
border_width[GTK_CSS_TOP] / 2.0,
@@ -430,32 +431,36 @@ render_frame_stroke (cairo_t *cr,
border_width[GTK_CSS_BOTTOM] / 2.0,
border_width[GTK_CSS_LEFT] / 2.0);
if (!different_colors && !different_borders && hidden_side == 0)
if (border_width[0] == border_width[1] &&
border_width[0] == border_width[2] &&
border_width[0] == border_width[3] &&
hidden_side == 0)
{
double length = 0;
/* FAST PATH:
* Mostly expected to trigger for focus rectangles */
for (i = 0; i < 4; i++)
for (i = 0; i < 4; i++)
{
length += _gtk_rounded_box_guess_length (&stroke_box, i);
}
gsk_rounded_rect_path (&stroke_box, cr);
gdk_cairo_set_source_rgba (cr, &colors[0]);
set_stroke_style (cr, border_width[0], stroke_style, length);
cairo_stroke (cr);
builder = gsk_path_builder_new ();
gsk_path_builder_add_rounded_rect (builder, &stroke_box);
path = gsk_path_builder_free_to_path (builder);
stroke = create_stroke_style (border_width[0],
stroke_style, length);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gsk_path_unref (path);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
gtk_snapshot_pop (snapshot);
}
else
{
GskRoundedRect padding_box;
padding_box = *border_box;
gsk_rounded_rect_shrink (&padding_box,
border_width[GTK_CSS_TOP],
border_width[GTK_CSS_RIGHT],
border_width[GTK_CSS_BOTTOM],
border_width[GTK_CSS_LEFT]);
const float weight = sqrtf(2)/2.0;
for (i = 0; i < 4; i++)
{
@@ -465,49 +470,111 @@ render_frame_stroke (cairo_t *cr,
if (border_width[i] == 0)
continue;
cairo_save (cr);
builder = gsk_path_builder_new ();
if (i == 0)
_gtk_rounded_box_path_top (border_box, &padding_box, cr);
{
/* top */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_LEFT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width,
stroke_box.bounds.origin.y,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width,
stroke_box.bounds.origin.y);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height / 2,
weight);
}
else if (i == 1)
_gtk_rounded_box_path_right (border_box, &padding_box, cr);
{
/* right */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height / 2,
weight);
}
else if (i == 2)
_gtk_rounded_box_path_bottom (border_box, &padding_box, cr);
{
/* bottom */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height / 2,
weight);
}
else if (i == 3)
_gtk_rounded_box_path_left (border_box, &padding_box, cr);
cairo_clip (cr);
{
/* left */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_LEFT].height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width,
stroke_box.bounds.origin.y,
weight);
}
_gtk_rounded_box_path_side (&stroke_box, cr, i);
path = gsk_path_builder_free_to_path (builder);
stroke = create_stroke_style (border_width[i],
stroke_style,
_gtk_rounded_box_guess_length (&stroke_box, i));
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gsk_path_unref (path);
gdk_cairo_set_source_rgba (cr, &colors[i]);
set_stroke_style (cr,
border_width[i],
stroke_style,
_gtk_rounded_box_guess_length (&stroke_box, i));
cairo_stroke (cr);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
cairo_restore (cr);
gtk_snapshot_pop (snapshot);
}
}
}
static void
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
{
double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
cairo_t *cr;
cr = gtk_snapshot_append_cairo (snapshot,
&outline->bounds);
render_frame_stroke (cr, outline, double_width, colors, hidden_side, stroke_style);
cairo_destroy (cr);
}
static void
color_shade (const GdkRGBA *color,
double factor,

View File

@@ -30,6 +30,7 @@
#include "gsk/gskrendernodeprivate.h"
#include "gsk/gskroundedrectprivate.h"
#include "gsk/gskstrokeprivate.h"
#include "gtk/gskpangoprivate.h"
@@ -105,6 +106,14 @@ struct _GtkSnapshotState {
struct {
GskRoundedRect bounds;
} rounded_clip;
struct {
GskPath *path;
GskFillRule fill_rule;
} fill;
struct {
GskPath *path;
GskStroke stroke;
} stroke;
struct {
gsize n_shadows;
GskShadow *shadows;
@@ -1096,6 +1105,135 @@ gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
gsk_rounded_rect_scale_affine (&state->data.rounded_clip.bounds, bounds, scale_x, scale_y, dx, dy);
}
static GskRenderNode *
gtk_snapshot_collect_fill (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *fill_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
fill_node = gsk_fill_node_new (node,
state->data.fill.path,
state->data.fill.fill_rule);
if (fill_node->bounds.size.width == 0 ||
fill_node->bounds.size.height == 0)
{
gsk_render_node_unref (node);
gsk_render_node_unref (fill_node);
return NULL;
}
gsk_render_node_unref (node);
return fill_node;
}
static void
gtk_snapshot_clear_fill (GtkSnapshotState *state)
{
gsk_path_unref (state->data.fill.path);
}
/**
* gtk_snapshot_push_fill:
* @snapshot: a #GtkSnapshot
* @path: The path describing the area to fill
* @fill_rule: The fill rule to use
*
* Fills the area given by @path and @fill_rule with an image and discards everything
* outside of it.
*
* The image is recorded until the next call to gtk_snapshot_pop().
*/
void
gtk_snapshot_push_fill (GtkSnapshot *snapshot,
GskPath *path,
GskFillRule fill_rule)
{
GtkSnapshotState *state;
/* FIXME: Is it worth calling ensure_affine() and transforming the path here? */
gtk_snapshot_ensure_identity (snapshot);
state = gtk_snapshot_push_state (snapshot,
gtk_snapshot_get_current_state (snapshot)->transform,
gtk_snapshot_collect_fill,
gtk_snapshot_clear_fill);
state->data.fill.path = gsk_path_ref (path);
state->data.fill.fill_rule = fill_rule;
}
static GskRenderNode *
gtk_snapshot_collect_stroke (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *stroke_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
stroke_node = gsk_stroke_node_new (node,
state->data.stroke.path,
&state->data.stroke.stroke);
if (stroke_node->bounds.size.width == 0 ||
stroke_node->bounds.size.height == 0)
{
gsk_render_node_unref (node);
gsk_render_node_unref (stroke_node);
return NULL;
}
gsk_render_node_unref (node);
return stroke_node;
}
static void
gtk_snapshot_clear_stroke (GtkSnapshotState *state)
{
gsk_path_unref (state->data.stroke.path);
gsk_stroke_clear (&state->data.stroke.stroke);
}
/**
* gtk_snapshot_push_stroke:
* @snapshot: a #GtkSnapshot
* @path: The path to stroke
* @stroke: The stroke attributes
*
* Strokes the given @path with the attributes given by @stroke and the
* image being recorded until the next call to gtk_snapshot_pop().
*/
void
gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
GskPath *path,
const GskStroke *stroke)
{
GtkSnapshotState *state;
/* FIXME: Is it worth calling ensure_affine() and transforming the path here? */
gtk_snapshot_ensure_identity (snapshot);
state = gtk_snapshot_push_state (snapshot,
gtk_snapshot_get_current_state (snapshot)->transform,
gtk_snapshot_collect_stroke,
gtk_snapshot_clear_stroke);
state->data.stroke.path = gsk_path_ref (path);
gsk_stroke_init_copy (&state->data.stroke.stroke, stroke);
}
static GskRenderNode *
gtk_snapshot_collect_shadow (GtkSnapshot *snapshot,
GtkSnapshotState *state,

View File

@@ -88,6 +88,14 @@ GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
const GskRoundedRect *bounds);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_fill (GtkSnapshot *snapshot,
GskPath *path,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
GskPath *path,
const GskStroke *stroke);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_shadow (GtkSnapshot *snapshot,
const GskShadow *shadow,
gsize n_shadows);

View File

@@ -11952,6 +11952,18 @@ gtk_widget_render (GtkWidget *widget,
gdk_profiler_end_mark (before_render, "widget render", "");
}
{
static gint64 last_print = 0;
gint64 t = g_get_monotonic_time ();
if (t - last_print > 5 * G_USEC_PER_SEC)
{
double fps = gdk_frame_clock_get_fps (gtk_widget_get_frame_clock (widget));
g_print ("%.2f fps\n", fps);
last_print = t;
}
}
}
static void

View File

@@ -298,6 +298,12 @@ create_list_model_for_render_node (GskRenderNode *node)
case GSK_ROUNDED_CLIP_NODE:
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_rounded_clip_node_get_child (node) }, 1);
case GSK_FILL_NODE:
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_fill_node_get_child (node) }, 1);
case GSK_STROKE_NODE:
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_stroke_node_get_child (node) }, 1);
case GSK_SHADOW_NODE:
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_shadow_node_get_child (node) }, 1);
@@ -425,6 +431,10 @@ node_type_name (GskRenderNodeType type)
return "Clip";
case GSK_ROUNDED_CLIP_NODE:
return "Rounded Clip";
case GSK_FILL_NODE:
return "Fill";
case GSK_STROKE_NODE:
return "Stroke";
case GSK_SHADOW_NODE:
return "Shadow";
case GSK_BLEND_NODE:
@@ -466,6 +476,8 @@ node_name (GskRenderNode *node)
case GSK_REPEAT_NODE:
case GSK_CLIP_NODE:
case GSK_ROUNDED_CLIP_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
case GSK_SHADOW_NODE:
case GSK_BLEND_NODE:
case GSK_MASK_NODE:
@@ -894,6 +906,20 @@ add_float_row (GListStore *store,
g_free (text);
}
static const char *
enum_to_nick (GType type,
int value)
{
GEnumClass *class;
GEnumValue *v;
class = g_type_class_ref (type);
v = g_enum_get_value (class, value);
g_type_class_unref (class);
return v->value_nick;
}
static void
populate_render_node_properties (GListStore *store,
GskRenderNode *node)
@@ -1133,9 +1159,7 @@ populate_render_node_properties (GListStore *store,
case GSK_BLEND_NODE:
{
GskBlendMode mode = gsk_blend_node_get_blend_mode (node);
tmp = g_enum_to_string (GSK_TYPE_BLEND_MODE, mode);
add_text_row (store, "Blendmode", tmp);
g_free (tmp);
add_text_row (store, "Blendmode", enum_to_nick (GSK_TYPE_BLEND_MODE, mode));
}
break;
@@ -1377,6 +1401,39 @@ populate_render_node_properties (GListStore *store,
}
break;
case GSK_FILL_NODE:
{
GskPath *path = gsk_fill_node_get_path (node);
GskFillRule fill_rule = gsk_fill_node_get_fill_rule (node);
tmp = gsk_path_to_string (path);
add_text_row (store, "Path", tmp);
g_free (tmp);
add_text_row (store, "Fill rule", enum_to_nick (GSK_TYPE_FILL_RULE, fill_rule));
}
break;
case GSK_STROKE_NODE:
{
GskPath *path = gsk_stroke_node_get_path (node);
const GskStroke *stroke = gsk_stroke_node_get_stroke (node);
GskLineCap line_cap = gsk_stroke_get_line_cap (stroke);
GskLineJoin line_join = gsk_stroke_get_line_join (stroke);
tmp = gsk_path_to_string (path);
add_text_row (store, "Path", tmp);
g_free (tmp);
tmp = g_strdup_printf ("%.2f", gsk_stroke_get_line_width (stroke));
add_text_row (store, "Line width", tmp);
g_free (tmp);
add_text_row (store, "Line cap", enum_to_nick (GSK_TYPE_LINE_CAP, line_cap));
add_text_row (store, "Line join", enum_to_nick (GSK_TYPE_LINE_JOIN, line_join));
}
break;
case GSK_CONTAINER_NODE:
tmp = g_strdup_printf ("%d", gsk_container_node_get_n_children (node));
add_text_row (store, "Children", tmp);

View File

@@ -23,7 +23,6 @@ $popover_radius: $button_radius + 4;
&:backdrop {
text-shadow: none;
-gtk-icon-shadow: none;
}
}
@@ -39,14 +38,6 @@ dnd {
-gtk-icon-size: 32px;
}
image:disabled {
-gtk-icon-filter: opacity(0.5);
}
picture:disabled {
opacity: 0.5;
}
.view,
%view {
color: $text_color;
@@ -95,13 +86,11 @@ iconview {
@if $contrast == 'high' {
&:selected {
box-shadow: inset 0 0 0 1px $borders_color;
}
}
&:drop(active) {
box-shadow: none;
}
> dndtarget:drop(active) {
@@ -148,7 +137,6 @@ gridview {
@extend %selected_items;
@if $contrast == 'high' {
box-shadow: inset 0 0 0 1px $borders_color;
}
}
@@ -198,7 +186,6 @@ label {
}
.dim-label {
opacity: $dim_label_opacity;
text-shadow: none;
}
@@ -228,7 +215,6 @@ window.aboutdialog image.large-icons {
border: none;
background-color: $osd_bg_color;
background-clip: padding-box;
-gtk-icon-shadow: 0 1px black;
}
@@ -248,8 +234,6 @@ spinner {
&:checked {
opacity: 1;
animation: spin 1s linear infinite;
&:disabled { opacity: 0.5; }
}
}
@@ -385,7 +369,6 @@ entry {
&:drop(active) {
&:focus-within, & {
border-color: $drop_target_color;
box-shadow: inset 0 0 0 1px $drop_target_color;
}
}
@@ -409,7 +392,6 @@ entry {
border-width: 0 0 2px;
border-color: $selected_bg_color;
border-style: solid;
box-shadow: none;
}
@@ -560,7 +542,6 @@ button {
&:disabled {
@include button(insensitive);
-gtk-icon-filter: opacity(0.5);
&:active,
&:checked { @include button(insensitive-active); }
@@ -623,7 +604,6 @@ button {
&:drop(active) {
color: $drop_target_color;
border-color: $drop_target_color;
box-shadow: inset 0 0 0 1px $drop_target_color;
}
}
@@ -655,7 +635,6 @@ button {
&:only-child {
margin: 4px;
border-radius: 50%; // completely round when it is a single button
box-shadow: 0 1px 2px transparentize(black, 0.8); // drop shadow for the only child
};
}
@@ -665,13 +644,11 @@ button {
@include button(osd);
border: none;
box-shadow: none;
&:hover {
@include button(osd-hover);
border: none;
box-shadow: none;
}
&:active,
@@ -679,7 +656,6 @@ button {
@include button(osd-active);
border: none;
box-shadow: none;
}
}
@@ -698,16 +674,12 @@ button {
&.flat {
@include button(undecorated);
box-shadow: none;
-gtk-icon-shadow: 0 1px black;
&:hover { @include button(osd-hover); }
&:disabled {
@include button(osd-insensitive);
background-image: none;
border-color: transparent;
box-shadow: none;
}
&:active,
@@ -873,10 +845,7 @@ button {
background-color: transparent;
background-image: none;
border-color: transparent;
box-shadow: inset 0 1px transparentize(white, 1),
0 1px transparentize(white, 1);
text-shadow: none;
-gtk-icon-shadow: none;
}
.linked:not(.vertical) > menubutton,
@@ -928,7 +897,6 @@ modelbutton.flat arrow {
background: none;
min-width: 16px;
min-height: 16px;
opacity: 0.3; //dim icon
&:hover { background: none; }
@@ -963,19 +931,6 @@ button.color {
> colorswatch:only-child {
&, > overlay { border-radius: 0; }
@if $variant == 'light' {
box-shadow: 0 1px $shadow_color;
.osd & { box-shadow: none; }
}
}
@if $variant == 'light' {
.osd &, & {
&:disabled,
&:active,
&:checked { colorswatch:only-child { box-shadow: none; }}
}
}
}
@@ -1076,7 +1031,6 @@ spinbutton {
background-color: transparent;
border: none;
border-radius: 0;
box-shadow: none;
padding: 6px;
&:backdrop:disabled { background-color: transparent; }
@@ -1098,7 +1052,6 @@ spinbutton {
border-style: none none none solid;
border-color: transparentize($borders_color, 0.7);
border-radius: 0;
box-shadow: none;
&:dir(rtl) { border-style: none solid none none; }
@@ -1114,7 +1067,6 @@ spinbutton {
&:active {
background-color: transparentize(black, 0.9);
box-shadow: inset 0 2px 3px -1px transparentize(black, 0.8);
}
&:dir(ltr):last-child { border-radius: 0 $button_radius $button_radius 0; }
@@ -1137,8 +1089,6 @@ spinbutton {
border-style: none none none solid;
border-color: transparentize($osd_borders_color, 0.3);
border-radius: 0;
box-shadow: none;
-gtk-icon-shadow: 0 1px black;
&:dir(rtl) { border-style: none solid none none; }
@@ -1148,8 +1098,6 @@ spinbutton {
color: $osd_fg_color;
border-color: transparentize(opacify($osd_borders_color, 1), 0.5);
background-color: darken($osd_bg_color,10%);
-gtk-icon-shadow: 0 1px black;
box-shadow: none;
}
&:disabled {
@@ -1157,8 +1105,6 @@ spinbutton {
color: $osd_insensitive_fg_color;
border-color: transparentize(opacify($osd_borders_color, 1), 0.5);
-gtk-icon-shadow: none;
box-shadow: none;
}
&:dir(ltr):last-child { border-radius: 0 $button_radius $button_radius 0; }
@@ -1179,7 +1125,6 @@ spinbutton {
&:drop(active) {
border-color: transparent;
box-shadow: none;
}
> text {
@@ -1261,7 +1206,6 @@ dropdown > button > box {
> stack > row.activatable:hover {
background: none;
box-shadow: none;
}
}
@@ -1287,7 +1231,6 @@ combobox {
}
&:drop(active) { // FIXME: untested
box-shadow: none;
button.combo { @extend %button_basic_drop_active; }
}
@@ -1308,7 +1251,6 @@ combobox {
outline-color: $alt_focus_border_color;
color: $text-color;
background-color: $menu_selected_color;
box-shadow: none;
border-radius: $button_radius;
}
}
@@ -1403,7 +1345,6 @@ searchbar > revealer > box {
&:backdrop {
border-color: $backdrop_borders_color;
background-color: $backdrop_dark_fill;
box-shadow: none;
transition: $backdrop_transition;
}
}
@@ -1508,8 +1449,6 @@ headerbar {
background-image: none;
transition: $backdrop_transition;
.title { filter: opacity(.5); }
}
.title {
@@ -1580,7 +1519,6 @@ headerbar {
margin-right: -1px;
margin-top: -1px;
border-radius: 0;
box-shadow: none;
}
}
}
@@ -1740,7 +1678,6 @@ treeview.view {
}
&:drop(active) {
box-shadow: none;
}
> dndtarget:drop(active) {
@@ -1779,7 +1716,6 @@ treeview.view {
background-color: $selected_bg_color;
background-image: image($selected_bg_color);
box-shadow: none;
&:selected {
&:focus, & {
@@ -1789,8 +1725,6 @@ treeview.view {
}
@else { box-shadow: inset 0 1px transparentize(white, 0.95); }
background-image: image($base_color);
&:backdrop {
@@ -1826,13 +1760,11 @@ treeview.view {
background-color: $base_color;
font-weight: bold;
text-shadow: none;
box-shadow: none;
&:hover {
@extend %column_header_button;
color: mix($_column_header_color, $fg_color, 50%);
box-shadow: none;
transition: none; //I shouldn't need this
}
@@ -1865,7 +1797,6 @@ treeview.view {
background-color: $selected_bg_color;
border-style: none;
border-radius: 0;
box-shadow: inset 0 0 0 1px $base_color;
text-shadow: none;
transition: none;
}
@@ -1905,7 +1836,6 @@ popover.background {
background-color: $menu_color;
background-clip: padding-box;
border: 1px solid $_popover_border;
box-shadow: 0 1px 2px transparentize(black, 0.7);
}
&:backdrop {
@@ -1941,7 +1871,6 @@ popover.background {
@extend %osd;
border: 1px solid transparentize(white, 0.9);
box-shadow: none;
}
}
@@ -1982,7 +1911,6 @@ popover.menu {
background: image($menu_selected_color);
@if $contrast == 'high' {
box-shadow: inset 0 0 0 1px $borders_color;
}
}
}
@@ -2072,20 +2000,17 @@ popover.menu {
// initial styling for popover menu and bar
menubar {
padding: 0px;
box-shadow: inset 0 -1px transparentize(black, 0.9);
> item {
min-height: 16px;
padding: 4px 8px;
&:selected { //Seems like it :hover even with keyboard focus
box-shadow: inset 0 -3px $selected_bg_color;
color: $link_color;
}
&:disabled {
color: $insensitive_fg_color;
box-shadow: none;
}
}
@@ -2122,10 +2047,6 @@ notebook {
> tabs {
margin-bottom: -2px;
> tab {
&:hover { box-shadow: inset 0 -4px $borders_color; }
&:checked { box-shadow: inset 0 -4px $selected_bg_color; }
}
}
}
@@ -2135,9 +2056,6 @@ notebook {
> tabs {
margin-top: -2px;
> tab {
&:hover { box-shadow: inset 0 4px $borders_color; }
&:checked { box-shadow: inset 0 4px $selected_bg_color; }
}
}
}
@@ -2147,9 +2065,6 @@ notebook {
> tabs {
margin-right: -2px;
> tab {
&:hover { box-shadow: inset -4px 0 $borders_color; }
&:checked { box-shadow: inset -4px 0 $selected_bg_color; }
}
}
}
@@ -2159,9 +2074,6 @@ notebook {
> tabs {
margin-left: -2px;
> tab {
&:hover { box-shadow: inset 4px 0 $borders_color; }
&:checked { box-shadow: inset 4px 0 $selected_bg_color; }
}
}
}
@@ -2226,7 +2138,6 @@ notebook {
background-image: none;
background-color: transparentize(white, 0.7);
border-color: transparent;
box-shadow: none;
}
&:disabled { @include button(undecorated); }
@@ -2499,7 +2410,6 @@ switch {
outline-color: $alt_focus_border_color;
@if $variant == 'light' {
box-shadow: none;
border-color: $switch_borders_color;
> slider { &:checked, & { border-color: $switch_borders_color; } }
@@ -2536,10 +2446,8 @@ switch {
border-radius: 5px;
background-image: none;
transition: 200ms;
box-shadow: none;
border-width: 0;
-gtk-icon-source: #{$check_icon};
-gtk-icon-shadow: none;
}
}
@@ -2786,7 +2694,6 @@ scale {
border-style: solid;
border-radius: 100%;
transition: $button_transition;
transition-property: background, border, box-shadow;
&:hover { @include button(hover); }
@@ -2934,7 +2841,6 @@ scale {
background-position: $_scale_slider_bg_pos;
background-repeat: no-repeat;
box-shadow: none;
}
}
@@ -2972,7 +2878,6 @@ scale {
> slider {
margin: 0;
opacity: .8;
}
}
@@ -3071,7 +2976,6 @@ progressbar {
border-style: none;
border-radius: 0;
background-color: transparent;
box-shadow: none;
> progress {
border-style: none;
@@ -3297,11 +3201,7 @@ row {
&:hover { background-color: if(variant == light, transparentize($fg_color, 0.9), transparentize($fg_color, 0.95)); }
&:active { box-shadow: inset 0 2px 2px -2px transparentize(black, 0.8); }
&:selected {
&:active { box-shadow: inset 0 2px 3px -1px transparentize(black, 0.5); }
&.has-open-popup,
&:hover { background-color: mix($fg_color, $selected_bg_color, 10%); }
}
@@ -3497,17 +3397,6 @@ expander-widget {
}
}
.navigation-sidebar,
placessidebar,
stackswitcher,
expander-widget {
&:not(decoration):not(window):drop(active):focus,
&:not(decoration):not(window):drop(active) {
box-shadow: none;
}
}
/************
* Calendar *
***********/
@@ -3521,7 +3410,6 @@ calendar {
> button {
border: none;
box-shadow: none;
background: none;
border-radius: 0;
}
@@ -3538,10 +3426,7 @@ calendar {
}
> label.today {
box-shadow: inset 0px -2px $borders_color;
&:selected {
box-shadow: none;
}
}
@@ -3664,7 +3549,6 @@ filechooserbutton>button>box {
border-spacing: 6px;
}
filechooserbutton:drop(active) {
box-shadow: none;
border-color: transparent;
}
@@ -3725,7 +3609,6 @@ stacksidebar {
background-color: darken($menu_selected_color,5%);
}
&.activatable:active, &.activatable:selected:active {
box-shadow: none; // #3413
}
}
}
@@ -3761,7 +3644,6 @@ separator.sidebar {
color: inherit;
@if $contrast == 'high' {
box-shadow: inset 0 0 0 1px $borders_color;
}
@@ -3780,12 +3662,6 @@ separator.sidebar {
* File chooser *
****************/
$_placesidebar_icons_opacity: 0.7;
row image.sidebar-icon { opacity: $_placesidebar_icons_opacity; } // dim the sidebar icons
// see bug #786613 for details
// on this oddity
/* this should be more generic, only using .navigation-sidebar
https://gitlab.gnome.org/GNOME/gtk/-/issues/2929
*/
@@ -3822,7 +3698,6 @@ placessidebar {
}
// in the sidebar case it makes no sense to click the selected row
&:selected:active { box-shadow: none; }
&.sidebar-placeholder-row {
padding: 0 8px;
@@ -3835,9 +3710,6 @@ placessidebar {
&:drop(active):not(:disabled) {
color: $drop_target_color;
box-shadow: inset 0 1px $drop_target_color,
inset 0 -1px $drop_target_color;
&:selected {
color: $selected_fg_color;
background-color: $drop_target_color;
@@ -3958,7 +3830,6 @@ tooltip {
padding: 6px 10px;
border-radius: $window_radius;
box-shadow: none; // otherwise it gets inherited by windowframe.csd
> box {
border-spacing: 6px;
@@ -4050,18 +3921,13 @@ colorswatch {
}
&:drop(active) {
box-shadow: none;
&.light > overlay {
border-color: $drop_target_color;
box-shadow: inset 0 0 0 2px if($variant == 'light', darken($drop_target_color, 7%), $borders_color),
inset 0 0 0 1px $drop_target_color;
}
&.dark > overlay {
border-color: $drop_target_color;
box-shadow: inset 0 0 0 2px if($variant == 'light', transparentize(black, 0.7), $borders_color),
inset 0 0 0 1px $drop_target_color;
}
}
@@ -4070,8 +3936,6 @@ colorswatch {
}
&.activatable:hover > overlay {
box-shadow: inset 0 1px transparentize(white, 0.6),
inset 0 -1px transparentize(black, 0.8);
}
&#add-color-button {
@@ -4089,16 +3953,12 @@ colorswatch {
}
&:disabled {
opacity: 0.5;
> overlay {
border-color: transparentize(black, 0.4);
box-shadow: none;
}
}
row:selected & { box-shadow: 0 0 0 2px $selected_fg_color; }
&#editor-color-sample {
border-radius: 4px;
@@ -4168,8 +4028,6 @@ window {
$_wm_border_backdrop: if($variant=='light', transparentize(black, 0.82), transparentize(black, 0.25));
&.csd {
box-shadow: 0 3px 9px 1px transparentize(black, 0.5),
0 0 0 1px $_wm_border; //doing borders with box-shadow
margin: 0px;
border-radius: $window_radius $window_radius 0 0;
&:backdrop {
@@ -4177,22 +4035,15 @@ window {
// change when we go to backdrop, to prevent jumping windows.
// The biggest shadow should be in the same order then in the active state
// or the jumping will happen during the transition.
box-shadow: 0 3px 9px 1px transparent,
0 2px 6px 2px transparentize(black, 0.8),
0 0 0 1px $_wm_border_backdrop;
transition: $backdrop_transition;
}
&.popup {
border-radius: $menu_radius;
box-shadow: 0 1px 2px transparentize(black, 0.8),
0 0 0 1px transparentize($_wm_border, 0.1);
}
&.dialog.message {
border-radius: $window_radius;
box-shadow: 0 1px 2px transparentize(black, 0.8),
0 0 0 1px transparentize($_wm_border, 0.1);
}
}
@@ -4201,13 +4052,9 @@ window {
padding: 4px;
border: solid 1px $borders_color;
border-radius: 0;
box-shadow: inset 0 0 0 4px $borders_color, inset 0 0 0 3px $headerbar_bg_color, inset 0 1px $top_hilight;
&:backdrop { box-shadow: inset 0 0 0 4px $borders_color, inset 0 0 0 3px $backdrop_bg_color, inset 0 1px $top_hilight; }
}
&.maximized,
&.fullscreen { border-radius: 0; box-shadow: none; }
&.tiled,
&.tiled-top,
@@ -4215,24 +4062,15 @@ window {
&.tiled-right,
&.tiled-bottom {
border-radius: 0;
box-shadow: 0 0 0 1px $_wm_border,
0 0 0 20px transparent; //transparent control workaround -- #3670
&:backdrop { box-shadow: 0 0 0 1px $_wm_border_backdrop,
0 0 0 20px transparent; // #3670
}
}
&.popup { box-shadow: none; }
// server-side decorations as used by mutter
&.ssd { box-shadow: 0 0 0 1px $_wm_border; } //just doing borders, wm draws actual shadows
}
tooltip.csd {
border-radius: 5px;
box-shadow: none;
}
// catch all extend :)
@@ -4259,7 +4097,6 @@ tooltip.csd {
cursor-handle {
background-color: transparent;
background-image: none;
box-shadow: none;
border-style: none;
min-width: 20px;
min-height: 24px;
@@ -4320,7 +4157,6 @@ shortcut > .keycap {
border: 1px solid;
border-color: if($variant == 'light', mix($borders_color, $bg_color, 50%), $borders_color);
border-radius: 5px;
box-shadow: if($variant == 'light', inset 0 -3px mix($base_color, $bg_color, 20%), inset 0 -3px mix($borders_color, $base_color, 60%));
font-size: smaller;
}
@@ -4328,7 +4164,6 @@ shortcut > .keycap {
:not(decoration):not(window):drop(active):focus,
:not(decoration):not(window):drop(active) { // FIXME needs to be done widget by widget, this wildcard should really die
border-color: $drop_target_color;
box-shadow: inset 0 0 0 1px $drop_target_color;
caret-color: $drop_target_color;
}
@@ -4345,25 +4180,6 @@ stackswitcher.circular {
padding: 0;
}
}
/*************
* App Icons *
*************/
/* Outline for low res icons */
.lowres-icon {
-gtk-icon-shadow: 0 -1px rgba(0,0,0,0.05),
1px 0 rgba(0,0,0,0.1),
0 1px rgba(0,0,0,0.3),
-1px 0 rgba(0,0,0,0.1);
}
/* Drapshadow for large icons */
.icon-dropshadow {
-gtk-icon-shadow: 0 1px 12px rgba(0,0,0,0.05),
0 -1px rgba(0,0,0,0.05),
1px 0 rgba(0,0,0,0.1),
0 1px rgba(0,0,0,0.3),
-1px 0 rgba(0,0,0,0.1);
}
/*********
* Emoji *
@@ -4397,7 +4213,6 @@ button.emoji-section {
/* reset props inherited from the button style */
background: none;
box-shadow: none;
text-shadow: none;
&:hover { border-color: if($variant == 'light', $borders_color, transparentize($fg_color, .9)); }
@@ -4414,7 +4229,6 @@ popover.emoji-picker emoji {
background: $selected_bg_color;
@if $contrast == 'high' {
box-shadow: inset 0 0 0 1px $borders_color;
}
}
}

View File

@@ -25,19 +25,6 @@
}
}
@mixin _shadows($list...) {
//
// Helper mixin to stack up to box-shadows;
//
$shadows: null;
@each $shadow in $list {
@if $shadow!=none { $shadows: $shadows, $shadow; }
}
box-shadow: $shadows;
}
// entries
@mixin entry($t, $fc:$focus_border_color) {
@@ -78,7 +65,6 @@
background-color: transparentize(opacify($osd_borders_color, 1), 0.5);
background-clip: padding-box;
box-shadow: none;
-gtk-icon-shadow: 0 1px black;
}
@if $t==osd-focus {
color: $osd_text_color;
@@ -136,7 +122,6 @@ $_default_button_c: lighten($bg_color,2%);
border-color: if($c!=$_default_button_c, _border_color($c, true), $borders_color); //tint if not default button color
background-image: if($variant == 'light', linear-gradient(to top, darken($c, 2%) 2px, $c),
linear-gradient(to top, darken($c,1%) 2px, $c));
@include _shadows($_button_shadow);
}
@else if $t==hover {
@@ -147,11 +132,9 @@ $_default_button_c: lighten($bg_color,2%);
border-color: if($c != $_default_button_c, _border_color($c), $borders_color);
@if $variant == 'light' {
background-image: linear-gradient(to top, darken($c,16%), darken($c,8%) 1px);
@include _shadows($_button_shadow);
}
@else {
background-image: linear-gradient(to top, darken($c,4%) 20%, darken($c, 3%) 90%);
@include _shadows($_button_shadow);
}
}
@@ -460,7 +443,6 @@ $_default_button_c: lighten($bg_color,2%);
background-clip: if($checked, border-box, padding-box);
background-image: linear-gradient(to bottom, lighten($c, 5%) 20%, $c 90%);
border-color: $_border_color;
box-shadow: 0 1px transparentize(black, 0.95);
color: $tc;
}
@@ -469,7 +451,6 @@ $_default_button_c: lighten($bg_color,2%);
}
@if $t==active {
box-shadow: inset 0 1px if($variant == 'light', rgba(0, 0, 0, 0.2), black);
background-image: if($c == white, image(darken($c, 15%)), image(darken($c, 5%)));
}

View File

@@ -0,0 +1,14 @@
clip {
clip: -50 -50 100 100;
child: cross-fade {
progress: 0.333333;
start: color {
bounds: 0 0 50000 50000;
color: red;
}
end: color {
bounds: -50000 -50000 50000 50000;
color: blue;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1,6 @@
linear-gradient {
bounds: 0 0 320 50;
start: 0 0;
end: 320 0;
stops: 0 rgb(255,0,0), 0.015625 rgb(255,0,0), 0.015625 rgb(178,0,255), 0.03125 rgb(178,0,255), 0.03125 rgb(0,153,255), 0.046875 rgb(0,153,255), 0.046875 rgb(0,255,25), 0.0625 rgb(0,255,25), 0.0625 rgb(255,204,0), 0.078125 rgb(255,204,0), 0.078125 rgb(255,0,127), 0.09375 rgb(255,0,127), 0.09375 rgb(51,0,255), 0.109375 rgb(51,0,255), 0.109375 rgb(0,255,229), 0.125 rgb(0,255,229), 0.125 rgb(102,255,0), 0.140625 rgb(102,255,0), 0.140625 rgb(255,76,0), 0.15625 rgb(255,76,0), 0.15625 rgb(255,0,255), 0.171875 rgb(255,0,255), 0.171875 rgb(0,76,255), 0.1875 rgb(0,76,255), 0.1875 rgb(0,255,102), 0.203125 rgb(0,255,102), 0.203125 rgb(229,255,0), 0.21875 rgb(229,255,0), 0.21875 rgb(255,0,51), 0.234375 rgb(255,0,51), 0.234375 rgb(127,0,255), 0.25 rgb(127,0,255), 0.25 rgb(0,204,255), 0.265625 rgb(0,204,255), 0.265625 rgb(25,255,0), 0.28125 rgb(25,255,0), 0.28125 rgb(255,153,0), 0.296875 rgb(255,153,0), 0.296875 rgb(255,0,178), 0.3125 rgb(255,0,178), 0.3125 rgb(0,0,255), 0.328125 rgb(0,0,255), 0.328125 rgb(0,255,178), 0.34375 rgb(0,255,178), 0.34375 rgb(153,255,0), 0.359375 rgb(153,255,0), 0.359375 rgb(255,25,0), 0.375 rgb(255,25,0), 0.375 rgb(204,0,255), 0.390625 rgb(204,0,255), 0.390625 rgb(0,127,255), 0.40625 rgb(0,127,255), 0.40625 rgb(0,255,51), 0.421875 rgb(0,255,51), 0.421875 rgb(255,229,0), 0.4375 rgb(255,229,0), 0.4375 rgb(255,0,102), 0.453125 rgb(255,0,102), 0.453125 rgb(76,0,255), 0.46875 rgb(76,0,255), 0.46875 rgb(0,255,255), 0.484375 rgb(0,255,255), 0.484375 rgb(76,255,0), 0.5 rgb(76,255,0), 0.5 rgb(255,102,0), 0.515625 rgb(255,102,0), 0.515625 rgb(255,0,229), 0.53125 rgb(255,0,229), 0.53125 rgb(0,51,255), 0.546875 rgb(0,51,255), 0.546875 rgb(0,255,127), 0.5625 rgb(0,255,127), 0.5625 rgb(204,255,0), 0.578125 rgb(204,255,0), 0.578125 rgb(255,0,25), 0.59375 rgb(255,0,25), 0.59375 rgb(153,0,255), 0.609375 rgb(153,0,255), 0.609375 rgb(0,178,255), 0.625 rgb(0,178,255), 0.625 rgb(0,255,0), 0.640625 rgb(0,255,0), 0.640625 rgb(255,178,0), 0.65625 rgb(255,178,0), 0.65625 rgb(255,0,153), 0.671875 rgb(255,0,153), 0.671875 rgb(25,0,255), 0.6875 rgb(25,0,255), 0.6875 rgb(0,255,204), 0.703125 rgb(0,255,204), 0.703125 rgb(127,255,0), 0.71875 rgb(127,255,0), 0.71875 rgb(255,51,0), 0.734375 rgb(255,51,0), 0.734375 rgb(229,0,255), 0.75 rgb(229,0,255), 0.75 rgb(0,102,255), 0.765625 rgb(0,102,255), 0.765625 rgb(0,255,76), 0.78125 rgb(0,255,76), 0.78125 rgb(255,255,0), 0.796875 rgb(255,255,0), 0.796875 rgb(255,0,76), 0.8125 rgb(255,0,76), 0.8125 rgb(102,0,255), 0.828125 rgb(102,0,255), 0.828125 rgb(0,229,255), 0.84375 rgb(0,229,255), 0.84375 rgb(51,255,0), 0.859375 rgb(51,255,0), 0.859375 rgb(255,127,0), 0.875 rgb(255,127,0), 0.875 rgb(255,0,204), 0.890625 rgb(255,0,204), 0.890625 rgb(0,25,255), 0.90625 rgb(0,25,255), 0.90625 rgb(0,255,153), 0.921875 rgb(0,255,153), 0.921875 rgb(178,255,0), 0.9375 rgb(178,255,0), 0.9375 rgb(255,0,0), 0.953125 rgb(255,0,0), 0.953125 rgb(178,0,255), 0.96875 rgb(178,0,255), 0.96875 rgb(0,153,255), 0.984375 rgb(0,153,255), 0.984375 rgb(0,255,25), 1 rgb(0,255,25);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

View File

@@ -0,0 +1,13 @@
transform {
transform: matrix(0, 4,
-8, 0,
0, 0);
child: repeat {
bounds: 0 0 5 2.5;
child-bounds: 0 0 1 0.5;
child: color {
bounds: 0.25 0.125 0.5 0.25;
color: rgb(0,0,255);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

View File

@@ -0,0 +1,17 @@
/* a normal clip */
clip {
clip: 0 0 50 50;
/* a 3D transform */
child: transform {
transform: rotateX(1);
/* a node that forces an offscreen */
child: rounded-clip {
clip: -30000 -30000 60000 60000 / 5;
child: color {
/* way too big, but it's clipped, so no problem */
bounds: -30000 -30000 60000 60000;
color: red;
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

View File

@@ -0,0 +1,13 @@
clip {
clip: 0 0 50 50;
child: transform {
transform: rotate(1);
child: rounded-clip {
clip: -30000 -30000 60000 60000 / 5;
child: color {
bounds: -30000 -30000 60000 60000;
color: rgb(255,0,0);
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

View File

@@ -0,0 +1,134 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include <gtk/gtk.h>
#include "gsk/gskcurveprivate.h"
static gboolean
measure_segment (const graphene_point_t *from,
const graphene_point_t *to,
float from_t,
float to_t,
GskCurveLineReason reason,
gpointer data)
{
float *length = data;
*length += graphene_point_distance (from, to, NULL, NULL);
return TRUE;
}
static float
measure_length (const GskCurve *curve)
{
float result = 0;
gsk_curve_decompose (curve, 0.5, measure_segment, &result);
return result;
}
/* This is a pretty nasty conic that makes it obvious that split()
* does not respect the progress values, so split() twice with
* scaled factor won't work.
*/
static void
test_conic_segment (void)
{
GskCurve c, s, e, m;
graphene_point_t pts[4] = {
GRAPHENE_POINT_INIT (-1856.131591796875, 46.217609405517578125),
GRAPHENE_POINT_INIT (-1555.9866943359375, 966.0810546875),
GRAPHENE_POINT_INIT (98.94945526123046875, 0),
GRAPHENE_POINT_INIT (-1471.33154296875, 526.701171875)
};
float start = 0.02222645096480846405029296875;
float end = 0.982032716274261474609375;
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CONIC, pts));
gsk_curve_split (&c, start, &s, NULL);
gsk_curve_segment (&c, start, end, &m);
gsk_curve_split (&c, end, NULL, &e);
g_assert_cmpfloat_with_epsilon (measure_length (&c), measure_length (&s) + measure_length (&m) + measure_length (&e), 0.03125);
}
static void
test_curve_tangents (void)
{
GskCurve c;
graphene_point_t p[4];
graphene_vec2_t t;
graphene_point_init (&p[0], 0, 0);
graphene_point_init (&p[1], 100, 0);
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
gsk_curve_get_start_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
gsk_curve_get_end_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
graphene_point_init (&p[0], 0, 0);
graphene_point_init (&p[1], 0, 100);
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
gsk_curve_get_start_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
gsk_curve_get_end_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
graphene_point_init (&p[0], 0, 0);
graphene_point_init (&p[1], 100, 0);
p[2] = GRAPHENE_POINT_INIT (g_test_rand_double_range (0, 20), 0);
graphene_point_init (&p[3], 100, 100);
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CONIC, p));
gsk_curve_get_start_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
gsk_curve_get_end_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
graphene_point_init (&p[0], 0, 0);
graphene_point_init (&p[1], 50, 0);
graphene_point_init (&p[2], 100, 50);
graphene_point_init (&p[3], 100, 100);
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CURVE, p));
gsk_curve_get_start_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
gsk_curve_get_end_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/curve/special/conic-segment", test_conic_segment);
g_test_add_func ("/curve/special/tangents", test_curve_tangents);
return g_test_run ();
}

205
testsuite/gsk/curve.c Normal file
View File

@@ -0,0 +1,205 @@
#include <gtk/gtk.h>
#include "gsk/gskcurveprivate.h"
static void
init_random_point (graphene_point_t *p)
{
p->x = g_test_rand_double_range (0, 1000);
p->y = g_test_rand_double_range (0, 1000);
}
static float
random_weight (void)
{
if (g_test_rand_bit ())
return g_test_rand_double_range (0, 100);
else
return 1.0 / g_test_rand_double_range (1, 100);
}
static void
init_random_curve (GskCurve *curve)
{
switch (g_test_rand_int_range (GSK_PATH_LINE, GSK_PATH_CONIC + 1))
{
case GSK_PATH_LINE:
{
graphene_point_t p[2];
init_random_point (&p[0]);
init_random_point (&p[1]);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_LINE, p));
}
break;
case GSK_PATH_CURVE:
{
graphene_point_t p[4];
init_random_point (&p[0]);
init_random_point (&p[1]);
init_random_point (&p[2]);
init_random_point (&p[3]);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CURVE, p));
}
break;
case GSK_PATH_CONIC:
{
graphene_point_t p[4];
init_random_point (&p[0]);
init_random_point (&p[1]);
p[2] = GRAPHENE_POINT_INIT (random_weight(), 0);
init_random_point (&p[3]);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CONIC, p));
}
break;
default:
g_assert_not_reached ();
}
}
static void
test_curve_tangents (void)
{
for (int i = 0; i < 100; i++)
{
GskCurve c;
graphene_vec2_t vec, exact;
init_random_curve (&c);
gsk_curve_get_tangent (&c, 0, &vec);
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
gsk_curve_get_start_tangent (&c, &exact);
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
gsk_curve_get_tangent (&c, 1, &vec);
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
gsk_curve_get_end_tangent (&c, &exact);
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
}
}
static void
test_curve_points (void)
{
for (int i = 0; i < 100; i++)
{
GskCurve c;
graphene_point_t p;
init_random_curve (&c);
/* We can assert equality here because evaluating the polynomials with 0
* has no effect on accuracy.
*/
gsk_curve_get_point (&c, 0, &p);
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &p));
/* But here we evaluate the polynomials with 1 which gives the highest possible
* accuracy error. So we'll just be generous here.
*/
gsk_curve_get_point (&c, 1, &p);
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c), &p, 0.05));
}
}
/* at this point the subdivision stops and the decomposer
* violates tolerance rules
*/
#define MIN_PROGRESS (1/1024.f)
typedef struct
{
graphene_point_t p;
float t;
} PointOnLine;
static gboolean
add_line_to_array (const graphene_point_t *from,
const graphene_point_t *to,
float from_progress,
float to_progress,
GskCurveLineReason reason,
gpointer user_data)
{
GArray *array = user_data;
PointOnLine *last = &g_array_index (array, PointOnLine, array->len - 1);
g_assert (array->len > 0);
g_assert_cmpfloat (from_progress, >=, 0.0f);
g_assert_cmpfloat (from_progress, <, to_progress);
g_assert_cmpfloat (to_progress, <=, 1.0f);
g_assert_true (graphene_point_equal (&last->p, from));
g_assert_cmpfloat (last->t, ==, from_progress);
g_array_append_vals (array, (PointOnLine[1]) { { *to, to_progress } }, 1);
return TRUE;
}
static void
test_curve_decompose (void)
{
static const float tolerance = 0.5;
for (int i = 0; i < 100; i++)
{
GArray *array;
GskCurve c;
init_random_curve (&c);
array = g_array_new (FALSE, FALSE, sizeof (PointOnLine));
g_array_append_vals (array, (PointOnLine[1]) { { *gsk_curve_get_start_point (&c), 0.f } }, 1);
g_assert_true (gsk_curve_decompose (&c, tolerance, add_line_to_array, array));
g_assert_cmpint (array->len, >=, 2); /* We at least got a line to the end */
g_assert_cmpfloat (g_array_index (array, PointOnLine, array->len - 1).t, ==, 1.0);
for (int j = 0; j < array->len; j++)
{
PointOnLine *pol = &g_array_index (array, PointOnLine, j);
graphene_point_t p;
/* Check that the points we got are actually on the line */
gsk_curve_get_point (&c, pol->t, &p);
g_assert_true (graphene_point_near (&pol->p, &p, 0.05));
/* Check that the mid point is not further than the tolerance */
if (j > 0)
{
PointOnLine *last = &g_array_index (array, PointOnLine, j - 1);
graphene_point_t mid;
if (pol->t - last->t > MIN_PROGRESS)
{
graphene_point_interpolate (&last->p, &pol->p, 0.5, &mid);
gsk_curve_get_point (&c, (pol->t + last->t) / 2, &p);
/* The decomposer does this cheaper Manhattan distance test,
* so graphene_point_near() does not work */
g_assert_cmpfloat (fabs (mid.x - p.x), <=, tolerance);
g_assert_cmpfloat (fabs (mid.y - p.y), <=, tolerance);
}
}
}
}
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/curve/points", test_curve_points);
g_test_add_func ("/curve/tangents", test_curve_tangents);
g_test_add_func ("/curve/decompose", test_curve_decompose);
return g_test_run ();
}

181
testsuite/gsk/dash.c Normal file
View File

@@ -0,0 +1,181 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include <gtk/gtk.h>
#include "gsk/gskpathdashprivate.h"
static gboolean
build_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_CURVE:
gsk_path_builder_curve_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
static void
test_simple (void)
{
const struct {
const char *test;
float dash[4];
gsize n_dash;
float dash_offset;
const char *result;
} tests[] = {
/* a line with a dash of a quarter its size, very simple test */
{
"M 0 0 L 20 0",
{ 5, }, 1, 0.f,
"M 0 0 L 5 0 M 10 0 L 15 0",
},
/* a square with a dash of half its size, another simple test */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 0.f,
"M 10 0 L 10 5 M 10 10 L 5 10 M 0 10 L 0 5 M 0 0 L 5 0"
},
/* a square smaller than the dash, make sure it closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 50, }, 1, 0.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a square exactly the dash's size, make sure it still closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 40, }, 1, 0.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a dash with offset */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 2.5f,
"M 7.5 0 L 10 0 L 10 2.5 M 10 7.5 L 10 10 L 7.5 10 M 2.5 10 L 0 10 L 0 7.5 M 0 2.5 L 0 0 L 2.5 0"
},
/* a dash with offset, but this time the rect isn't closed */
{
"M 0 0 L 10 0 L 10 10 L 0 10 L 0 0",
{ 5, }, 1, 2.5f,
"M 0 0 L 2.5 0 M 7.5 0 L 10 0 L 10 2.5 M 10 7.5 L 10 10 L 7.5 10 M 2.5 10 L 0 10 L 0 7.5 M 0 2.5 L 0 0"
},
/* a dash with offset into an empty dash */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 7.5f,
"M 2.5 0 L 7.5 0 M 10 2.5 L 10 7.5 M 7.5 10 L 2.5 10 M 0 7.5 L 0 2.5"
},
/* a dash with offset where the whole rectangle fits into one element - make sure it closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 1, 1, 100 }, 3, 3.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a dash with 0-length elements, aka dotting */
{
"M 0 0 h 10 v 10 h -10 z",
{ 0, 5 }, 2, 0.f,
"M 5 0 M 10 0 M 10 5 M 10 10 M 5 10 M 0 10 M 0 5 M 0 0"
},
/* a dash of a circle */
{
"M 10 5 O 10 10, 5 10, 0.70710676908493042 O 0 10, 0 5, 0.70710676908493042 O 0 0, 5 0, 0.70710676908493042 O 10 0, 10 5, 0.70710676908493042 Z",
{ 32, }, 1, 0.f,
"M 10 5 O 10 10, 5 10, 0.70710676908493042 O 0 10, 0 5, 0.70710676908493042 O 0 0, 5 0, 0.70710676908493042 O 10 0, 10 5, 0.70710676908493042 Z",
},
/* a dash with offset and 2 contours */
{
"M 10 10 h 10 v 10 h -10 z M 20 20 h 10 v 10 h -10 z",
{ 5, }, 1, 2.5f,
"M 17.5 10 L 20 10 L 20 12.5 M 20 17.5 L 20 20 L 17.5 20 M 12.5 20 L 10 20 L 10 17.5 M 10 12.5 L 10 10 L 12.5 10 "
"M 27.5 20 L 30 20 L 30 22.5 M 30 27.5 L 30 30 L 27.5 30 M 22.5 30 L 20 30 L 20 27.5 M 20 22.5 L 20 20 L 22.5 20"
},
};
GskPath *path, *result;
GskPathBuilder *builder;
GskStroke *stroke;
char *s;
for (gsize i = 0; i < G_N_ELEMENTS(tests); i++)
{
stroke = gsk_stroke_new (1);
gsk_stroke_set_dash (stroke, tests[i].dash, tests[i].n_dash);
gsk_stroke_set_dash_offset (stroke, tests[i].dash_offset);
path = gsk_path_parse (tests[i].test);
g_assert_nonnull (path);
s = gsk_path_to_string (path);
g_assert_cmpstr (s, ==, tests[i].test);
g_free (s);
builder = gsk_path_builder_new ();
gsk_path_dash (path, stroke, 0.5, build_path, builder);
result = gsk_path_builder_free_to_path (builder);
s = gsk_path_to_string (result);
g_assert_cmpstr (s, ==, tests[i].result);
g_free (s);
gsk_path_unref (result);
gsk_stroke_free (stroke);
gsk_path_unref (path);
}
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/dash/simple", test_simple);
return g_test_run ();
}

View File

@@ -36,6 +36,7 @@ compare_render_tests = [
'color-matrix-identity',
'color-matrix-parsing',
'crossfade-clip-both-children',
'cross-fade-clipped-with-huge-children',
'cross-fade-in-opacity',
'cross-fade-in-rotate',
'css-background',
@@ -64,6 +65,7 @@ compare_render_tests = [
'inset-shadow-multiple',
'invalid-transform',
'issue-3615',
'linear-gradient-with-64-colorstops',
'mask',
'mask-clipped-inverted-alpha',
'mask-modes',
@@ -81,6 +83,7 @@ compare_render_tests = [
'repeat-no-repeat',
'repeat-empty-child-bounds',
'repeat-negative-coords',
'repeat-scaling',
'repeat-texture',
'repeating-gradient-scaled',
'scale-textures-negative-ngl',
@@ -92,6 +95,8 @@ compare_render_tests = [
'texture-scale-magnify-rotate',
'texture-scale-stripes',
'texture-url',
'transform-huge-child',
'transform-huge-child-3d',
'transform-in-transform',
'transform-in-transform-in-transform',
'z-transform-clipping-bounds-3d',
@@ -108,6 +113,7 @@ informative_render_tests = [
renderers = [
{ 'name': 'gl' },
{ 'name': 'vulkan' },
{ 'name': 'broadway', 'exclude_term': '-3d' },
{ 'name': 'cairo', 'exclude_term': '-3d' },
]
@@ -138,7 +144,7 @@ foreach renderer : renderers
'gsk-compare-' + renderer_name,
]
if compare_xfails.contains(testname)
if compare_xfails.contains(testname) or renderer_name == 'vulkan'
suites += 'failing'
endif
@@ -147,7 +153,8 @@ foreach renderer : renderers
endif
if ((exclude_term == '' or not testname.contains(exclude_term)) and
(renderer_name != 'broadway' or broadway_enabled))
(renderer_name != 'broadway' or broadway_enabled) and
(renderer_name != 'vulkan' or have_vulkan))
test(renderer_name + ' ' + testname, compare_render,
args: [
'--output', join_paths(meson.current_build_dir(), 'compare', renderer_name),
@@ -357,6 +364,11 @@ foreach test : node_parser_tests
endforeach
tests = [
['curve', ['../../gsk/gskcurve.c'], ['-DGTK_COMPILATION']],
['curve-special-cases', ['../../gsk/gskcurve.c'], ['-DGTK_COMPILATION']],
['dash'],
['path'],
['path-special-cases'],
['transform'],
['shader'],
]

View File

@@ -0,0 +1,319 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include <gtk/gtk.h>
/* testcases from path_parser.rs in librsvg */
static void
test_rsvg_parse (void)
{
struct {
const char *in;
const char *out;
} tests[] = {
{ "", "" },
// numbers
{ "M 10 20", "M 10 20" },
{ "M -10 -20", "M -10 -20" },
{ "M .10 0.20", "M 0.1 0.2" },
{ "M -.10 -0.20", "M -0.1 -0.2" },
{ "M-.10-0.20", "M -0.1 -0.2" },
{ "M10.5.50", "M 10.5 0.5" },
{ "M.10.20", "M 0.1 0.2" },
{ "M .10E1 .20e-4", "M 1 2e-05" },
{ "M-.10E1-.20", "M -1 -0.2" },
{ "M10.10E2 -0.20e3", "M 1010 -200" },
{ "M-10.10E2-0.20e-3", "M -1010 -0.0002" },
{ "M1e2.5", "M 100 0.5" },
{ "M1e-2.5", "M 0.01 0.5" },
{ "M1e+2.5", "M 100 0.5" },
// bogus numbers
{ "M+", NULL },
{ "M-", NULL },
{ "M+x", NULL },
{ "M10e", NULL },
{ "M10ex", NULL },
{ "M10e-", NULL },
{ "M10e+x", NULL },
// numbers with comma
{ "M 10, 20", "M 10 20" },
{ "M -10,-20", "M -10 -20" },
{ "M.10 , 0.20", "M 0.1 0.2" },
{ "M -.10, -0.20 ", "M -0.1 -0.2" },
{ "M-.10-0.20", "M -0.1 -0.2" },
{ "M.10.20", "M 0.1 0.2" },
{ "M .10E1,.20e-4", "M 1 2e-05" },
{ "M-.10E-2,-.20", "M -0.001 -0.2" },
{ "M10.10E2,-0.20e3", "M 1010 -200" },
{ "M-10.10E2,-0.20e-3", "M -1010 -0.0002" },
// single moveto
{ "M 10 20 ", "M 10 20" },
{ "M10,20 ", "M 10 20" },
{ "M10 20 ", "M 10 20" },
{ " M10,20 ", "M 10 20" },
// relative moveto
{ "m10 20", "M 10 20" },
// absolute moveto with implicit lineto
{ "M10 20 30 40", "M 10 20 L 30 40" },
{ "M10,20,30,40", "M 10 20 L 30 40" },
{ "M.1-2,3E2-4", "M 0.1 -2 L 300 -4" },
// relative moveto with implicit lineto
{ "m10 20 30 40", "M 10 20 L 40 60" },
// relative moveto with relative lineto sequence
{ "m 46,447 l 0,0.5 -1,0 -1,0 0,1 0,12",
"M 46 447 L 46 447.5 L 45 447.5 L 44 447.5 L 44 448.5 L 44 460.5" },
// absolute moveto with implicit linetos
{ "M10,20 30,40,50 60", "M 10 20 L 30 40 L 50 60" },
// relative moveto with implicit linetos
{ "m10 20 30 40 50 60", "M 10 20 L 40 60 L 90 120" },
// absolute moveto moveto
{ "M10 20 M 30 40", "M 10 20 M 30 40" },
// relative moveto moveto
{ "m10 20 m 30 40", "M 10 20 M 40 60" },
// relative moveto lineto moveto
{ "m10 20 30 40 m 50 60", "M 10 20 L 40 60 M 90 120" },
// absolute moveto lineto
{ "M10 20 L30,40", "M 10 20 L 30 40" },
// relative moveto lineto
{ "m10 20 l30,40", "M 10 20 L 40 60" },
// relative moveto lineto lineto abs lineto
{ "m10 20 30 40l30,40,50 60L200,300",
"M 10 20 L 40 60 L 70 100 L 120 160 L 200 300" },
// horizontal lineto
{ "M10 20 H30", "M 10 20 L 30 20" },
{ "M 10 20 H 30 40", "M 10 20 L 30 20 L 40 20" },
{ "M10 20 H30,40-50", "M 10 20 L 30 20 L 40 20 L -50 20" },
{ "m10 20 h30,40-50", "M 10 20 L 40 20 L 80 20 L 30 20" },
// vertical lineto
{ "M10 20 V30", "M 10 20 L 10 30" },
{ "M10 20 V30 40", "M 10 20 L 10 30 L 10 40" },
{ "M10 20 V30,40-50", "M 10 20 L 10 30 L 10 40 L 10 -50" },
{ "m10 20 v30,40-50", "M 10 20 L 10 50 L 10 90 L 10 40" },
// curveto
{ "M10 20 C 30,40 50 60-70,80", "M 10 20 C 30 40, 50 60, -70 80" },
{ "M10 20 C 30,40 50 60-70,80,90 100,110 120,130,140",
"M 10 20 C 30 40, 50 60, -70 80 C 90 100, 110 120, 130 140" },
{ "m10 20 c 30,40 50 60-70,80,90 100,110 120,130,140",
"M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
{ "m10 20 c 30,40 50 60-70,80 90 100,110 120,130,140",
"M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
// smooth curveto
{ "M10 20 S 30,40-50,60", "M 10 20 C 10 20, 30 40, -50 60" },
{ "M10 20 S 30,40 50 60-70,80,90 100",
"M 10 20 C 10 20, 30 40, 50 60 C 70 80, -70 80, 90 100" },
// quadratic curveto
{ "M10 20 Q30 40 50 60", "M 10 20 C 23.3333333 33.3333333, 36.6666667 46.6666667, 50 60" },
{ "M10 20 Q30 40 50 60,70,80-90 100",
"M 10 20 C 23.3333333 33.3333333, 36.6666667 46.6666667, 50 60 C 63.3333333 73.3333333, 16.6666667 86.6666667, -90 100" },
{ "m10 20 q 30,40 50 60-70,80 90 100",
"M 10 20 C 30 46.6666667, 46.6666667 66.6666667, 60 80 C 13.3333333 133.3333333, 43.3333333 166.6666667, 150 180" },
// smooth quadratic curveto
{ "M10 20 T30 40", "M 10 20 C 10 20, 16.6666667 26.6666667, 30 40" },
{ "M10 20 Q30 40 50 60 T70 80",
"M 10 20 C 23.3333333 33.3333333, 36.6666667 46.6666667, 50 60 C 63.3333333 73.3333333, 70 80, 70 80" },
{ "m10 20 q 30,40 50 60t-70,80",
"M 10 20 C 30 46.6666667, 46.6666667 66.6666667, 60 80 C 73.3333333 93.3333333, 50 120, -10 160" },
// elliptical arc. Exact numbers depend on too much math, so just verify
// that these parse successfully
{ "M 1 3 A 1 2 3 00 6 7", "path" },
{ "M 1 2 A 1 2 3 016 7", "path" },
{ "M 1 2 A 1 2 3 10,6 7", "path" },
{ "M 1 2 A 1 2 3 1,1 6 7", "path" },
{ "M 1 2 A 1 2 3 1 1 6 7", "path" },
{ "M 1 2 A 1 2 3 1 16 7", "path" },
// close path
{ "M10 20 Z", "M 10 20 Z" },
{ "m10 20 30 40 m 50 60 70 80 90 100z", "M 10 20 L 40 60 M 90 120 L 160 200 L 250 300 Z" },
// must start with moveto
{ " L10 20", NULL },
// moveto args
{ "M", NULL },
{ "M,", NULL },
{ "M10", NULL },
{ "M10,", NULL },
{ "M10x", NULL },
{ "M10,x", NULL },
{ "M10-20,", NULL },
{ "M10-20-30", NULL },
{ "M10-20-30 x", NULL },
// closepath args
{ "M10-20z10", NULL },
{ "M10-20z,", NULL },
// lineto args
{ "M10-20L10", NULL },
{ "M 10,10 L 20,20,30", NULL },
{ "M 10,10 L 20,20,", NULL },
// horizontal lineto args
{ "M10-20H", NULL },
{ "M10-20H,", NULL },
{ "M10-20H30,", NULL },
// vertical lineto args
{ "M10-20v", NULL },
{ "M10-20v,", NULL },
{ "M10-20v30,", NULL },
// curveto args
{ "M10-20C1", NULL },
{ "M10-20C1,", NULL },
{ "M10-20C1 2", NULL },
{ "M10-20C1,2,", NULL },
{ "M10-20C1 2 3", NULL },
{ "M10-20C1,2,3", NULL },
{ "M10-20C1,2,3,", NULL },
{ "M10-20C1 2 3 4", NULL },
{ "M10-20C1,2,3,4", NULL },
{ "M10-20C1,2,3,4,", NULL },
{ "M10-20C1 2 3 4 5", NULL },
{ "M10-20C1,2,3,4,5", NULL },
{ "M10-20C1,2,3,4,5,", NULL },
{ "M10-20C1,2,3,4,5,6,", NULL },
// smooth curveto args
{ "M10-20S1", NULL },
{ "M10-20S1,", NULL },
{ "M10-20S1 2", NULL },
{ "M10-20S1,2,", NULL },
{ "M10-20S1 2 3", NULL },
{ "M10-20S1,2,3,", NULL },
{ "M10-20S1,2,3,4,", NULL },
// quadratic curveto args
{ "M10-20Q1", NULL },
{ "M10-20Q1,", NULL },
{ "M10-20Q1 2", NULL },
{ "M10-20Q1,2,", NULL },
{ "M10-20Q1 2 3", NULL },
{ "M10-20Q1,2,3", NULL },
{ "M10-20Q1,2,3,", NULL },
{ "M10 20 Q30 40 50 60,", NULL },
// smooth quadratic curveto args
{ "M10-20T1", NULL },
{ "M10-20T1,", NULL },
{ "M10 20 T 30 40,", NULL },
// elliptical arc args
{ "M10-20A1", NULL },
{ "M10-20A1,", NULL },
{ "M10-20A1 2", NULL },
{ "M10-20A1 2,", NULL },
{ "M10-20A1 2 3", NULL },
{ "M10-20A1 2 3,", NULL },
{ "M10-20A1 2 3 4", NULL },
{ "M10-20A1 2 3 1", NULL },
{ "M10-20A1 2 3,1,", NULL },
{ "M10-20A1 2 3 1 5", NULL },
{ "M10-20A1 2 3 1 1", NULL },
{ "M10-20A1 2 3,1,1,", NULL },
{ "M10-20A1 2 3 1 1 6", NULL },
{ "M10-20A1 2 3,1,1,6,", NULL },
{ "M 1 2 A 1 2 3 1.0 0.0 6 7", NULL },
{ "M10-20A1 2 3,1,1,6,7,", NULL },
// misc
{ "M.. 1,0 0,100000", NULL },
{ "M 10 20,M 10 20", NULL },
{ "M 10 20, M 10 20", NULL },
{ "M 10 20, M 10 20", NULL },
{ "M 10 20, ", NULL },
};
int i;
for (i = 0; i < G_N_ELEMENTS (tests); i++)
{
GskPath *path;
char *string;
char *string2;
if (g_test_verbose ())
g_print ("%d: %s\n", i, tests[i].in);
path = gsk_path_parse (tests[i].in);
if (tests[i].out)
{
g_assert_nonnull (path);
string = gsk_path_to_string (path);
gsk_path_unref (path);
if (strcmp (tests[i].out, "path") != 0)
{
/* Preferred, but doesn't work, because
* gsk_path_print() prints numbers with
* insane accuracy */
/* g_assert_cmpstr (tests[i].out, ==, string); */
path = gsk_path_parse (tests[i].out);
g_assert_nonnull (path);
string2 = gsk_path_to_string (path);
gsk_path_unref (path);
g_assert_cmpstr (string, ==, string2);
}
path = gsk_path_parse (string);
g_assert_nonnull (path);
string2 = gsk_path_to_string (path);
gsk_path_unref (path);
g_assert_cmpstr (string, ==, string2);
g_free (string);
g_free (string2);
}
else
g_assert_null (path);
}
}
/* Test that circles and rectangles serialize as expected and can be
* round-tripped through strings.
*/
static void
test_serialize_custom_contours (void)
{
GskPathBuilder *builder;
GskPath *path;
GskPath *path1;
char *string;
char *string1;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 50);
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (111, 222, 333, 444));
path = gsk_path_builder_free_to_path (builder);
string = gsk_path_to_string (path);
g_assert_cmpstr ("M 150 100 A 50 50 0 0 0 50 100 A 50 50 0 0 0 150 100 z M 111 222 h 333 v 444 h -333 z", ==, string);
path1 = gsk_path_parse (string);
string1 = gsk_path_to_string (path1);
g_assert_cmpstr (string, ==, string1);
g_free (string);
g_free (string1);
gsk_path_unref (path);
gsk_path_unref (path1);
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/path/rsvg-parse", test_rsvg_parse);
g_test_add_func ("/path/serialize-custom-contours", test_serialize_custom_contours);
return g_test_run ();
}

1152
testsuite/gsk/path.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -130,6 +130,14 @@ count_nodes (GskRenderNode *node,
d = MAX (d, dd);
break;
case GSK_FILL_NODE:
count_nodes (gsk_fill_node_get_child (node), counts, &d);
break;
case GSK_STROKE_NODE:
count_nodes (gsk_stroke_node_get_child (node), counts, &d);
break;
case GSK_NOT_A_RENDER_NODE:
default:
g_assert_not_reached ();