w3c / css-houdini-drafts

Mirror of https://hg.css-houdini.org/drafts
https://drafts.css-houdini.org/
Other
1.84k stars 141 forks source link

[css-layout-api] Precision and rounding of the css-layout-api. #830

Open bfgeek opened 5 years ago

bfgeek commented 5 years ago

Currently in the specification no rounding of inputs/outputs occurs.

For example:

const fragment = await child.layoutNextFragment({
  availableInlineSize: 10.13,
});

The internal representation will be:

1/64 representation 1/60 representation 1/100 representation
10.125 10.133... 10.13

If the above child is a block "auto" sized. Then the fragment.inlineSize will be:

1/64 representation 1/60 representation 1/100 representation
10.125 10.133331298828125 10.133331298828125 (i think?)

These quirks are exposed to web developers today: https://www.software.hixie.ch/utilities/js/live-dom-viewer/?saved=6316

Precision is typically kept when going from internal -> JS 52bit float -> internal due to the relatively high floating precision of the JS representation.

css-meeting-bot commented 5 years ago

The Houdini Task Force just discussed Layout precision.

The full IRC log of that discussion <TabAtkins> Topic: Layout precision
<heycam> github: https://github.com/w3c/css-houdini-drafts/issues/817
<TabAtkins> github: https://github.com/w3c/css-houdini-drafts/issues/830
<heycam> iank_: I wanted to write down the problem clearly
<heycam> ... as specced now, with availableInlineSize: 10.13, the internal representations in each engine will be different
<heycam> ... I believe Blink/WebKit will be 10.125, Gecko will be 10.133, Edge will be 10.13
<heycam> ... similarly, say that child is auto sized, the value that you will get out in JS will be 10.125 for Blink/WebKit, some ugly values near 10.33333 for the others
<heycam> iank_: this is exposed right now
<heycam> ... if you set width:10.13px on an element, and you call getClientRects, you will get similar issues
<dbaron> Some of the ugliness is something that we could fix (by going directly from fixed-point to double rather than fixed-point to float to double)
<heycam> ... part of me thinks this is a non-issue
<heycam> iank_: the thing I worry about with rounding is that you'll get potential tearing
<glazou> LEAVEROU !!!
<heycam> ... imagine if you're doing a flexbox-like thing
<heycam> ... where you're distributing available space between children
<heycam> ... with the current API you won't get any subpixel tear, but if you round inputs and outputs you will
<heycam> ... you'll have to define the whole layout tree working on rounded precision mode
<heycam> eae: when you say rounding, you mean rounding to ints or to these values?
<heycam> iank_: if you round to int values, going in and out of the engine, you might see tearing
<heycam> ... if you're distributing some fractional space in a flexbox-like thing you can't do that
<heycam> ... also you'd need to specify the whole subtree works in rounded int precision
<heycam> smfr: what do you mean by tearing?
<heycam> iank_: if you try to position the two fragments side by side, you'd get a gap
<heycam> ... similarly, the second class there is if you happen to be distribtuing some space that wouldn't get a gap, the element might be smaller
<heycam> bz: to be clear, the container being smaller than the elements is a problem anyway
<heycam> ... even with floats
<heycam> iank_: the conversion to the js float thing, it's still possible to get tears accidentally
<heycam> ... but because the JS repr is high enough, converting from the internal fractional to JS to the internal fractional will result in the same fractional value
<heycam> chrishtr: what about pixel snapping?
<heycam> iank_: we only do that at paint
<heycam> ... we don't pixel snap any of the layout
<heycam> TabAtkins: yes we do
<heycam> ... border widths e.g.
<heycam> eae: borders are very special
<heycam> emilio: device pixels
<heycam> dbaron: there's a small class of things where you care about the with of the thing being the same across repeated occurrences
<heycam> ... borders, column rules, probably line heights as well
<heycam> ... but for most other things we don't do internal snapping
<heycam> ... you round to internal repr then snap edges to the nearest pixels
<heycam> iank_: the pixel snapping borders we can repr that value in the LayoutEdges object
<heycam> bz: presumably this is snapped to dev pixels here
<heycam> iank_: yes
<heycam> chrishtr: Blink currently snaps to CSS px
<heycam> eae: not really
<heycam> chrishtr: in cases where the device pixel ratio is incorporated into zoom it's device pixels
<heycam> ... long term intention is to use device pixels
<heycam> smfr: someone mentioned pixel snapping in border would happen at layout time? isn't purely a paint time operation?
<heycam> dbaron: for us it's a computed value time thing
<heycam> Rossen: for us it's also style
<heycam> ... it matters for lower values, 1, 2, 3, px
<heycam> ... but over 13, 14px it doesn't really matter
<heycam> dbaron: but it does matter for not getting the 1px gaps that you do the snapping before the layout calcs based on it
<heycam> ... if you do layout calcs based on a non pixel snapped border, then snap it later, you'll sometimes get 1px gaps or overlaps
<heycam> Rossen: so we'll do the layout with fractional border sizes
<heycam> ... then snap during paint
<heycam> dbaron: but you need to snap all the things next to that border during paint too or they won't line up
<heycam> Rossen: no they won't
<heycam> dbaron: then your system is more complex then ours
<dbaron> s/more complex then ours/different in more complex ways from ours/
<heycam> chrishtr: is the intention to spec the rounding?
<heycam> iank_: no
<heycam> chrishtr: or just things that UAs may do?
<heycam> iank_: my preference is to allow the UA to do internal rounding to the fractional repr and back
<heycam> ... main thing I want to avoid is the 1px tearing in alyouts
<heycam> chrishtr: it should at least be said if the left/right edge of things are the same number, there should not be a tear
<heycam> eae: can probably find a way to spec that
<heycam> chrishtr: you'd have to say the rounding and layout is the same, and that pixel snapping at paint time preserves that consistency
<heycam> iank_: yep
<heycam> Rossen: does that work for you Simon?
<heycam> smfr: yes I think so
<heycam> ... WebKit does some snapping of border widths as well
<heycam> ... I'm trying to understand when you're laying out fragments iteratively, does the browser tell you how much space is left? a floating point input
<heycam> ... with custom layout, call layoutnextfragment 3 times
<heycam> ... each time an input is the remaining space?
<heycam> iank_: no you need to do that yourself
<heycam> ... so you'll have some available size, layout first child, subtract the size of it in the JS double
<heycam> smfr: was concerned about the conversions between jS doubles and the quantized values internally
<heycam> iank_: since it's handled by script it's always the JS doubles
<heycam> smfr: trying to think of cases where the rounding woul hurt you
<heycam> ... maybe comvining custom paint and layout
<heycam> ... you ask for a certain with with double precision
<heycam> ... then in paint ...
<heycam> iank_: I think previously you've brought up trying to fit into the one fractional unit, and some engines will leave a tiny fractional unit over
<heycam> ... in one you go into the next line, in another you'll have a skinny box for some reason
<heycam> ... I think it's a super edge case
<heycam> fremy: my impression is that the only case where this could be an issue is where you do the data sharing between custom layout
<heycam> ... you could share with full precision, and on the other side of the API you'd get it
<heycam> ... but I also don't expect this to be a problem
<TabAtkins> Found that the internal->JS->internal conversion is always accurate up to (10^15)px; at 10^16 there's a few rounding errors with Microsoft's precision.
<TabAtkins> http://software.hixie.ch/utilities/js/live-dom-viewer/saved/6318
<heycam> smfr: as a data point, this same problem also happens in media timing values
<heycam> ... currently they're all floating point, but there've been discussions about turning them into a rational type
<heycam> ... they're trying to solve some similar way here
<heycam> iank_: I think there's been talk in JS previously about having value types
<heycam> TabAtkins: they're not ready yet
<heycam> iank_: they've been vapourware for a while
<heycam> ... but the intention would be to work in that fractional type
<heycam> ... we shouldn't depend on that
<heycam> ... could imaging a future where JS does get that fractional type, see if it's compatible to change to that
<heycam> ... or otherwise add an option to opt in to it
<heycam> fremy: this transformation is a rounding
<heycam> iank_: it's a rounding to the fracctional repr
<heycam> fremy: can we floor rather thatn round?
<heycam> ... otherwise if you divide by 7, all your 7 column objects, you give them avail size / 7
<heycam> ... then you have a layout where each gets the size they can. if each is rounded up, the sum will be bigger than 100% and you'll wrap
<heycam> ... better to floor
<heycam> ... you make sure the sum will all fit
<heycam> TabAtkins: flooring starts loosing this internal -> JS double -> internal relationship more quickly
<heycam> eae: you can have a situation where you have a lot of space at the end
<heycam> ... which is also not desirable
<heycam> fremy: people writing the layout will pay attention to this
<heycam> smfr: has anyone tried using the prototpye to see if it's a problem?
<heycam> fremy: the prototype is only in one browser
<heycam> smfr: still possible to make layouts with hairline gaps
<heycam> fremy: I've not tried but I guess it's possible
<heycam> iank_: we can see if people run into it
<heycam> ... so far surmar and fremy haven't run into it
<heycam> smfr: your masonry layout wasn't trying to fit something into something with a border, not obvious there's a gap
<heycam> iank_: we can experiment with prototypes with borders to see if we can get hairline gaps
<heycam> fremy: grid has the opportunity to ahve these options, but you can position things at particular points, no wrapping
<heycam> ... even with 7 column grids, you place them before sizing, so it doesn't matter
<heycam> ... we should try the flexbox-like thing
<heycam> iank_: I think the action item on us is to play with our impl to see if we can break
<heycam> ... but sounds like people are relatively fine with the float repr and rounding to fractional units internally
<heycam> ... we can report back to the group
<TabAtkins> <br dur=1hr>
<heycam> github: end topic