On Sun, 16 Aug 2026 03:58:29 GMT, John Hendrikx <[email protected]> wrote:
>> Michael Strauß has updated the pull request incrementally with one >> additional commit since the last revision: >> >> reframe section > > 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). ------------- PR Review Comment: https://git.openjdk.org/jfx/pull/2260#discussion_r3807855819
