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

Reply via email to