Closed neesjanvaneck closed 2 years ago
Can you put a breakpoint in NeonCortex.getScaleFactor and see what it returns under Java 8 and Java 10?
NeonCortex.getScaleFactor is returning the following values.
Thanks. The only Windows machine I have available has a different configuration, with a higher-density display. I'll see if I can play with the DPI settings there and reproduce something similar.
maybe caused by http://openjdk.java.net/jeps/263 ?
Please try the latest 0.9-SNAPSHOT build to see how it looks on your environment. It's still not perfect, but should be better.
Thanks for the update. It seems to be improved. Please see the screenshot below. As you can see, it is indeed not yet perfect. Do you see possibilities to make more improvements? I would be happy to test it again.
There's a rather convoluted setup that I borrowed from intellij.
https://github.com/kirill-grouchnikov/radiance/blob/master/neon/src/main/java/org/pushingpixels/neon/internal/contrib/intellij/UIUtil.java queries the desktop for the current resolution to see if it should take the high-DPI path. https://github.com/kirill-grouchnikov/radiance/blob/master/neon/src/main/java/org/pushingpixels/neon/internal/contrib/intellij/JBHiDPIScaledImage.java is the high-DPI aware image that, internally, allocates a larger image if necessary to account for the desktop scale factor (examples below). And https://github.com/kirill-grouchnikov/radiance/blob/master/neon/src/main/java/org/pushingpixels/neon/internal/contrib/intellij/HiDPIScaledGraphics.java is the pass-through graphics.
So let's say you're running on a MacBook Pro with 2x scale factor. Substance caches almost all visuals as offscreen images because otherwise the performance would be terrible. For a button which is 100x30 "Swing" pixels on the screen, Substance allocates a 200x60 pixel image (JBHiDPIScaledImage).
When the time comes to draw the outlines, those are intended to be hairline, 1-pixel stroke. However, that is the screen 1-pixel and not Swing 1-pixel. You see the difference when you create a BasicStroke with with of 1 and how it looks like on a MacBook Pro screen - it's too thick.
This is what happens. During rendering in Substance UI delegates, a BasicStroke instance is created with width of 0.5. Then, as a drawPath operation is issued to the underlying graphics, that Graphics object returned by JBHiDPIScaledImage in createGraphics is scaled by 2x in both x and y. So essentially, there's a mix of Swing pixels (sizes, gradient stops), screen pixels (stroke width) and scaled image (200x60 vs 100x30).
The stroke created by the UI delegate is 0.5, which is then scaled by 2x inside JBHiDPIScaledImage when it is rendered into that 200x60 image. And then, finally, that image is drawn onto the screen on a 100x30 rectangle, essentially scaling it by 0.5x. The end result is that the 1-pixel (Swing) stroke rendered on that image is then scaled down to be 1-pixel (screen) hairline.
This works well for scale factor of 2x (and 2.5x on my Windows laptop), but does indeed result in these glitches on 1.25x and 1.5x.
An alternative approach would be to bypass the scale-down/scale-up double pass in JBHiDPIScaledImage, but rather operate directly on the underlying big image. But that big image would still need to be rendered back to a smaller area. So in this case, a 100x30 button under 1.25x is 125x37.5 - and that's where it starts getting tricky. How does this half-a-pixel get treated? The same can happen under 1.5x for controls with odd width/height.
I don't really have a good answer for now, unfortunately.
I'm seeing a similar problem, and this is the main issue preventing me from moving away from Java 8 and older versions of Substance at this time, though I would like to upgrade both.
When using Windows 7, magification 125% (which is the default), I am seeing extra one-pixel lines around the edge of the frame, and when I use 'JFrame.setDefaultLookAndFeelDecorated(true);' I also see that "X" for the close button looks strangely curved.
Example from Radiance-Substance 2.5.1 and Java 11.0.1 on Windows 7. (You can ignore the details of this example. I'm just testing a ListCellRenderer.)
How does this demo look like under other look and feels that support hi dpi, like Synthetica or Darcula?
I have not yet tried those other LAFs with Java 9+. We like and are accustomed to Substance and don't want to change away from it unless necessary! We don't like Synthetica. Darcula is fine for a dark LAF but we also want a lighter LAF as an option.
To be honest, the image I attached does not look horrible, and may be acceptable. When I change the title bar to a dark color, though, those little gaps become more noticeable. The curvy-looking "X" for the close button is also distracting.
I need to learn how to make some of the small Skin customizations I've done in previous versions which don't work with the current API. Probably mostly just re-named classes and methods.
Had some time to look at it a bit more.
For a button that is 28x28 "units" on a 2.5x scaled screen is 70x70 underlying pixels. This whole block is considered to be the visual bounds of the button, and Substance composes the overall visuals of the button into that 70x70 block. Specifically, it is the border, the background fill, the icon and the text.
The border is computed to be aligned flush with the bounding box. That is, unlike Nimbus, Darcula and a few other third-party look-and-feels that leave outer margin around the border for focus ring and drop shadow, Substance "fills" the whole bounds with the button visuals. There's a combination of factors that leads to cropped / cutoff visuals under the fractional metrics.
The first is the underlying limitation of representing fractional numbers as Java doubles/floats. This can be seen by stepping through the code in SubstanceOutlineUtilities.getBaseOutline
. The simplest math such as 0.2f+16.6f-3.0f
gives the result of 13.800001f
. As that method computes the shape that corresponds to the outer border, these aberrations creep into the resulting GeneralPath
.
Then that path is used in ButtonBackgroundDelegate.createBackgroundImage
to paint that path to an offscreen image, and that image is painted on screen at the end of ButtonBackgroundDelegate.updateBackground
. At this point, the code is drawing the 70x70 image back to a 28x28 unit square in the underlying Java2D pipeline. The combination of "overflowing" right and bottom coordinates of the path, along with the default rendering hints makes for the observed appearance. Here, the following rendering hints make the appearance a little bit better:
graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
But it's still not what it should be in the ideal world.
Now, why is this not happening in Darcula? Darcula does two things:
This combination of a thicker border + outer margins + thick focus ring is used everywhere in Darcula - buttons, checkboxes, comboboxes, etc.
While it's certainly a viable approach, I want to be careful in taking this path going forward. Diverging the optical bounds of the controls from the real bounds (that is, leaving extra outer margins for the focus ring) means far-reaching changes to application layouts. Either the relevant controls become optically smaller (as they shrink visually), or they take more space in their containers - and everything around them needs to take that into consideration to prevent content overflow or too much space between controls.
As for the rendering hint block above, I want to first check what kind of performance hit it adds, by running the Lightbeam performance suite and see if it's worth including that in the upcoming 3.0 release.
Thank you for continuing to work on this!
Just to say, JDK's built in Windows L&F suffers exactly the same kind of artefacts fractional scale levels - e.g. missing borders from Combo boxes (etc) taken from the Windows Theming (smoke and mirrors) stuff.
(Although there are even more jarring problems - e.g. borders that flip between 1px or 2px just as you move/resize things.)
Thanks for taking the time to post updates, it helps us non-users facing similar issues! (Although we have had calls to unify on a platform-agnostic L&F and for dark theme support, so we may become a user yet!)
Christoph-GEM - please file this as a separate entry in the issue tracker
See the last part of https://www.pushing-pixels.org/2021/10/04/radiance-4-5-0-and-whats-next.html
I have only recently got a Windows machine with High DPI. My program has a bunch of 16x16 icons, and these look very tiny on that machine. (Sadly, designing resolution-independent icons is not a priority for my company.) We have found that we can get around the problem by changing one of the "compatibility settings" on the javaw.exe program itself. This can possibly also work as an imperfect solution to this issue in some cases.
I am starting to localize my program to multiple Locales. I noticed something very strange related to this issue. It was so unexpected that I thought I must be mistaken.
If I set the locale to Esperanto (eo) or Volapük (vo), the Substance LAF does not apply the 1.5 magnification that I specified in Windows display properties. (In the real world, I don't need to use these languages, but I use them to test for edge cases.) For all other languages I've tested, the expected 1.5 magnification is applied.
Metal LAF and Windows LAF do not show this Locale-dependent behavior.
Note that I'm still using Java 1.8 and an older version of Substance. This behavior may have changed with newer Radiance and Java 9.
PS: I'm the same person as "enwired" above. Just using my work account today.
Can't really comment on those old versions of Substance anymore. Too much time has passed since then.
I was not expecting you to do anything about this. That's why I put it in a comment rather than opening a new issue. It was just very surprising that changing the locale could have any effect on the fractional scaling.
I think there are some locale-related issues that are still present in the current version, so I may open "issues" for those.
Started converting a bit of code to direct rendering on the graphics context, but the off-pixel rendering issues happen there as well. Here's the simplest repro:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
abstract class Base extends JComponent {
protected float strokeWidth;
public Base() {
this.setOpaque(false);
this.setPreferredSize(new Dimension(80, 24));
this.strokeWidth = 1.0f / (float) getScaleFactor();
}
private static double getScaleFactor(GraphicsDevice device) {
GraphicsConfiguration graphicsConfig = device.getDefaultConfiguration();
AffineTransform tx = graphicsConfig.getDefaultTransform();
double scaleX = tx.getScaleX();
double scaleY = tx.getScaleY();
return Math.max(scaleX, scaleY);
}
private static double getScaleFactor() {
double result = 1.0;
GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = e.getScreenDevices();
// now get the configurations for each device
for (GraphicsDevice device : devices) {
result = Math.max(result, getScaleFactor(device));
}
return result;
}
}
class Version1 extends Base {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2d.setStroke(new BasicStroke(this.strokeWidth, BasicStroke.JOIN_ROUND, BasicStroke.CAP_BUTT));
g2d.setColor(Color.BLACK);
g2d.draw(new Rectangle2D.Float(this.strokeWidth, this.strokeWidth, this.getWidth() - 2 * this.strokeWidth,
this.getHeight() - 2 * this.strokeWidth));
for (int i = 1; i < 5; i++) {
float inset = 2 * i;
g2d.draw(new Rectangle2D.Float(this.strokeWidth + inset, this.strokeWidth + inset,
this.getWidth() - 2 * this.strokeWidth - 2 * inset,
this.getHeight() - 2 * this.strokeWidth - 2 * inset));
}
g2d.dispose();
}
}
public class Hairlines {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Hairlines");
frame.setLayout(new FlowLayout());
JComponent version1 = new Version1();
frame.add(version1);
frame.setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
});
}
}
Running on my Windows 10 laptop that has a 2.5x scale out of the box
Results in this:
The only difference between the two is starting to resize the frame horizontally by a pixel each time. Half the time the vertical lines are hairline and black, and half the time they are two pixels wide and grey.
Going to send a question on the https://mail.openjdk.java.net/mailman/listinfo/client-libs-dev mailing list to ask what is the "right" way to do hairline paths under fractional screen metrics.
As mentioned earlier in this thread, this is not an issue for other active third-party look-and-feels such as flatlaf and darklaf that use thicker borders. And core look-and-feels such as Metal and Windows do display the same issue on this monitor during resizing.
The only difference between the two is starting to resize the frame horizontally by a pixel each time. Half the time the vertical lines are hairline and black, and half the time they are two pixels wide and grey.
That's because AWT does some "rounding" when converting logical coordinates to device coordinates and depending on where the window is located on screen, the results is different. To compensate this, you probably have to take the window location into account and do some "reverse rounding" ...
But there is a easy solution: change the scaling of the java.awt.Graphics
to 1
. Then you can paint exact pixels.
I use this in FlatLaf. Here is the related FlatLaf code (feel free to copy it): https://github.com/JFormDesigner/FlatLaf/blob/58dbccec2d5264cd2ce0317e49f83c766bd562b7/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java#L33-L101
This is your example, modified to use HiDPIUtils.paintAtScale1x()
:
import com.formdev.flatlaf.util.HiDPIUtils;
class Version2 extends JComponent {
public Version2() {
this.setOpaque(false);
this.setPreferredSize(new Dimension(80, 24));
}
@Override
protected void paintComponent(Graphics g) {
HiDPIUtils.paintAtScale1x( (Graphics2D) g, this, this::paintImpl );
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
g.setColor(Color.BLACK);
g.drawRect(x, y, width - 1, height - 1);
for (int i = 1; i < 5; i++) {
int inset = 2 * i;
g.drawRect(x + inset, y + inset, width - (inset * 2) - 1, height - (inset * 2) - 1);
}
}
}
Screenshot at 150%:
Some tips for painting Swing components at HiDPI screens.
You should not store any scale factor in the component (or somewhere else). The scale factor may change anytime (in Java 9+). Either the user changes it in the Windows preferences. Or the user moves the window to secondary screen that uses different scale factor than primary screen.
Either get scale factor from the component:
double scaleFactor = comp.getGraphicsConfiguration().getDefaultTransform().getScaleX();
Or from Graphics2D
:
double scaleFactor = ((Graphics2D)g).getTransform().getScaleX();
Because line drawing methods (g.drawLine()
, g.drawRect()
, g.drawRoundRect()
, ...) expect coordinates specified
"in the middle" of the stroke thickness, and due to AWT scaling/rounding, the lines are not always drawn where you expect it.
To paint horizontal or vertical lines it is better to use g.fillRect()
.
To paint (rounded) rectangles, use paths. E.g.:
Path2D border = new Path2D.Float( Path2D.WIND_EVEN_ODD );
border.append( new Rectangle( x, y, w, h ), false );
border.append( new Rectangle( x + 1, y + 1, w - 2, h - 2 ), false );
g.fill( border );
If you always use HiDPIUtils.paintAtScale1x()
, then you probably can use line drawing methods.
Never use antialiasing when filling complete component background with:
g.fillRect(0, 0, c.getWidth(),c.getHeight());
When using antialiasing and painting at the outside edge of a component (e.g. a border or a rounded background),
then use HiDPIUtils.paintAtScale1x()
to do so.
Otherwise AWT would paint slightly outside of component bounds (due to antialiasing and scaling).
Thanks Karl. Much appreciated!
The idea is indeed to move away from relying on the scale factor for all drawing in Radiance. I'll try the code you linked to a bit later today on my Windows box to see how it looks. Pretty much all borders, outlines and separators in Radiance are 1-pixel, so hopefully this should work out and unblock this transition that I'm planning.
A question on this line, @DevCharly:
private static double normalize( double value ) {
return Math.floor( value + 0.25 ) + 0.25;
}
Where are the constants from? You reference this file but from its initialization here, here and here, looks like they're passing 0.499 as normPosition
to be used in their normalize
No sure where it is from. Maybe from classes SurfaceData
or D3DSurfaceData
, which use 0.25
.
Anyway, if I set a breakpoint at PixelToParallelogramConverter.normalize()
,
the value for normRoundingBias
is 0.25
(on Windows 11, Java 11 and 17)
Does it also depend on the default stroke control to get those crisp lines? If I change the paintComponent
to set up the stroke control like this:
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
HiDPIUtils.paintAtScale1x( g2d, this, this::paintImpl );
g2d.dispose();
}
I get this:
See the middle one getting all smoodged up? And the one on the right is with drawRoundRect
with progressively smaller radiuses.
If I only do anti-aliasing (I want to get smooth rounded corners):
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
HiDPIUtils.paintAtScale1x( g2d, this, this::paintImpl );
g2d.dispose();
}
it looks much better:
The full code for the component on the right in this last screenshot:
class Version3 extends Base {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
HiDPIUtils.paintAtScale1x( g2d, this, this::paintImpl );
g2d.dispose();
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int arcSize = 10;
g.setColor(Color.BLACK);
g.drawRoundRect(x, y, width - 1, height - 1, arcSize, arcSize);
for (int i = 1; i < 5; i++) {
int inset = 2 * i;
g.drawRoundRect(x + inset, y + inset, width - (inset * 2) - 1, height - (inset * 2) - 1,
arcSize - 2, arcSize - 2);
}
}
}
No sure where it is from. Maybe from classes
SurfaceData
orD3DSurfaceData
, which use0.25
.Anyway, if I set a breakpoint at
PixelToParallelogramConverter.normalize()
, the value fornormRoundingBias
is0.25
(on Windows 11, Java 11 and 17)
Interesting. Here they create it with 0.25
but in another place in the same class they do 0.499
.
This last version looks pretty decent on my 2.5x Windows screen
Regarding stroke control:
For line painting, AWT actually expects coordinates specified "in the middle" of the stroke thickness.
The default stroke control normalizes coordinates to paint at full pixels.
So if you do g.drawLine( 0, 0, 100, 0 )
,
then AWT actually paints a 1px thick line at 0.5, 0.5, 99.5, 0.5
If you set RenderingHints.VALUE_STROKE_PURE
, then AWT does not normalize
the coordinates and paints the line "on two pixels" if antialiasing is enable, which looks blurry.
If you use HiDPIUtils.paintAtScale1x()
it makes no sense to use pure stroke control.
In FlatLaf, I use pure stroke control only for painting chevron arrows. Otherwise (small) arrows may look asymmetric when AWT normalizes coordinates...
Hi folks, just to say there's a closely related issue in the JDK currently under review - about how to fix up TitledBorder to get its 2 lines of border to always draw 'pixel-perfectly' even under fractional scaling levels. In case you want to compare notes or share some pearls of wisdom: https://github.com/openjdk/jdk/pull/7449#issuecomment-1139069818
Thanks for the link @lukeu. After reading through comments, I found https://github.com/apache/netbeans/pull/1284/files and https://github.com/apache/netbeans/pull/1777/files to be most relevant in terms of the approach.
https://github.com/apache/netbeans/blob/master/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/DPIUnscaledBorder.java is the latest source code for the scale-aware border.
There was an interesting suggestion in that thread about very high density displays, that at some point a hairline border is just too thin in terms of its physical size, and it becomes washed out and almost invisible.
I don't think there's a lot of such displays out there at 4x+ resolution, but worth keeping it in mind. That maybe in that future, Radiance borders and separators will be 2px instead of 1px after a certain scale threshold.
FYI: my interest in HiDPI started way back in 2015 I got my first Dell XPS with 4k on a relatively small 15" screen. Hacking around the lack of support in Java 7/8 from outside the L&F was, um, "fun". (In hindsight, probably shouldn't have tried to keep supporting Swing's in-built L&Fs. Whoops.)
I don't think there's a lot of such displays out there at 4x+ resolution, but worth keeping it in mind. That maybe in that future, Radiance borders and separators will be 2px instead of 1px after a certain scale threshold.
Yeah whether to upscale a normally-1px line is probably context dependent, but I would suggest that doing so may be the safer default stance. My screen is also 250% and while 1px lines are generally visible (assuming a reasonable contrast) the majority of times I notice them (in any application) it's usually because scaling wasn't taken into account properly, and they look too fine and unbalanced.
An update on this issue. The work being done for #388 to convert all rendering in Radiance to direct rendering instead of via cached offscreen images is in a good shape. The majority of core Swing component rendering has been converted, with the remainder being arrow icons, double arrow icons and title pane icons (which are not hairline strokes in any case).
Once these icons have been converted to direct rendering, the second large chunk of work will be to convert all custom Radiance components to direct rendering (command buttons, strips, popups, panels, the entire ribbon).
It would be great if people who have filed and commented on the original visual issues of Radiance under fractional scaling take the latest 6.0-SNAPSHOT bits for a ride and let me know how it looks like. I've been looking at it on my 2.5x scale Windows 10 box, but of course it will be super helpful to have more testing on it.
Thanks for working on this. I am finally in a good place to update past java 8, so I can now start using recent builds of Radiance. So I am willing to try 6.0-SNAPSHOT. Where can I find a copy? I would prefer not to attempt to build the jars locally since I have no experience with Gradle.
I'm still seeing a few issues with fractional scaling in 5.0. I will let you know if they still appear in 6.0.
https://oss.sonatype.org/content/repositories/snapshots/org/pushing-pixels/ for the snapshot builds if you're using Maven or Gradle. If not, this document for building it locally.
All of the changes went into 6.0-SNAPSHOT, so 5.0 isn't going to be of much help / progress. Speaking of which, version 5.0 was a pure renaming / refactoring to bring everything under the Radiance umbrella.
Upgrading to 5.0 does help me because it allows me to have some bug fixes that you have made over the years since the very old version I'm using. Also, I can now file bugs about 5.0 and have some hope of them being fixed.
The re-naming in 5.0, and a few other changes, threw me for a bit of a loop. I've found how to do most things I need in the new code, but would like to ask some questions about how to do a few things.
Please open a new conversation so that this one stays focused on this particular issue
I've tried both 5.0 and 6.0-SNAPSHOT. Windows 10, 250% scaling. Corretto JRE 11.
5.0 fixes the problems I mentioned on [Nov 7, 2019]. (A one-pixel line around some borders, and a strange-looking window close button.) I found only one area in my GUI where there still seems to be a problem with an unwanted one-pixel line with random contents. (That is quite possibly a mistake with my component, not the LAF.)
5.0 will probably work fine for me.
6.0: I do not notice any difference in appearance, but there is a huge negative impact on performance.
My program uses many self-drawn graphics. (Graphs and such.) When I hide those panels, the regular Swing component panels show only a modest negative performance impact.
The current performance with my self-drawn graphics would not be acceptable. Perhaps, though, there are some rendering hints that I need to change?
PS: Regarding my comment on Oct 25, 2021 about components changing size when the locale is changed: this is because the system changes some fonts when using certain locales, and the different font metrics cause changes to some component sizes. Not really so strange after all.
6.0: I do not notice any difference in appearance, but there is a huge negative impact on performance.
My program uses many self-drawn graphics. (Graphs and such.) When I hide those panels, the regular Swing component panels show only a modest negative performance impact.
The current performance with my self-drawn graphics would not be acceptable. Perhaps, though, there are some rendering hints that I need to change?
What kind of components / drawing are we talking about?
The change in 6.0 is to switch from rendering to offscreen buffered images to direct rendering to the passed graphics content in all the UI delegates. For example, in pre-6.0 world, a button would track its state. Let's say that button starts in enabled state. Then that button would render itself to an offscreen buffered image, cache that image, and then render that image on screen. Subsequent requests to draw the same exact button in the same exact state would reuse that cached image and render it on the screen without rendering the button first to a new offscreen image. So essentially, the more you used your app, the more the different parts of it would be rendered onto dozens, hundreds, thousands of these offscreen images, and the faster the rendering would become.
In the new world, everything is rendered directly to the screen with no intermediate and cached offscreen bitmaps.
Do your components extend some core Swing component like buttons or checkboxes? Do they perhaps use them as "renderers" a-la lists, tables and trees approach in core Swing? Your code might have explicitly relied on Substance / Radiance doing that offscreen rendering and caching, which does not exist anymore.
We have a few custom Swing components. For example a JTree where the nodes have checkboxes. But such things are a minor part of the program.
We also use many customized TableCellRenderer objects which derive from Swing renderers, not Radiance renderers. But those don't seem to be causing a problem.
The slowdown comes from customized JPanel classes which draw graphs and charts. Everything inside those is drawn directly onto the Graphics object, using no Swing UI components. I have a suspicion of what the problem might be. Essentially, our graphics contain 100s of thousands of objects. Those draw themselves with more or less details depending on the current zoom level. If an object will only be a few pixels wide on the screen, we don't draw the inner details of the object. It is possible that something has changed about how the objects are determining their size on screen. Maybe they now think they are 2.5 times as large as they really are because the magnification factor on Windows is set to 250%.
I need a few days to investigate.
About performance of 6.0-SNAPSHOT: I investigated a bunch of stuff in my program. I may find some problems on my end. But right now it looks like there is something wrong in performance of the UI for JSlider. I use Sliders in multiple panels and they interact with each other (via some shared background models). Even if you have only one JSlider, connected to nothing else, it performs slowly sometimes. It often, though not always, responds slowly to being dragged, especially near the end of the range. Problem is absent in 5.0.
Again, Windows 10 at 250%, Java 11 Corretto.
Yes, thanks for finding that. I can see it in the Radiance main demo app.
After switching to some of the skins like Autumn or Nebula, the sliders are taking enormous amount of time to paint themselves. First there's this logged to console:
UNSUPPORTED (log once): POSSIBLE ISSUE: unit 1 GLD_TEXTURE_INDEX_2D is unloadable and bound to sampler type (Float) - using zero texture because texture unloadable
And then sliders that take multiple seconds to paint:
Drawing a slider took 453ms
Drawing a slider took 454ms
Drawing a slider took 1674ms
Drawing a slider took 1785ms
Drawing a slider took 1966ms
Drawing a slider took 1962ms
Drawing a slider took 2042ms
Drawing a slider took 1956ms
Drawing a slider took 6418ms
Drawing a slider took 6332ms
This will absolutely need to be addressed as it makes the whole thing unshippable
Should be addressed now with this latest commit. I'll upload to Sonatype snapshot repository later in the day, but it can be built and tested from sources.
Thanks. I will wait for the snapshot build, and use 5.0 in the meantime. But at least I know a fix will be coming.
This statement that I made above was incorrect: "My program uses many self-drawn graphics. (Graphs and such.) When I hide those panels, the regular Swing component panels show only a modest negative performance impact."
I don't understand the code you checked-in, but it looks like this problem maybe also affected checkboxes?
It's some sort of a combination of applying an affine transform on graphics context (for 1x rendering) and then also applying a custom clipping area before drawing gradients. No idea why would that go into multiple seconds, though.
Latest snapshot of 6.0 has just been published
I can verify that the fix restores the proper performance. I am seeing a separate bug. No time to investigate today, but this stack trace should help;
java.util.NoSuchElementException: null at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1000) at java.base/java.util.Collections.min(Collections.java:601) at org.pushingpixels.radiance.theming.internal.painter.SeparatorPainterUtils.paintVerticalLines(SeparatorPainterUtils.java:405) at org.pushingpixels.radiance.theming.internal.ui.RadianceSliderUI.paintTicks(RadianceSliderUI.java:587) at org.pushingpixels.radiance.theming.internal.ui.RadianceSliderUI.paint(RadianceSliderUI.java:429) at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
Thanks, should be fixed now.
I have tested the 0.9 snapshot version of Radiance Substance with high DPI and different Java versions on Windows 7 and 10. Everything seems to work perfectly with Java 8. However, with Java 9 and 10 there seems to be a problem. If a custom DPI setting of more than 100% is used it seems that the margins or insets of the controls are not adjusted in a correct way. Please see the first screenshot below of the Substance Check demo application running on Java 10 with a custom DPI setting of 150%. The same test with Java 8 seems to work fine (second screenshot).
Java 10 with a custom DPI setting of 150% (Windows; Radiance Substance 0.9):
Java 8 with a custom DPI setting of 150% (Windows; Radiance Substance 0.9):