Working on something un-related I noticed that PrismTextLayout was getting cache misses even
when it was invoked twice in a row with the same conditions (text, font, alignment ...)
The calls came from methods in javafx.controls Utils class which were in turn called from
methods calculating the preferred and minimum width of a label
< at javafx.controls@20-internal/javafx.scene.control.skin.LabeledSkinBase.computePrefWidth(LabeledSkinBase.java:329)
< at javafx.controls@20-internal/javafx.scene.control.Control.computePrefWidth(Control.java:571)
< at javafx.graphics@20-internal/javafx.scene.Parent.prefWidth(Parent.java:1020)
< at javafx.graphics@20-internal/javafx.scene.layout.Region.prefWidth(Region.java:1589)
---
> at javafx.controls@20-internal/javafx.scene.control.skin.LabeledSkinBase.computeMinLabeledPartWidth(LabeledSkinBase.java:795)
> at javafx.controls@20-internal/javafx.scene.control.skin.LabeledSkinBase.computeMinWidth(LabeledSkinBase.java:306)
> at javafx.controls@20-internal/javafx.scene.control.Control.computeMinWidth(Control.java:504)
> at javafx.graphics@20-internal/javafx.scene.Parent.minWidth(Parent.java:1048)
> at javafx.graphics@20-internal/javafx.scene.layout.Region.minWidth(Region.java:1553)
It seems to be because PrismTextLayout has this test ..
private boolean copyCache() {
int align = flags & ALIGN_MASK;
int boundsType = flags & BOUNDS_MASK;
/* Caching for boundsType == Center, bias towards Modena */
return wrapWidth != 0 || align != ALIGN_LEFT || boundsType == 0 || isMirrored();
}
and if it return true it doesn't cache.
It returns true because nothing in the Urils code set the boundsType bit.
You can see the comment that it wants to cache for Center as used by Modena (supposedly).
There's then a calculation of the width of "..." then it goes back to the original string to get the height.
This is all very inefficient on the part of LabeledSkin, you'd think it could ask for this ONCE and then
cache it itself. PrismTextLayout can only do so much guessing ..
The interesting thing about Utils.computeTextHeight() is that it does :-
if (boundsType == TextBoundsType.LOGICAL_VERTICAL_CENTER) {
layout.setBoundsType(TextLayout.BOUNDS_CENTER);
} else {
layout.setBoundsType(0);
}
So now bounds type is set ! And nothing ever unsets it !
So now, and only now, will later calls - including back to computeTextWidth() get cache hits.
The bounds bit only affects the vertical bounds (as Utils apparently knows) so it ignores
it when getting width .. but this means if the code just called getBounds() and then cached
that it would probably not have what it wanted for height if it hasn't set that bit yet.
It seems to me that setting it for width anyway would be better for caching and hence performance.
This requires updating the method signature and code that calls it.
I also might want to question the assumption in PrismTextLayout about refusing to cache
the non center case but I don't have data on it, but if the caller only sets it on the 3rd run
through we do lose performance.
Also disruptive to performance is that the getting the width of the ellipsis is actually the 3rd call
so you'd only use the cache once, blow it with the ellipsis and only later get it filled and re-used
To be fair, I then see a number of cache hits .. but the test app only has one Label .. adding a 2nd
then calls for each Label get intermingled so throwing away cache hits has more of an effect.
So
1) Always set the vertical bounds type - which *might* in turn mean that PrismTextLayout
can stop refusing to cache non-centered bounds, but if Modena always sets that, it matters less.
2) LabeledSkin could consider doing some higher level caching of the bounds or re-order
the calls so asking for ellipsis bounds doesn't blow the cache.
when it was invoked twice in a row with the same conditions (text, font, alignment ...)
The calls came from methods in javafx.controls Utils class which were in turn called from
methods calculating the preferred and minimum width of a label
< at javafx.controls@20-internal/javafx.scene.control.skin.LabeledSkinBase.computePrefWidth(LabeledSkinBase.java:329)
< at javafx.controls@20-internal/javafx.scene.control.Control.computePrefWidth(Control.java:571)
< at javafx.graphics@20-internal/javafx.scene.Parent.prefWidth(Parent.java:1020)
< at javafx.graphics@20-internal/javafx.scene.layout.Region.prefWidth(Region.java:1589)
---
> at javafx.controls@20-internal/javafx.scene.control.skin.LabeledSkinBase.computeMinLabeledPartWidth(LabeledSkinBase.java:795)
> at javafx.controls@20-internal/javafx.scene.control.skin.LabeledSkinBase.computeMinWidth(LabeledSkinBase.java:306)
> at javafx.controls@20-internal/javafx.scene.control.Control.computeMinWidth(Control.java:504)
> at javafx.graphics@20-internal/javafx.scene.Parent.minWidth(Parent.java:1048)
> at javafx.graphics@20-internal/javafx.scene.layout.Region.minWidth(Region.java:1553)
It seems to be because PrismTextLayout has this test ..
private boolean copyCache() {
int align = flags & ALIGN_MASK;
int boundsType = flags & BOUNDS_MASK;
/* Caching for boundsType == Center, bias towards Modena */
return wrapWidth != 0 || align != ALIGN_LEFT || boundsType == 0 || isMirrored();
}
and if it return true it doesn't cache.
It returns true because nothing in the Urils code set the boundsType bit.
You can see the comment that it wants to cache for Center as used by Modena (supposedly).
There's then a calculation of the width of "..." then it goes back to the original string to get the height.
This is all very inefficient on the part of LabeledSkin, you'd think it could ask for this ONCE and then
cache it itself. PrismTextLayout can only do so much guessing ..
The interesting thing about Utils.computeTextHeight() is that it does :-
if (boundsType == TextBoundsType.LOGICAL_VERTICAL_CENTER) {
layout.setBoundsType(TextLayout.BOUNDS_CENTER);
} else {
layout.setBoundsType(0);
}
So now bounds type is set ! And nothing ever unsets it !
So now, and only now, will later calls - including back to computeTextWidth() get cache hits.
The bounds bit only affects the vertical bounds (as Utils apparently knows) so it ignores
it when getting width .. but this means if the code just called getBounds() and then cached
that it would probably not have what it wanted for height if it hasn't set that bit yet.
It seems to me that setting it for width anyway would be better for caching and hence performance.
This requires updating the method signature and code that calls it.
I also might want to question the assumption in PrismTextLayout about refusing to cache
the non center case but I don't have data on it, but if the caller only sets it on the 3rd run
through we do lose performance.
Also disruptive to performance is that the getting the width of the ellipsis is actually the 3rd call
so you'd only use the cache once, blow it with the ellipsis and only later get it filled and re-used
To be fair, I then see a number of cache hits .. but the test app only has one Label .. adding a 2nd
then calls for each Label get intermingled so throwing away cache hits has more of an effect.
So
1) Always set the vertical bounds type - which *might* in turn mean that PrismTextLayout
can stop refusing to cache non-centered bounds, but if Modena always sets that, it matters less.
2) LabeledSkin could consider doing some higher level caching of the bounds or re-order
the calls so asking for ellipsis bounds doesn't blow the cache.