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

rect::intersection can fail due to floating point precision #306

Closed
mattwoodrow opened this issue Oct 25, 2018 · 6 comments
Closed

rect::intersection can fail due to floating point precision #306

mattwoodrow opened this issue Oct 25, 2018 · 6 comments
Labels
bug

Comments

@mattwoodrow
Copy link

@mattwoodrow mattwoodrow commented Oct 25, 2018

I have a testcase with tiling where we're calling rect::intersection with the following rects:

{20, 280.399994, 200, 256}
{20, 280.399994, 200, 256.000031}

The result ends up returning a width of 256.000031, not 256.

@kvark
Copy link
Member

@kvark kvark commented Oct 26, 2018

Dupe of #177 🤣

@kvark kvark added the bug label Oct 26, 2018
@mattwoodrow
Copy link
Author

@mattwoodrow mattwoodrow commented Oct 26, 2018

I tried working around it, and we just hit the exact same issue within webrender::image::tiles. Floats are a pain.

@nical
Copy link
Collaborator

@nical nical commented Oct 26, 2018

So I had a look at Gecko's rect implementation and tried to rewrite euclid's the same way:

        if !self.intersects(other) {
            return None;
        }

        let x = max(self.min_x(), other.min_x());
        let y = max(self.min_y(), other.min_y());

        let w = min(
            self.min_x() - x + self.size.width,
            other.min_x() - x + other.size.width,
        );

        let h = min(
            self.min_y() - y + self.size.height,
            other.min_y() - y + other.size.height,
        );

        Some(rect(x, y, w, h))

The idea of having the exact same precision behavior as gecko is very appealing and it does address this particular test case, but gecko's implementation appears to rely on the number representation being signed. With this implementation self.min_x() - x is either zero or negative and we use unsigned integers with euclid in many places.

@nical
Copy link
Collaborator

@nical nical commented Oct 26, 2018

Dupe of #177 rofl

Yeah that might be enough of a motivation to add the endpoint representation and move some of webrender's code over to it.

@mattwoodrow
Copy link
Author

@mattwoodrow mattwoodrow commented Oct 26, 2018

I tried the same thing!

The gecko expression is ordered weirdly, but you can write it more sanely as self.max_x() - x, and that avoids the underflow.

The effective difference is that current code is doing min() on the bottom-right points, and then converting the result to width/height. The gecko version computes the two lengths, and does min() on that.

bors-servo added a commit to servo/webrender that referenced this issue Oct 29, 2018
Fixes for tiled blobs

This switches TileOffset to u32 (since gecko reftests have cases that overflow a u16 offset), and fixes the floating point precision issues outlined in servo/euclid#306

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/webrender/3237)
<!-- Reviewable:end -->
@nical
Copy link
Collaborator

@nical nical commented Jul 23, 2020

I went back to this and tried a few permutations. Even writing self.min_x() - x + self.size.width as self.max_x() - x exhibits the precision issue. What's happening in gecko's formuation for this particular test case is that self.min_x() and x are equal so the substraction doesn't lose precision.

min_x() - x has 50% chance of not losing precision since x is either self.min_x() or other.min_x() but it also has 50% chance of underflowing. Any other ordering of operations will be more likely to reduce precision but short of adding a branch we can't do it in a way that keeps the likelihood of preserving precisions without risking underflows.

Btw we are currently converting the rect into a box and doing the intersection in the endpoints form instead of the origin+size form of when this issue filed (the test case is still failing).

In my opinion the better solution is to avoid using Rect and use Box2D anywhere this type of precision issue matters. The precesion loss is happening when converting from Rect to Box2D but if the data is stored directly as a box, we only do min/max without addition/substraction so the precision is preserved.

@nical nical closed this Jul 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants
You can’t perform that action at this time.