Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

elements.comp scene traversal may leave gaps in paths #62

Closed
eliasnaur opened this issue Feb 13, 2021 · 5 comments
Closed

elements.comp scene traversal may leave gaps in paths #62

eliasnaur opened this issue Feb 13, 2021 · 5 comments

Comments

@eliasnaur
Copy link
Collaborator

eliasnaur commented Feb 13, 2021

The fill algorithm implemented by path_coarse.comp assumes that closed paths contain no gaps. Unfortunately, the non-deterministic scene tracersal in elements.comp may introduce gaps in otherwise gap-less paths.

Consider two fill path segments through endpoints A, B, C in a scene fragment with three preceding transforms. The segments are initialized with the identity transform, I.

T₁·T₂·T₃·S(I, A, B)·S(I, B, C)

Since scene element operations are associative, elements.comp evaluates them in a GPU-friendly but non-deterministic order. In particular, it may use the ordering

S((T₁·T₂)·T₃, A, B)·S(T₁·(T₂·T₃), B, C)

Because of numerical imprecision, the path segments may end up with slightly different transformations,

S(M₁, A, B)·S(M₂, B, C)

Finally, M₁·B is not in general equal to M₂·B, leading to a gap in the path that includes the two segments.

To verify my claim, I made the following change,

diff --git gpu/shaders/elements.comp gpu/shaders/elements.comp
index a43c270..b5f2342 100644
--- gpu/shaders/elements.comp
+++ gpu/shaders/elements.comp
@@ -259,34 +259,8 @@ void main() {
                     State their_prefix = State_read(state_prefix_ref(look_back_ix));
                     exclusive = combine_state(their_prefix, exclusive);
                     break;
-                } else if (flag == FLAG_AGGREGATE_READY) {
-                    their_agg = State_read(state_aggregate_ref(look_back_ix));
-                    exclusive = combine_state(their_agg, exclusive);
-                    look_back_ix--;
-                    their_ix = 0;
-                    continue;
-                }
+                } 
                 // else spin
-
-                // Unfortunately there's no guarantee of forward progress of other
-                // workgroups, so compute a bit of the aggregate before trying again.
-                // In the worst case, spinning stops when the aggregate is complete.
-                ElementRef ref = ElementRef((look_back_ix * PARTITION_SIZE + their_ix) * Element_size);
-                State s = map_element(ref);
-                if (their_ix == 0) {
-                    their_agg = s;
-                } else {
-                    their_agg = combine_state(their_agg, s);
-                }
-                their_ix++;
-                if (their_ix == PARTITION_SIZE) {
-                    exclusive = combine_state(their_agg, exclusive);
-                    if (look_back_ix == 0) {
-                        break;
-                    }
-                    look_back_ix--;
-                    their_ix = 0;
-                }
             }
 
             // step 5 of paper: compute inclusive prefix

which removes the non-deterministic evaluation order. As expected, the path gaps disappeared.

I don't have a good fix yet. It's possible that the endpoints can be propagated in the scene traversal, at the cost of state memory. Perhaps a PathSeparator scene element should be added to delimit closed paths. It may also be a good idea to make path scene segment have implicit start points, saving a bit of memory.

eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Feb 17, 2021
Gaps may be introduced by the redundant path segment endpoints. Track
each segment's successor and use a single endpoint per pair of segments.
The last segment in a path uses the first segment's endpoint.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Feb 17, 2021
Gaps may be introduced by the redundant path segment endpoints. Track
each segment's successor and use a single endpoint per pair of segments.
The last segment in a path uses the first segment's endpoint.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
whereswaldon pushed a commit to gioui/gio that referenced this issue Feb 21, 2021
See linebender/vello#62 for description
of the issue. The fix is the Gio copy of the piet-gpu fix.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Feb 24, 2021
Gaps may be introduced by the redundant path segment endpoints. Track
each segment's successor and use a single endpoint per pair of segments.
The last segment in a path uses the first segment's endpoint.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
@raphlinus
Copy link
Contributor

This is a very interesting problem, thanks for bringing it to my attention. I will study your solution, but also consider some alternatives. Just to brainstorm, one is to represent the affine transformation using fixed point arithmetic, which should make the math deterministic, but with limitations.

@eliasnaur
Copy link
Collaborator Author

@raphlinus I'd like to take a stab at implementing your idea of combining transformations in their own monoid in elements.comp. Are you currently (or anyone else) working on this?

Related, is the comment about shared arrays of structs in elements.comp still relevant? If we're going to have two monoid passes, it would be a big readability to work on structs.

FWIW, the malloc functionality (example) uses shared arrays of structs, and I haven't heard you complain.

@raphlinus
Copy link
Contributor

I'm open to you having a crack at it. If you would like to write a brief design doc before digging into the code, I'll try to prioritize it (I've had difficulty switching gears to focus on piet-gpu, but plan for it to be my main work over the next weeks).

I think we can do shared structs now, the bug in the NV shader compiler has been fixed a while ago and we're not at the point where we have to accumulate workarounds for driver bugs - yet.

eliasnaur added a commit that referenced this issue Mar 13, 2021
The non-deterministic evaluation order of the state monoid may end up
assigning inconsistent transformations to path endpoints. This change
avoids that problem by evaluating transformation state before all other
state.

Fixes #62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
@eliasnaur
Copy link
Collaborator Author

eliasnaur commented Mar 13, 2021

Please review #71 which contains an implementation of your separate transformation monoid. It works for me in Gio, and the piet-gpu tiger.

EDIT: I lied. It doesn't always work correctly. Stay tuned.

whereswaldon pushed a commit to gioui/gio that referenced this issue Mar 13, 2021
This is effectively a revert of [0], reintroducing the path gaps
described in [1]. A follow-up change will implement another attempt.

[0] https://gioui.org/commit/2feec23561cd84d6b8ddbab84a202df66b123208
[1] linebender/vello#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
whereswaldon pushed a commit to gioui/gio that referenced this issue Mar 13, 2021
This is an alternative and better implementation of [0] and fixes
rendering glitches caused by inconsistent transformations of otherwise
closed paths. See [1] for a detailed description.

[0] https://gioui.org/commit/2feec23561cd84d6b8ddbab84a202df66b123208
[1] linebender/vello#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 14, 2021
The non-deterministic evaluation order of the state monoid may end up
assigning inconsistent transformations to path endpoints. This change
avoids that problem by evaluating transformation state before all other
state.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 15, 2021
As described in linebender#62, the non-deterministic scene monoid may result in
slightly different transformations for path segments in an otherwise
closed path.

This change ensure consistent transformation across paths in three steps.

First, absolute transformations computed by the scene monoid is stored
along with path segments and annotated elements.

Second, elements.comp no longer transforms path segments. Instead,
each path segment is stored untransformed along with a reference to its
absolute transformation.

Finally, path_coarse performs the transformation of path segments.
Because all segments in a path shared a single transformation reference,
the inconsistency in linebender#62 is avoided.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 15, 2021
Gaps may be introduced by the redundant path segment endpoints. Track
each segment's successor and use a single endpoint per pair of segments.
The last segment in a path uses the first segment's endpoint.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
@eliasnaur eliasnaur mentioned this issue Mar 15, 2021
@eliasnaur
Copy link
Collaborator Author

Ok, I believe I found a correct (and simpler) fix, a hybrid between your resolved transforms and my tracking of path endpoints. I abandoned the second pass, because I ended up needing both the resolved transformations (for path segments) and the relative transformations (for the bounding box monoid). The new approach keeps a single pass, and doesn't need an end path flag.

Tested with Gio and the piet-gpu tiger.

Please review at #72.

eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 15, 2021
As described in linebender#62, the non-deterministic scene monoid may result in
slightly different transformations for path segments in an otherwise
closed path.

This change ensures consistent transformation across paths in three steps.

First, absolute transformations computed by the scene monoid is stored
along with path segments and annotated elements.

Second, elements.comp no longer transforms path segments. Instead, each
segment is stored untransformed along with a reference to its absolute
transformation.

Finally, path_coarse performs the transformation of path segments.
Because all segments in a path share a single transformation reference,
the inconsistency in linebender#62 is avoided.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 15, 2021
As described in linebender#62, the non-deterministic scene monoid may result in
slightly different transformations for path segments in an otherwise
closed path.

This change ensures consistent transformation across paths in three steps.

First, absolute transformations computed by the scene monoid is stored
along with path segments and annotated elements.

Second, elements.comp no longer transforms path segments. Instead, each
segment is stored untransformed along with a reference to its absolute
transformation.

Finally, path_coarse performs the transformation of path segments.
Because all segments in a path share a single transformation reference,
the inconsistency in linebender#62 is avoided.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 15, 2021
As described in linebender#62, the non-deterministic scene monoid may result in
slightly different transformations for path segments in an otherwise
closed path.

This change ensures consistent transformation across paths in three steps.

First, absolute transformations computed by the scene monoid is stored
along with path segments and annotated elements.

Second, elements.comp no longer transforms path segments. Instead, each
segment is stored untransformed along with a reference to its absolute
transformation.

Finally, path_coarse performs the transformation of path segments.
Because all segments in a path share a single transformation reference,
the inconsistency in linebender#62 is avoided.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
whereswaldon pushed a commit to gioui/gio that referenced this issue Mar 15, 2021
This is another attempt at fixing the issue described in [0], the
previous attempt was reverted[1].

This change fixes the issue by tracking resolved transformations and
ensure that all segments within a path share a single transformation.

[0] linebender/vello#62
[1] https://gioui.org/commit/2b21b48a7c5c4451deb642c164548a134bb9ad06

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 15, 2021
As described in linebender#62, the non-deterministic scene monoid may result in
slightly different transformations for path segments in an otherwise
closed path.

This change ensures consistent transformation across paths in three steps.

First, absolute transformations computed by the scene monoid is stored
along with path segments and annotated elements.

Second, elements.comp no longer transforms path segments. Instead, each
segment is stored untransformed along with a reference to its absolute
transformation.

Finally, path_coarse performs the transformation of path segments.
Because all segments in a path share a single transformation reference,
the inconsistency in linebender#62 is avoided.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 17, 2021
As described in linebender#62, the non-deterministic scene monoid may result in
slightly different transformations for path segments in an otherwise
closed path.

This change ensures consistent transformation across paths in three steps.

First, absolute transformations computed by the scene monoid is stored
along with path segments and annotated elements.

Second, elements.comp no longer transforms path segments. Instead, each
segment is stored untransformed along with a reference to its absolute
transformation.

Finally, path_coarse performs the transformation of path segments.
Because all segments in a path share a single transformation reference,
the inconsistency in linebender#62 is avoided.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
eliasnaur added a commit to eliasnaur/piet-gpu that referenced this issue Mar 19, 2021
As described in linebender#62, the non-deterministic scene monoid may result in
slightly different transformations for path segments in an otherwise
closed path.

This change ensures consistent transformation across paths in three steps.

First, absolute transformations computed by the scene monoid is stored
along with path segments and annotated elements.

Second, elements.comp no longer transforms path segments. Instead, each
segment is stored untransformed along with a reference to its absolute
transformation.

Finally, path_coarse performs the transformation of path segments.
Because all segments in a path share a single transformation reference,
the inconsistency in linebender#62 is avoided.

Fixes linebender#62

Signed-off-by: Elias Naur <mail@eliasnaur.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants