On Tue, 18 Aug 2026 20:55:33 GMT, Michael Strauß <[email protected]> wrote:
>> modules/javafx.graphics/src/main/java/javafx/scene/layout/package-info.java >> line 428: >> >>> 426: * pieces of content are two allocations, while two >>> intermediate terms describing one piece of content are >>> 427: * one allocation. >>> 428: * <li><b>Re-snap after calculations, using the meaning of the >>> result.</b><br> >> >> I think we may need to check if this actually helps (it sometimes definitely >> will), even though I've saying this as well there is still a problem: >> >> The `snapSize` operation first applies its own floating point calculation >> (multiplying by render scale which can already introduce a tiny error) >> before calling `Math.ceil`. So even a correctly snapped end result (say >> 0.66...667) multiplied by the renderscale 1.5 can become 1.00...001 which is >> then ceil'd to 2. >> >> This happens because the result of a child's `compute`, even if snapped, >> gets used by the parent that treats it as content (using `snapSizeX/Y` >> ceiling). >> >> This is why I'm now of the opinion that we should subtract a constant value >> before ceiling, so the operation becomes: >> >> ceil((v * renderScale) - epsilon) / renderScale >> >> Where the epsilon is set to 1 millionth of a logical pixel (1e-6). >> >> The reasoning to use 1 millionth is: >> - too large a value may become noticable (ie. 1/10th of a pixel may >> introduce slight blurriness) >> - too small a value may not absorb floating point errors that have been >> multiplied (a spacing * number of children), are using values of fairly high >> magnitudes (a 100000 pixel screen or group of screens) or are using a fairly >> high renderscale >> - 1 millionth of a pixel is still unobservable and it is fair to say that a >> value within 1 millionth of a pixel can be considered to be *that* pixel >> >> Why not `ulp`? >> - It doesn't help correct the result when the calculations have accumulated >> more than 1 floating point error >> - It works poorly when the value to snap value is `Double.MAX_VALUE` (or >> something equally large) which is used through-out FX (an `ulp` at that >> magnitude is like 1e292 pixels) -- `ceiling` a `Double.MAX_VALUE` should >> yield `Double.MAX_VALUE` not `Double.MAX_VALUE - 1e292`). >> >> So resnapping may only be needed after significant number of calculations >> have been done with values that were snapped originally; tiny floating point >> errors should be absorbed by the parent's snapping (after `ceil` has been >> fixed) or by the rendering hardware (usually only accurate up to `float` >> precision). > > I've thought about this quite a bit, and here's how I understand it. > > 1. When we re-snap after calculations, we only use > `snapPosition`/`snapSpace`, never `snapSize`. > > The proposed text already says that. The reason for this is that the terms > of the calculation were already snapped to begin with, so the _idealized_ > result is guaranteed to be pixel-aligned. This is a critical piece of > information that we, as the authors of the calculation, now: that any > deviation from the pixel grid is not genuine, it's merely an artifact of > floating-point arithmetic. We know for a fact that we can safely discard it, > and `snapSpace` is guaranteed to give us the correctly snapped result as long > as float drift does not exceed 0.5 pixels (which is a safe assumption). > > 2. You point out that the parent can use this snapped size, and then use it > in its own calculations by `snapSize`-ing it again. > > However, isn't idempotency in this situation what the current `ScaledMath` > implementation already guarantees? Granted, it only explicitly guarantees > (for pixel-aligned values less than 10^15): > ``` > ceil(ceil(value, scale), scale) == ceil(value, scale) > ``` > It doesn't explicitly guarantee cross-operation idempotency like in our > scenario. But since the values returned from `ScaledMath` functions are all > pixel-aligned, I think the guarantee can be extended across operations. > > If this is true, then your example (0.666... with scale 1.5) won't ever > ceil to 2 for any realistic render scale. > > 3. Fundamentally, when we apply such a correction in `ScaledMath`, we've lost > important information: whether the tiny excess is genuine, or whether it is a > result of arithmetic drift. Only the caller of the scaled rounding methods > knows this. The difference between the two is not meaningless, in the end it > is the difference between a pixel added or removed. And that is, in turn, the > difference between a layout staying as it is, or reflowing to the next line. > > It is true that your idea improves perturbation stability of sloppily-snapped > calculations, but I think that it doesn't improve the result if the snapping > was done correctly to begin with (that is, discard any arithmetic excess if > you know that your calculation was done on pixel-aligned values). > the terms of the calculation were already snapped to begin with, so the > idealized result is guaranteed to be pixel-aligned Can we emphasize this please? ------------- PR Review Comment: https://git.openjdk.org/jfx/pull/2260#discussion_r3808059426
