From bfbb8875bf22bc0009ed7db9e637974e4011a225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Thu, 1 May 2025 00:55:05 -0500 Subject: [PATCH 01/25] Component should be opaque MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assuming that JOSM draws *nothing else* underneath MapView, this should be safe. Reasoning: the unchanged layers buffer looks to be the back-back buffer that JOSM overlays the actual back buffer onto before dumping all that onto MapView’s canvas. --- src/org/openstreetmap/josm/gui/MapView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 5605881848f..33fc41982ee 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -311,6 +311,7 @@ public void mousePressed(MouseEvent me) { AutoFilterManager.getInstance().enableAutoFilterRule(AutoFilterManager.PROP_AUTO_FILTER_RULE.get()); } setTransferHandler(new OsmTransferHandler()); + setOpaque(true); } /** From 06482082321cbce0a38022c1453ef569531f660b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Fri, 2 May 2025 02:11:59 -0500 Subject: [PATCH 02/25] Factor buffer null guards out of paint method Since we can create a BufferedImage without a valid GraphicsConfiguration (i.e., it is null), we can re-use the headless conditional to pass a dummy buffer during component instantiation. That conditional also short-circuits the null check. --- src/org/openstreetmap/josm/gui/MapView.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 33fc41982ee..1891b4fccd9 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -234,8 +234,8 @@ public void detachFromMapView(MapViewEvent event) { */ private final transient Set temporaryLayers = new LinkedHashSet<>(); - private transient BufferedImage nonChangedLayersBuffer; - private transient BufferedImage offscreenBuffer; + private transient BufferedImage nonChangedLayersBuffer = getAcceleratedImage(MapView.this, 1, 1); + private transient BufferedImage offscreenBuffer = getAcceleratedImage(MapView.this, 1, 1); // Layers that wasn't changed since last paint private final transient List nonChangedLayers = new ArrayList<>(); private int lastViewID; @@ -335,7 +335,7 @@ public static List getMapNavigationComponents(MapView forM } private static BufferedImage getAcceleratedImage(Component mv, int width, int height) { - if (GraphicsEnvironment.isHeadless()) { + if (GraphicsEnvironment.isHeadless() || null == mv.getGraphicsConfiguration()) { return new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); } return mv.getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.OPAQUE); @@ -558,13 +558,12 @@ private void drawMapContent(Graphics2D g) { && lastClipBounds.contains(g.getClipBounds()) && nonChangedLayers.equals(visibleLayers.subList(0, nonChangedLayers.size())); - if (null == offscreenBuffer || offscreenBuffer.getWidth() != width || offscreenBuffer.getHeight() != height) { + if (offscreenBuffer.getWidth() != width || offscreenBuffer.getHeight() != height) { offscreenBuffer = getAcceleratedImage(this, width, height); } - if (!canUseBuffer || nonChangedLayersBuffer == null) { - if (null == nonChangedLayersBuffer - || nonChangedLayersBuffer.getWidth() != width || nonChangedLayersBuffer.getHeight() != height) { + if (!canUseBuffer) { + if (nonChangedLayersBuffer.getWidth() != width || nonChangedLayersBuffer.getHeight() != height) { nonChangedLayersBuffer = getAcceleratedImage(this, width, height); } Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); From ca3185bd20729620bafd09564909a18c7ade7cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Fri, 2 May 2025 02:16:47 -0500 Subject: [PATCH 03/25] Preempt GC of child Graphics2D objects --- src/org/openstreetmap/josm/gui/MapView.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 1891b4fccd9..fe42a6c8870 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -575,6 +575,7 @@ private void drawMapContent(Graphics2D g) { for (int i = 0; i < nonChangedLayersCount; i++) { paintLayer(visibleLayers.get(i), g2); } + g2.dispose(); } else { // Maybe there were more unchanged layers then last time - draw them to buffer if (nonChangedLayers.size() != nonChangedLayersCount) { @@ -584,6 +585,7 @@ private void drawMapContent(Graphics2D g) { for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) { paintLayer(visibleLayers.get(i), g2); } + g2.dispose(); } } @@ -629,6 +631,8 @@ private void drawMapContent(Graphics2D g) { playHeadMarker.paint(tempG, this); } + tempG.dispose(); + try { g.setTransform(new AffineTransform(1, 0, 0, 1, trOrig.getTranslateX(), trOrig.getTranslateY())); g.drawImage(offscreenBuffer, 0, 0, null); From 77582d5d7b4635c8846b4f2a59ad9ba8b273a5a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 05:47:48 -0500 Subject: [PATCH 04/25] Use VolatileImage for back-buffers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit, the map renderer uses transient BufferedImages, an image type that notoriously executes drawing entirely in software loops (you may verify this behavior by running with `-Dsun.java2d.trace=count` and either `-Dsun.java2d .opengl=true` or `-Dsun.java2d.d3d=true`). Unfortunately, this means that for the last decade or so, JOSM has had no support for hardware‑accelerated rendering (see ticket #10101). Rendering performance is worsened by using said BufferedImages for bespoke double-buffering instead of Swing’s native double-buffering or BufferStrategy/VolatileImage. This chronic performance degradation is unacceptable: a mid-range tower from the mid-2010s should not struggle to render a **single layer of Bing tiles**. VolatileImages do not have this problem as acceleratable–hardware surfaces. Switching to these also obsolesces the UI-scaling affine transforms (presumably because hardware surface takes UI scaling into account? it just works, apparently 😕). --- src/org/openstreetmap/josm/gui/MapView.java | 265 ++++++++++---------- 1 file changed, 126 insertions(+), 139 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index fe42a6c8870..3a946ae1c2f 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -3,6 +3,7 @@ import static java.util.function.Predicate.not; +import java.awt.AWTException; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; @@ -10,10 +11,9 @@ import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; -import java.awt.GraphicsEnvironment; +import java.awt.ImageCapabilities; import java.awt.Point; import java.awt.Rectangle; -import java.awt.Shape; import java.awt.Stroke; import java.awt.Transparency; import java.awt.event.ComponentAdapter; @@ -22,9 +22,8 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; -import java.awt.geom.AffineTransform; import java.awt.geom.Area; -import java.awt.image.BufferedImage; +import java.awt.image.VolatileImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -234,8 +233,8 @@ public void detachFromMapView(MapViewEvent event) { */ private final transient Set temporaryLayers = new LinkedHashSet<>(); - private transient BufferedImage nonChangedLayersBuffer = getAcceleratedImage(MapView.this, 1, 1); - private transient BufferedImage offscreenBuffer = getAcceleratedImage(MapView.this, 1, 1); + private transient VolatileImage nonChangedLayersBuffer; + private transient VolatileImage offscreenBuffer; // Layers that wasn't changed since last paint private final transient List nonChangedLayers = new ArrayList<>(); private int lastViewID; @@ -334,11 +333,27 @@ public static List getMapNavigationComponents(MapView forM return Arrays.asList(zoomSlider, scaler); } - private static BufferedImage getAcceleratedImage(Component mv, int width, int height) { - if (GraphicsEnvironment.isHeadless() || null == mv.getGraphicsConfiguration()) { - return new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + private static VolatileImage getAcceleratedImage(Component mv, int width, int height) { + VolatileImage volatileImage; + // Creating a VolatileImage is impossible if 1) we’re headless or 2) our component is isolated (i.e., not in a + // container). The former is inherent to VolatileImage creation; the latter is not, so we need to check that to + // produce optimal VolatileImages. + if (null != mv.getGraphicsConfiguration()) { + // TODO: allow user to toggle between SW-backed and HW-backed? + var volatileImageCapabilities = new ImageCapabilities(true); + try { + volatileImage = mv.getGraphicsConfiguration().createCompatibleVolatileImage(width, height, volatileImageCapabilities, Transparency.OPAQUE); + } catch (AWTException e) { + // + volatileImage = mv.getGraphicsConfiguration().createCompatibleVolatileImage(width, height, Transparency.OPAQUE); + } + } else { + volatileImage = mv.createVolatileImage(width, height); + } + if (null != volatileImage) { + volatileImage.setAccelerationPriority(1); } - return mv.getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.OPAQUE); + return volatileImage; } // remebered geometry of the component @@ -516,154 +531,126 @@ public void paint(Graphics g) { } private void drawMapContent(Graphics2D g) { - // In HiDPI-mode, the Graphics g will have a transform that scales - // everything by a factor of 2.0 or so. At the same time, the value returned - // by getWidth()/getHeight will be reduced by that factor. - // - // This would work as intended, if we were to draw directly on g. But - // with a temporary buffer image, we need to move the scale transform to - // the Graphics of the buffer image and (in the end) transfer the content - // of the temporary buffer pixel by pixel onto g, without scaling. - // (Otherwise, we would upscale a small buffer image and the result would be - // blurry, with 2x2 pixel blocks.) - AffineTransform trOrig = g.getTransform(); - double uiScaleX = g.getTransform().getScaleX(); - double uiScaleY = g.getTransform().getScaleY(); - // width/height in full-resolution screen pixels - int width = (int) Math.round(getWidth() * uiScaleX); - int height = (int) Math.round(getHeight() * uiScaleY); - // This transformation corresponds to the original transformation of g, - // except for the translation part. It will be applied to the temporary - // buffer images. - AffineTransform trDef = AffineTransform.getScaleInstance(uiScaleX, uiScaleY); - // The goal is to create the temporary image at full pixel resolution, - // so scale up the clip shape - Shape scaledClip = trDef.createTransformedShape(g.getClip()); - List visibleLayers = layerManager.getVisibleLayersInZOrder(); + int nonChangedLayersCount = getNonChangedLayersCount(visibleLayers); + drawUnchangedLayers(g, visibleLayers, nonChangedLayersCount); + drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); + } + + private void drawOffscreenBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { + do { + if (null == offscreenBuffer + || offscreenBuffer.getWidth() != getWidth() + || offscreenBuffer.getHeight() != getHeight() + || VolatileImage.IMAGE_INCOMPATIBLE == offscreenBuffer.validate(getGraphicsConfiguration())) { + offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + } + Graphics2D tempG = offscreenBuffer.createGraphics(); + tempG.setClip(g.getClip()); + do { + var validationNonChangedLayersBuffer = nonChangedLayersBuffer.validate(getGraphicsConfiguration()); + if (VolatileImage.IMAGE_RESTORED == validationNonChangedLayersBuffer) { + drawUnchangedLayers(g, visibleLayers, nonChangedLayersCount); + } else if (VolatileImage.IMAGE_INCOMPATIBLE == validationNonChangedLayersBuffer) { + nonChangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + drawUnchangedLayers(g, visibleLayers, nonChangedLayersCount); + } + tempG.drawImage(nonChangedLayersBuffer, 0, 0, null); + } while (nonChangedLayersBuffer.contentsLost()); - int nonChangedLayersCount = 0; - Set invalidated = invalidatedListener.collectInvalidatedLayers(); - for (Layer l: visibleLayers) { - if (invalidated.contains(l)) { - break; - } else { - nonChangedLayersCount++; + for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) { + paintLayer(visibleLayers.get(i), tempG); + } + + try { + drawTemporaryLayers(tempG, getLatLonBounds(g.getClipBounds())); + } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { + BugReport.intercept(e).put("temporaryLayers", temporaryLayers).warn(); + } + + // draw world borders + try { + drawWorldBorders(tempG); + } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { + // getProjection() needs to be inside lambda to catch errors. + BugReport.intercept(e).put("bounds", () -> getProjection().getWorldBoundsLatLon()).warn(); + } + + MapFrame map = MainApplication.getMap(); + if (AutoFilterManager.getInstance().getCurrentAutoFilter() != null) { + AutoFilterManager.getInstance().drawOSDText(tempG); + } else if (MainApplication.isDisplayingMapView() && map.filterDialog != null) { + map.filterDialog.drawOSDText(tempG); + } + + if (playHeadMarker != null) { + playHeadMarker.paint(tempG, this); } - } + tempG.dispose(); + + var validationOffscreenBuffer = offscreenBuffer.validate(getGraphicsConfiguration()); + if (VolatileImage.IMAGE_RESTORED == validationOffscreenBuffer) { + drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); + } else if (VolatileImage.IMAGE_INCOMPATIBLE == validationOffscreenBuffer) { + offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); + } + g.drawImage(offscreenBuffer, 0, 0, null); + } while (offscreenBuffer.contentsLost()); + offscreenBuffer.flush(); + } + + private int drawUnchangedLayers(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { boolean canUseBuffer = !paintPreferencesChanged.getAndSet(false) && nonChangedLayers.size() <= nonChangedLayersCount && lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds()) && nonChangedLayers.equals(visibleLayers.subList(0, nonChangedLayers.size())); - - if (offscreenBuffer.getWidth() != width || offscreenBuffer.getHeight() != height) { - offscreenBuffer = getAcceleratedImage(this, width, height); - } - - if (!canUseBuffer) { - if (nonChangedLayersBuffer.getWidth() != width || nonChangedLayersBuffer.getHeight() != height) { - nonChangedLayersBuffer = getAcceleratedImage(this, width, height); - } - Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); - g2.setClip(scaledClip); - g2.setTransform(trDef); - g2.setColor(PaintColors.getBackgroundColor()); - g2.fillRect(0, 0, width, height); - - for (int i = 0; i < nonChangedLayersCount; i++) { - paintLayer(visibleLayers.get(i), g2); - } - g2.dispose(); - } else { - // Maybe there were more unchanged layers then last time - draw them to buffer - if (nonChangedLayers.size() != nonChangedLayersCount) { + if (!canUseBuffer || (canUseBuffer && nonChangedLayers.size() != nonChangedLayersCount)) { + do { + if (null == nonChangedLayersBuffer + || nonChangedLayersBuffer.getWidth() != getWidth() + || nonChangedLayersBuffer.getHeight() != getHeight() + || VolatileImage.IMAGE_INCOMPATIBLE == nonChangedLayersBuffer.validate(getGraphicsConfiguration())) { + + nonChangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + } Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); - g2.setClip(scaledClip); - g2.setTransform(trDef); - for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) { - paintLayer(visibleLayers.get(i), g2); + g2.setClip(g.getClip()); + if (!canUseBuffer) { + g2.setColor(PaintColors.getBackgroundColor()); + g2.fillRect(0, 0, getWidth(), getHeight()); + for (int i = 0; i < nonChangedLayersCount; i++) { + paintLayer(visibleLayers.get(i), g2); + } + } else { + // Maybe there were more unchanged layers then last time - draw them to buffer + for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) { + paintLayer(visibleLayers.get(i), g2); + } } g2.dispose(); - } + } while (nonChangedLayersBuffer.contentsLost()); } - nonChangedLayers.clear(); nonChangedLayers.addAll(visibleLayers.subList(0, nonChangedLayersCount)); lastViewID = getViewID(); lastClipBounds = g.getClipBounds(); + return nonChangedLayersCount; + } - Graphics2D tempG = offscreenBuffer.createGraphics(); - tempG.setClip(scaledClip); - tempG.setTransform(new AffineTransform()); - tempG.drawImage(nonChangedLayersBuffer, 0, 0, null); - tempG.setTransform(trDef); - - for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) { - paintLayer(visibleLayers.get(i), tempG); - } - - try { - drawTemporaryLayers(tempG, getLatLonBounds(new Rectangle( - (int) Math.round(g.getClipBounds().x * uiScaleX), - (int) Math.round(g.getClipBounds().y * uiScaleY)))); - } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { - BugReport.intercept(e).put("temporaryLayers", temporaryLayers).warn(); - } - - // draw world borders - try { - drawWorldBorders(tempG); - } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { - // getProjection() needs to be inside lambda to catch errors. - BugReport.intercept(e).put("bounds", () -> getProjection().getWorldBoundsLatLon()).warn(); - } - - MapFrame map = MainApplication.getMap(); - if (AutoFilterManager.getInstance().getCurrentAutoFilter() != null) { - AutoFilterManager.getInstance().drawOSDText(tempG); - } else if (MainApplication.isDisplayingMapView() && map.filterDialog != null) { - map.filterDialog.drawOSDText(tempG); - } - - if (playHeadMarker != null) { - playHeadMarker.paint(tempG, this); - } - - tempG.dispose(); - - try { - g.setTransform(new AffineTransform(1, 0, 0, 1, trOrig.getTranslateX(), trOrig.getTranslateY())); - g.drawImage(offscreenBuffer, 0, 0, null); - } catch (ClassCastException e) { - // See #11002 and duplicate tickets. On Linux with Java >= 8 Many users face this error here: - // - // java.lang.ClassCastException: sun.awt.image.BufImgSurfaceData cannot be cast to sun.java2d.xr.XRSurfaceData - // at sun.java2d.xr.XRPMBlitLoops.cacheToTmpSurface(XRPMBlitLoops.java:145) - // at sun.java2d.xr.XrSwToPMBlit.Blit(XRPMBlitLoops.java:353) - // at sun.java2d.pipe.DrawImage.blitSurfaceData(DrawImage.java:959) - // at sun.java2d.pipe.DrawImage.renderImageCopy(DrawImage.java:577) - // at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:67) - // at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:1014) - // at sun.java2d.pipe.ValidatePipe.copyImage(ValidatePipe.java:186) - // at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3318) - // at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3296) - // at org.openstreetmap.josm.gui.MapView.paint(MapView.java:834) - // - // It seems to be this JDK bug, but Oracle does not seem to be fixing it: - // https://bugs.openjdk.java.net/browse/JDK-7172749 - // - // According to bug reports it can happen for a variety of reasons such as: - // - long period of time - // - change of screen resolution - // - addition/removal of a secondary monitor - // - // But the application seems to work fine after, so let's just log the error - Logging.error(e); - } finally { - g.setTransform(trOrig); + private int getNonChangedLayersCount(List visibleLayers) { + int nonChangedLayersCount = 0; + Set invalidated = invalidatedListener.collectInvalidatedLayers(); + for (Layer l: visibleLayers) { + if (invalidated.contains(l)) { + break; + } else { + nonChangedLayersCount++; + } } + return nonChangedLayersCount; } private void drawTemporaryLayers(Graphics2D tempG, Bounds box) { From b35f74bf61c6df65ef7244c7143d19ce23204124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 22:04:26 -0500 Subject: [PATCH 05/25] Move code copying the back buffer out of the drawing method --- src/org/openstreetmap/josm/gui/MapView.java | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 3a946ae1c2f..5b8b1b35132 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -535,6 +535,17 @@ private void drawMapContent(Graphics2D g) { int nonChangedLayersCount = getNonChangedLayersCount(visibleLayers); drawUnchangedLayers(g, visibleLayers, nonChangedLayersCount); drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); + do { + var bufferValidation = offscreenBuffer.validate(getGraphicsConfiguration()); + if (VolatileImage.IMAGE_RESTORED == bufferValidation) { + drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); + } else if (VolatileImage.IMAGE_INCOMPATIBLE == bufferValidation) { + offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); + } + g.drawImage(offscreenBuffer, 0, 0, null); + } while (offscreenBuffer.contentsLost()); + offscreenBuffer.flush(); } private void drawOffscreenBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { @@ -588,17 +599,7 @@ private void drawOffscreenBuffer(Graphics2D g, List visibleLayers, int no } tempG.dispose(); - - var validationOffscreenBuffer = offscreenBuffer.validate(getGraphicsConfiguration()); - if (VolatileImage.IMAGE_RESTORED == validationOffscreenBuffer) { - drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); - } else if (VolatileImage.IMAGE_INCOMPATIBLE == validationOffscreenBuffer) { - offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); - drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); - } - g.drawImage(offscreenBuffer, 0, 0, null); } while (offscreenBuffer.contentsLost()); - offscreenBuffer.flush(); } private int drawUnchangedLayers(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { @@ -613,7 +614,6 @@ private int drawUnchangedLayers(Graphics2D g, List visibleLayers, int non || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight() || VolatileImage.IMAGE_INCOMPATIBLE == nonChangedLayersBuffer.validate(getGraphicsConfiguration())) { - nonChangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); } Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); From dd3161e8d345e2239891e8cb9c62b6f0d8f9eea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 22:13:59 -0500 Subject: [PATCH 06/25] Normalize variable names --- src/org/openstreetmap/josm/gui/MapView.java | 98 ++++++++++----------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 5b8b1b35132..c475a44f2e5 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -233,10 +233,10 @@ public void detachFromMapView(MapViewEvent event) { */ private final transient Set temporaryLayers = new LinkedHashSet<>(); - private transient VolatileImage nonChangedLayersBuffer; + private transient VolatileImage unchangedLayersBuffer; private transient VolatileImage offscreenBuffer; // Layers that wasn't changed since last paint - private final transient List nonChangedLayers = new ArrayList<>(); + private final transient List unchangedLayers = new ArrayList<>(); private int lastViewID; private final AtomicBoolean paintPreferencesChanged = new AtomicBoolean(true); private Rectangle lastClipBounds = new Rectangle(); @@ -532,23 +532,23 @@ public void paint(Graphics g) { private void drawMapContent(Graphics2D g) { List visibleLayers = layerManager.getVisibleLayersInZOrder(); - int nonChangedLayersCount = getNonChangedLayersCount(visibleLayers); - drawUnchangedLayers(g, visibleLayers, nonChangedLayersCount); - drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); + int unchangedLayersCount = getUnchangedLayersCount(visibleLayers); + renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); + renderOffscreenBuffer(g, visibleLayers, unchangedLayersCount); do { - var bufferValidation = offscreenBuffer.validate(getGraphicsConfiguration()); - if (VolatileImage.IMAGE_RESTORED == bufferValidation) { - drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); - } else if (VolatileImage.IMAGE_INCOMPATIBLE == bufferValidation) { + var offscreenBufferValidation = offscreenBuffer.validate(getGraphicsConfiguration()); + if (VolatileImage.IMAGE_RESTORED == offscreenBufferValidation) { + renderOffscreenBuffer(g, visibleLayers, unchangedLayersCount); + } else if (VolatileImage.IMAGE_INCOMPATIBLE == offscreenBufferValidation) { offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); - drawOffscreenBuffer(g, visibleLayers, nonChangedLayersCount); + renderOffscreenBuffer(g, visibleLayers, unchangedLayersCount); } g.drawImage(offscreenBuffer, 0, 0, null); } while (offscreenBuffer.contentsLost()); offscreenBuffer.flush(); } - private void drawOffscreenBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { + private void renderOffscreenBuffer(Graphics2D g, List visibleLayers, int unchangedLayersCount) { do { if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() @@ -556,32 +556,32 @@ private void drawOffscreenBuffer(Graphics2D g, List visibleLayers, int no || VolatileImage.IMAGE_INCOMPATIBLE == offscreenBuffer.validate(getGraphicsConfiguration())) { offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); } - Graphics2D tempG = offscreenBuffer.createGraphics(); - tempG.setClip(g.getClip()); + var g2 = offscreenBuffer.createGraphics(); + g2.setClip(g.getClip()); do { - var validationNonChangedLayersBuffer = nonChangedLayersBuffer.validate(getGraphicsConfiguration()); - if (VolatileImage.IMAGE_RESTORED == validationNonChangedLayersBuffer) { - drawUnchangedLayers(g, visibleLayers, nonChangedLayersCount); - } else if (VolatileImage.IMAGE_INCOMPATIBLE == validationNonChangedLayersBuffer) { - nonChangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); - drawUnchangedLayers(g, visibleLayers, nonChangedLayersCount); + var unchangedLayersBufferValidation = unchangedLayersBuffer.validate(getGraphicsConfiguration()); + if (VolatileImage.IMAGE_RESTORED == unchangedLayersBufferValidation) { + renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); + } else if (VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBufferValidation) { + unchangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); } - tempG.drawImage(nonChangedLayersBuffer, 0, 0, null); - } while (nonChangedLayersBuffer.contentsLost()); + g2.drawImage(unchangedLayersBuffer, 0, 0, null); + } while (unchangedLayersBuffer.contentsLost()); - for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) { - paintLayer(visibleLayers.get(i), tempG); + for (int i = unchangedLayersCount; i < visibleLayers.size(); i++) { + paintLayer(visibleLayers.get(i), g2); } try { - drawTemporaryLayers(tempG, getLatLonBounds(g.getClipBounds())); + drawTemporaryLayers(g2, getLatLonBounds(g.getClipBounds())); } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { BugReport.intercept(e).put("temporaryLayers", temporaryLayers).warn(); } // draw world borders try { - drawWorldBorders(tempG); + drawWorldBorders(g2); } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { // getProjection() needs to be inside lambda to catch errors. BugReport.intercept(e).put("bounds", () -> getProjection().getWorldBoundsLatLon()).warn(); @@ -589,34 +589,34 @@ private void drawOffscreenBuffer(Graphics2D g, List visibleLayers, int no MapFrame map = MainApplication.getMap(); if (AutoFilterManager.getInstance().getCurrentAutoFilter() != null) { - AutoFilterManager.getInstance().drawOSDText(tempG); + AutoFilterManager.getInstance().drawOSDText(g2); } else if (MainApplication.isDisplayingMapView() && map.filterDialog != null) { - map.filterDialog.drawOSDText(tempG); + map.filterDialog.drawOSDText(g2); } if (playHeadMarker != null) { - playHeadMarker.paint(tempG, this); + playHeadMarker.paint(g2, this); } - tempG.dispose(); + g2.dispose(); } while (offscreenBuffer.contentsLost()); } - private int drawUnchangedLayers(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { + private int renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { boolean canUseBuffer = !paintPreferencesChanged.getAndSet(false) - && nonChangedLayers.size() <= nonChangedLayersCount + && unchangedLayers.size() <= nonChangedLayersCount && lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds()) - && nonChangedLayers.equals(visibleLayers.subList(0, nonChangedLayers.size())); - if (!canUseBuffer || (canUseBuffer && nonChangedLayers.size() != nonChangedLayersCount)) { + && unchangedLayers.equals(visibleLayers.subList(0, unchangedLayers.size())); + if (!canUseBuffer || (canUseBuffer && unchangedLayers.size() != nonChangedLayersCount)) { do { - if (null == nonChangedLayersBuffer - || nonChangedLayersBuffer.getWidth() != getWidth() - || nonChangedLayersBuffer.getHeight() != getHeight() - || VolatileImage.IMAGE_INCOMPATIBLE == nonChangedLayersBuffer.validate(getGraphicsConfiguration())) { - nonChangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + if (null == unchangedLayersBuffer + || unchangedLayersBuffer.getWidth() != getWidth() + || unchangedLayersBuffer.getHeight() != getHeight() + || VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBuffer.validate(getGraphicsConfiguration())) { + unchangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); } - Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); + Graphics2D g2 = unchangedLayersBuffer.createGraphics(); g2.setClip(g.getClip()); if (!canUseBuffer) { g2.setColor(PaintColors.getBackgroundColor()); @@ -626,31 +626,31 @@ private int drawUnchangedLayers(Graphics2D g, List visibleLayers, int non } } else { // Maybe there were more unchanged layers then last time - draw them to buffer - for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) { + for (int i = unchangedLayers.size(); i < nonChangedLayersCount; i++) { paintLayer(visibleLayers.get(i), g2); } } g2.dispose(); - } while (nonChangedLayersBuffer.contentsLost()); + } while (unchangedLayersBuffer.contentsLost()); } - nonChangedLayers.clear(); - nonChangedLayers.addAll(visibleLayers.subList(0, nonChangedLayersCount)); + unchangedLayers.clear(); + unchangedLayers.addAll(visibleLayers.subList(0, nonChangedLayersCount)); lastViewID = getViewID(); lastClipBounds = g.getClipBounds(); return nonChangedLayersCount; } - private int getNonChangedLayersCount(List visibleLayers) { - int nonChangedLayersCount = 0; + private int getUnchangedLayersCount(List visibleLayers) { + int unchangedLayersCount = 0; Set invalidated = invalidatedListener.collectInvalidatedLayers(); for (Layer l: visibleLayers) { if (invalidated.contains(l)) { break; } else { - nonChangedLayersCount++; + unchangedLayersCount++; } } - return nonChangedLayersCount; + return unchangedLayersCount; } private void drawTemporaryLayers(Graphics2D tempG, Bounds box) { @@ -813,11 +813,11 @@ public void destroy() { if (mapMover != null) { mapMover.destroy(); } - nonChangedLayers.clear(); + unchangedLayers.clear(); synchronized (temporaryLayers) { temporaryLayers.clear(); } - nonChangedLayersBuffer = null; + unchangedLayersBuffer = null; offscreenBuffer = null; setTransferHandler(null); GuiHelper.destroyComponents(this, false); From 87469c4d99c1ea50d8842364f73da779be1179e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 22:14:25 -0500 Subject: [PATCH 07/25] Simplify conditional --- src/org/openstreetmap/josm/gui/MapView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index c475a44f2e5..c57b1c1d9b5 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -608,7 +608,7 @@ private int renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, && lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds()) && unchangedLayers.equals(visibleLayers.subList(0, unchangedLayers.size())); - if (!canUseBuffer || (canUseBuffer && unchangedLayers.size() != nonChangedLayersCount)) { + if (!canUseBuffer || unchangedLayers.size() != nonChangedLayersCount) { do { if (null == unchangedLayersBuffer || unchangedLayersBuffer.getWidth() != getWidth() From 4ab700684a64f945aae729689a55309ae2438e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 22:15:01 -0500 Subject: [PATCH 08/25] Correct method return type --- src/org/openstreetmap/josm/gui/MapView.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index c57b1c1d9b5..ce909125f9b 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -602,7 +602,7 @@ private void renderOffscreenBuffer(Graphics2D g, List visibleLayers, int } while (offscreenBuffer.contentsLost()); } - private int renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { + private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { boolean canUseBuffer = !paintPreferencesChanged.getAndSet(false) && unchangedLayers.size() <= nonChangedLayersCount && lastViewID == getViewID() @@ -637,7 +637,6 @@ private int renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, unchangedLayers.addAll(visibleLayers.subList(0, nonChangedLayersCount)); lastViewID = getViewID(); lastClipBounds = g.getClipBounds(); - return nonChangedLayersCount; } private int getUnchangedLayersCount(List visibleLayers) { From c72f36e69d2bb7423160cc17293b1eef632bb6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 22:16:36 -0500 Subject: [PATCH 09/25] Reorder proposition --- src/org/openstreetmap/josm/gui/MapView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index ce909125f9b..6ac682bc58b 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -604,9 +604,9 @@ private void renderOffscreenBuffer(Graphics2D g, List visibleLayers, int private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { boolean canUseBuffer = !paintPreferencesChanged.getAndSet(false) - && unchangedLayers.size() <= nonChangedLayersCount && lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds()) + && unchangedLayers.size() <= nonChangedLayersCount && unchangedLayers.equals(visibleLayers.subList(0, unchangedLayers.size())); if (!canUseBuffer || unchangedLayers.size() != nonChangedLayersCount) { do { From 7aab4eb1fbdcb718f523b0a054eb2a291d1a6965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 22:24:48 -0500 Subject: [PATCH 10/25] Convert indexed for loops to for-each loops over sublists --- src/org/openstreetmap/josm/gui/MapView.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 6ac682bc58b..a69f25b8d73 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -569,8 +569,8 @@ private void renderOffscreenBuffer(Graphics2D g, List visibleLayers, int g2.drawImage(unchangedLayersBuffer, 0, 0, null); } while (unchangedLayersBuffer.contentsLost()); - for (int i = unchangedLayersCount; i < visibleLayers.size(); i++) { - paintLayer(visibleLayers.get(i), g2); + for (var layer : visibleLayers.subList(unchangedLayersCount, visibleLayers.size())) { + paintLayer(layer, g2); } try { @@ -621,13 +621,13 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers if (!canUseBuffer) { g2.setColor(PaintColors.getBackgroundColor()); g2.fillRect(0, 0, getWidth(), getHeight()); - for (int i = 0; i < nonChangedLayersCount; i++) { - paintLayer(visibleLayers.get(i), g2); + for (var layer : visibleLayers.subList(0, nonChangedLayersCount)) { + paintLayer(layer, g2); } } else { // Maybe there were more unchanged layers then last time - draw them to buffer - for (int i = unchangedLayers.size(); i < nonChangedLayersCount; i++) { - paintLayer(visibleLayers.get(i), g2); + for (var layer : visibleLayers.subList(unchangedLayers.size(), nonChangedLayersCount)) { + paintLayer(layer, g2); } } g2.dispose(); From a16eb3493924b64006f6da8bb629b65d421d4cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 22:25:38 -0500 Subject: [PATCH 11/25] Use implicit type for G2D --- src/org/openstreetmap/josm/gui/MapView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index a69f25b8d73..da30721b635 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -616,7 +616,7 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers || VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBuffer.validate(getGraphicsConfiguration())) { unchangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); } - Graphics2D g2 = unchangedLayersBuffer.createGraphics(); + var g2 = unchangedLayersBuffer.createGraphics(); g2.setClip(g.getClip()); if (!canUseBuffer) { g2.setColor(PaintColors.getBackgroundColor()); From d772329e817182238a9f8356627a260b99ae21ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sat, 3 May 2025 22:29:34 -0500 Subject: [PATCH 12/25] Copyedit sentences --- src/org/openstreetmap/josm/gui/MapView.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index da30721b635..5aaa09ba2e9 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -85,12 +85,12 @@ import org.openstreetmap.josm.tools.bugreport.BugReport; /** - * This is a component used in the {@link MapFrame} for browsing the map. It use is to + * This is a component used in the {@link MapFrame} for browsing the map. It’s used to * provide the MapMode's enough capabilities to operate.

* * {@code MapView} holds meta-data about the data set currently displayed, as scale level, * center point viewed, what scrolling mode or editing mode is selected or with - * what projection the map is viewed etc..

+ * what projection the map is viewed, etc.

* * {@code MapView} is able to administrate several layers. * @@ -356,7 +356,7 @@ private static VolatileImage getAcceleratedImage(Component mv, int width, int he return volatileImage; } - // remebered geometry of the component + // Remembered geometry of the component private Dimension oldSize; private Point oldLoc; @@ -455,7 +455,7 @@ public void setVirtualNodesEnabled(boolean enabled) { /** * Checks if virtual nodes should be drawn. Default is false - * @return The virtual nodes property. + * @return The virtual node’s property. * @see Rendering#render */ public boolean isVirtualNodesEnabled() { @@ -625,7 +625,7 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers paintLayer(layer, g2); } } else { - // Maybe there were more unchanged layers then last time - draw them to buffer + // If there were more unchanged layers than last time, draw them to the buffer for (var layer : visibleLayers.subList(unchangedLayers.size(), nonChangedLayersCount)) { paintLayer(layer, g2); } From a8bff107d1668a8b68203de2af271cd7a0943cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sun, 4 May 2025 01:58:34 -0500 Subject: [PATCH 13/25] Cleanup draw methods --- src/org/openstreetmap/josm/gui/MapView.java | 55 ++++++--------------- 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 5aaa09ba2e9..6f817633d94 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -533,41 +533,17 @@ public void paint(Graphics g) { private void drawMapContent(Graphics2D g) { List visibleLayers = layerManager.getVisibleLayersInZOrder(); int unchangedLayersCount = getUnchangedLayersCount(visibleLayers); - renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); - renderOffscreenBuffer(g, visibleLayers, unchangedLayersCount); - do { - var offscreenBufferValidation = offscreenBuffer.validate(getGraphicsConfiguration()); - if (VolatileImage.IMAGE_RESTORED == offscreenBufferValidation) { - renderOffscreenBuffer(g, visibleLayers, unchangedLayersCount); - } else if (VolatileImage.IMAGE_INCOMPATIBLE == offscreenBufferValidation) { - offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); - renderOffscreenBuffer(g, visibleLayers, unchangedLayersCount); - } - g.drawImage(offscreenBuffer, 0, 0, null); - } while (offscreenBuffer.contentsLost()); - offscreenBuffer.flush(); - } - - private void renderOffscreenBuffer(Graphics2D g, List visibleLayers, int unchangedLayersCount) { do { if (null == offscreenBuffer + || VolatileImage.IMAGE_INCOMPATIBLE == offscreenBuffer.validate(getGraphicsConfiguration()) || offscreenBuffer.getWidth() != getWidth() - || offscreenBuffer.getHeight() != getHeight() - || VolatileImage.IMAGE_INCOMPATIBLE == offscreenBuffer.validate(getGraphicsConfiguration())) { + || offscreenBuffer.getHeight() != getHeight()) { offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); } var g2 = offscreenBuffer.createGraphics(); g2.setClip(g.getClip()); - do { - var unchangedLayersBufferValidation = unchangedLayersBuffer.validate(getGraphicsConfiguration()); - if (VolatileImage.IMAGE_RESTORED == unchangedLayersBufferValidation) { - renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); - } else if (VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBufferValidation) { - unchangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); - renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); - } - g2.drawImage(unchangedLayersBuffer, 0, 0, null); - } while (unchangedLayersBuffer.contentsLost()); + renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); + g2.drawImage(unchangedLayersBuffer, 0, 0, null); for (var layer : visibleLayers.subList(unchangedLayersCount, visibleLayers.size())) { paintLayer(layer, g2); @@ -599,7 +575,9 @@ private void renderOffscreenBuffer(Graphics2D g, List visibleLayers, int } g2.dispose(); - } while (offscreenBuffer.contentsLost()); + g.drawImage(offscreenBuffer, 0, 0, null); + } while (offscreenBuffer.contentsLost() || unchangedLayersBuffer.contentsLost()); + offscreenBuffer.flush(); } private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { @@ -608,14 +586,13 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers && lastClipBounds.contains(g.getClipBounds()) && unchangedLayers.size() <= nonChangedLayersCount && unchangedLayers.equals(visibleLayers.subList(0, unchangedLayers.size())); - if (!canUseBuffer || unchangedLayers.size() != nonChangedLayersCount) { - do { - if (null == unchangedLayersBuffer - || unchangedLayersBuffer.getWidth() != getWidth() - || unchangedLayersBuffer.getHeight() != getHeight() - || VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBuffer.validate(getGraphicsConfiguration())) { - unchangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); - } + do { + if (null == unchangedLayersBuffer + || VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBuffer.validate(getGraphicsConfiguration())) { + unchangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + canUseBuffer = false; + } + if (!canUseBuffer || (unchangedLayers.size() != nonChangedLayersCount)) { var g2 = unchangedLayersBuffer.createGraphics(); g2.setClip(g.getClip()); if (!canUseBuffer) { @@ -631,8 +608,8 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers } } g2.dispose(); - } while (unchangedLayersBuffer.contentsLost()); - } + } + } while (unchangedLayersBuffer.contentsLost()); unchangedLayers.clear(); unchangedLayers.addAll(visibleLayers.subList(0, nonChangedLayersCount)); lastViewID = getViewID(); From c03cbcef70151f35673a018d669ea293a8c71753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Sun, 4 May 2025 02:28:05 -0500 Subject: [PATCH 14/25] Refactor static method as object method; rename --- src/org/openstreetmap/josm/gui/MapView.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 6f817633d94..cdc6cb0a3e2 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -7,7 +7,6 @@ import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; -import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; @@ -333,22 +332,24 @@ public static List getMapNavigationComponents(MapView forM return Arrays.asList(zoomSlider, scaler); } - private static VolatileImage getAcceleratedImage(Component mv, int width, int height) { + private VolatileImage getAcceleratedBuffer() { VolatileImage volatileImage; + var width = getWidth(); + var height = getHeight(); // Creating a VolatileImage is impossible if 1) we’re headless or 2) our component is isolated (i.e., not in a // container). The former is inherent to VolatileImage creation; the latter is not, so we need to check that to // produce optimal VolatileImages. - if (null != mv.getGraphicsConfiguration()) { + if (null != getGraphicsConfiguration()) { // TODO: allow user to toggle between SW-backed and HW-backed? var volatileImageCapabilities = new ImageCapabilities(true); try { - volatileImage = mv.getGraphicsConfiguration().createCompatibleVolatileImage(width, height, volatileImageCapabilities, Transparency.OPAQUE); + volatileImage = getGraphicsConfiguration().createCompatibleVolatileImage(width, height, volatileImageCapabilities, Transparency.OPAQUE); } catch (AWTException e) { // - volatileImage = mv.getGraphicsConfiguration().createCompatibleVolatileImage(width, height, Transparency.OPAQUE); + volatileImage = getGraphicsConfiguration().createCompatibleVolatileImage(width, height, Transparency.OPAQUE); } } else { - volatileImage = mv.createVolatileImage(width, height); + volatileImage = createVolatileImage(width, height); } if (null != volatileImage) { volatileImage.setAccelerationPriority(1); @@ -538,7 +539,7 @@ private void drawMapContent(Graphics2D g) { || VolatileImage.IMAGE_INCOMPATIBLE == offscreenBuffer.validate(getGraphicsConfiguration()) || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) { - offscreenBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + offscreenBuffer = getAcceleratedBuffer(); } var g2 = offscreenBuffer.createGraphics(); g2.setClip(g.getClip()); @@ -589,7 +590,7 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers do { if (null == unchangedLayersBuffer || VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBuffer.validate(getGraphicsConfiguration())) { - unchangedLayersBuffer = getAcceleratedImage(this, getWidth(), getHeight()); + unchangedLayersBuffer = getAcceleratedBuffer(); canUseBuffer = false; } if (!canUseBuffer || (unchangedLayers.size() != nonChangedLayersCount)) { From 1e882df5b8d39232e4082ae3404c4917b884a46c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Mon, 5 May 2025 04:10:59 -0500 Subject: [PATCH 15/25] Fix performance regression when hovering over primitives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN 19176 introduced a performance regression in the OSM data layer where hovering over a primitive triggers a layer invalidation and tile cache reset, all presumably for the tiled renderer. When working with even modest datasets, this triggers superfluous repaints of the MapView. The only active renderer check is in `resetTiles` (SVN 19271)—and this fails to guard against any other execution specific to the tiled renderer. --- .../openstreetmap/josm/gui/layer/OsmDataLayer.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java index faaaf502689..610056b725d 100644 --- a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java +++ b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java @@ -1507,12 +1507,14 @@ public void highlightUpdated(HighlightUpdateEvent e) { @Override public void primitiveHovered(PrimitiveHoverEvent e) { - List primitives = new ArrayList<>(2); - primitives.add(e.getHoveredPrimitive()); - primitives.add(e.getPreviousPrimitive()); - primitives.removeIf(Objects::isNull); - resetTiles(primitives); - this.invalidate(); + if (MapRendererFactory.getInstance().isMapRendererActive(StyledTiledMapRenderer.class)) { + List primitives = new ArrayList<>(2); + primitives.add(e.getHoveredPrimitive()); + primitives.add(e.getPreviousPrimitive()); + primitives.removeIf(Objects::isNull); + resetTiles(primitives); + this.invalidate(); + } } @Override From da888825a53e262109778880f3315da28bac33e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 6 May 2025 00:23:59 -0500 Subject: [PATCH 16/25] Reduce back-buffer allocations and restrict painting when headless MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous behavior has the render code invalidate the back buffers on *all* component resizes. We can avoid this by creating this buffer exactly once during initialization with the maximum possible virtual desktop boundary. Render code now only needs to clip the buffer accordingly to component bounds. Also, paint code runs when headless (?). I’m uncertain of if this necessary; a quick look at tests indicates no, but I’ve added some extra guards where applicable. --- src/org/openstreetmap/josm/gui/MapView.java | 114 +++++++++++++------- 1 file changed, 77 insertions(+), 37 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index cdc6cb0a3e2..3c4fa9f1c0a 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -10,6 +10,9 @@ import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; import java.awt.ImageCapabilities; import java.awt.Point; import java.awt.Rectangle; @@ -99,6 +102,9 @@ public class MapView extends NavigatableComponent implements PropertyChangeListener, PreferenceChangedListener, LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { + private static final boolean IS_HEADLESS = GraphicsEnvironment.isHeadless(); + private static final Rectangle VIRTUAL_DESKTOP_BOUNDS = getVirtualDesktopBounds(); + static { MapPaintStyles.addMapPaintStylesUpdateListener(new MapPaintStylesUpdateListener() { @Override @@ -275,7 +281,14 @@ public void componentResized(ComponentEvent e) { mapMover = new MapMover(MapView.this); } }); - + addPropertyChangeListener("graphicsConfiguration", event -> { + var graphicsConfiguration = (GraphicsConfiguration) event.getNewValue(); + if (null == graphicsConfiguration) { + return; + } + offscreenBuffer = getAcceleratedBuffer(graphicsConfiguration); + unchangedLayersBuffer = getAcceleratedBuffer(graphicsConfiguration); + }); // listens to selection changes to redraw the map SelectionEventManager.getInstance().addSelectionListenerForEdt(repaintSelectionChangedListener); @@ -310,6 +323,13 @@ public void mousePressed(MouseEvent me) { } setTransferHandler(new OsmTransferHandler()); setOpaque(true); + + if (!IS_HEADLESS) { + var defaultScreenDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + var defaultGraphicsConfiguration = defaultScreenDevice.getDefaultConfiguration(); + offscreenBuffer = getAcceleratedBuffer(defaultGraphicsConfiguration); + unchangedLayersBuffer = getAcceleratedBuffer(defaultGraphicsConfiguration); + } } /** @@ -332,29 +352,50 @@ public static List getMapNavigationComponents(MapView forM return Arrays.asList(zoomSlider, scaler); } - private VolatileImage getAcceleratedBuffer() { - VolatileImage volatileImage; - var width = getWidth(); - var height = getHeight(); - // Creating a VolatileImage is impossible if 1) we’re headless or 2) our component is isolated (i.e., not in a - // container). The former is inherent to VolatileImage creation; the latter is not, so we need to check that to - // produce optimal VolatileImages. - if (null != getGraphicsConfiguration()) { - // TODO: allow user to toggle between SW-backed and HW-backed? - var volatileImageCapabilities = new ImageCapabilities(true); - try { - volatileImage = getGraphicsConfiguration().createCompatibleVolatileImage(width, height, volatileImageCapabilities, Transparency.OPAQUE); - } catch (AWTException e) { - // - volatileImage = getGraphicsConfiguration().createCompatibleVolatileImage(width, height, Transparency.OPAQUE); - } - } else { - volatileImage = createVolatileImage(width, height); + private static Rectangle getVirtualDesktopBounds() { + var localGraphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + var localScreenDevices = localGraphicsEnvironment.getScreenDevices(); + var virtualDesktopBounds = new Rectangle(); + for (var screenDevice : localScreenDevices) { + var gcBounds = getHiDPIDeviceBounds(screenDevice); + virtualDesktopBounds = virtualDesktopBounds.union(gcBounds); + } + virtualDesktopBounds.setLocation(0,0); + return virtualDesktopBounds; + } + + private static Rectangle getHiDPIDeviceBounds(GraphicsDevice screenDevice) { + var defaultGraphicsConfiguration = screenDevice.getDefaultConfiguration(); + // bounds for a DPI-aware GC return bounds with correctly scaled dimensions but unscaled screen-space + // coordinates, for whatever reason + // in fact, *everything* that returns screen-space coordinates does so without scaling 😭 + var defaultTransform = defaultGraphicsConfiguration.getDefaultTransform(); + var scaleX = defaultTransform.getScaleX(); + var scaleY = defaultTransform.getScaleY(); + var gcBounds = defaultGraphicsConfiguration.getBounds(); + gcBounds.setLocation((int) Math.round(gcBounds.x / scaleX), (int) Math.round(gcBounds.y / scaleY)); + return gcBounds; + } + + private VolatileImage getAcceleratedBuffer(GraphicsConfiguration graphicsConfiguration) { + // hardware-accelerated pipelines typically initialize on at least the default device, so we use that and hope + // for the best 😭 + var width = VIRTUAL_DESKTOP_BOUNDS.width; + var height = VIRTUAL_DESKTOP_BOUNDS.height; + var bufferCapabilities = new ImageCapabilities(true); + VolatileImage buffer; + try { + buffer = graphicsConfiguration.createCompatibleVolatileImage(width, height, bufferCapabilities, Transparency.OPAQUE); + } catch (AWTException e) { + buffer = graphicsConfiguration.createCompatibleVolatileImage(width, height, Transparency.OPAQUE); } - if (null != volatileImage) { - volatileImage.setAccelerationPriority(1); + var isBufferAccelerated = buffer.getCapabilities().isAccelerated(); + if (isBufferAccelerated) { + buffer.setAccelerationPriority(1); + } else { + Logging.warn("HW acceleration unavailable on screen device {0}", graphicsConfiguration.getDevice()); } - return volatileImage; + return buffer; } // Remembered geometry of the component @@ -522,11 +563,13 @@ public void paint(Graphics g) { return; } - try { - drawMapContent((Graphics2D) g); - } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { - throw BugReport.intercept(e).put("visibleLayers", layerManager::getVisibleLayersInZOrder) - .put("temporaryLayers", temporaryLayers); + if (!IS_HEADLESS) { + try { + drawMapContent((Graphics2D) g); + } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { + throw BugReport.intercept(e).put("visibleLayers", layerManager::getVisibleLayersInZOrder) + .put("temporaryLayers", temporaryLayers); + } } super.paint(g); } @@ -535,14 +578,12 @@ private void drawMapContent(Graphics2D g) { List visibleLayers = layerManager.getVisibleLayersInZOrder(); int unchangedLayersCount = getUnchangedLayersCount(visibleLayers); do { - if (null == offscreenBuffer - || VolatileImage.IMAGE_INCOMPATIBLE == offscreenBuffer.validate(getGraphicsConfiguration()) - || offscreenBuffer.getWidth() != getWidth() - || offscreenBuffer.getHeight() != getHeight()) { - offscreenBuffer = getAcceleratedBuffer(); + if (VolatileImage.IMAGE_INCOMPATIBLE == offscreenBuffer.validate(getGraphicsConfiguration())) { + offscreenBuffer = getAcceleratedBuffer(getGraphicsConfiguration()); } var g2 = offscreenBuffer.createGraphics(); - g2.setClip(g.getClip()); + // TODO: clip to content visible in screen-space *only* + g2.setClip(getBounds()); renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); g2.drawImage(unchangedLayersBuffer, 0, 0, null); @@ -588,14 +629,13 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers && unchangedLayers.size() <= nonChangedLayersCount && unchangedLayers.equals(visibleLayers.subList(0, unchangedLayers.size())); do { - if (null == unchangedLayersBuffer - || VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBuffer.validate(getGraphicsConfiguration())) { - unchangedLayersBuffer = getAcceleratedBuffer(); + if (VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBuffer.validate(getGraphicsConfiguration())) { + unchangedLayersBuffer = getAcceleratedBuffer(getGraphicsConfiguration()); canUseBuffer = false; } if (!canUseBuffer || (unchangedLayers.size() != nonChangedLayersCount)) { var g2 = unchangedLayersBuffer.createGraphics(); - g2.setClip(g.getClip()); + g2.setClip(getBounds()); if (!canUseBuffer) { g2.setColor(PaintColors.getBackgroundColor()); g2.fillRect(0, 0, getWidth(), getHeight()); From 745bc6e220367e546cc97e05dd10778edce2ab81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 6 May 2025 02:34:07 -0500 Subject: [PATCH 17/25] Resolve PMD warnings, normalize parameter name, and improve logging message --- src/org/openstreetmap/josm/gui/MapView.java | 72 +++++++++++---------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 3c4fa9f1c0a..26491ffde4a 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -24,6 +24,7 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; +import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.image.VolatileImage; import java.beans.PropertyChangeEvent; @@ -282,12 +283,12 @@ public void componentResized(ComponentEvent e) { } }); addPropertyChangeListener("graphicsConfiguration", event -> { - var graphicsConfiguration = (GraphicsConfiguration) event.getNewValue(); - if (null == graphicsConfiguration) { + GraphicsConfiguration gc = (GraphicsConfiguration) event.getNewValue(); + if (null == gc) { return; } - offscreenBuffer = getAcceleratedBuffer(graphicsConfiguration); - unchangedLayersBuffer = getAcceleratedBuffer(graphicsConfiguration); + offscreenBuffer = getAcceleratedBuffer(gc); + unchangedLayersBuffer = getAcceleratedBuffer(gc); }); // listens to selection changes to redraw the map SelectionEventManager.getInstance().addSelectionListenerForEdt(repaintSelectionChangedListener); @@ -325,10 +326,10 @@ public void mousePressed(MouseEvent me) { setOpaque(true); if (!IS_HEADLESS) { - var defaultScreenDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - var defaultGraphicsConfiguration = defaultScreenDevice.getDefaultConfiguration(); - offscreenBuffer = getAcceleratedBuffer(defaultGraphicsConfiguration); - unchangedLayersBuffer = getAcceleratedBuffer(defaultGraphicsConfiguration); + GraphicsDevice defaultSD = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + GraphicsConfiguration defaultGC = defaultSD.getDefaultConfiguration(); + offscreenBuffer = getAcceleratedBuffer(defaultGC); + unchangedLayersBuffer = getAcceleratedBuffer(defaultGC); } } @@ -353,26 +354,27 @@ public static List getMapNavigationComponents(MapView forM } private static Rectangle getVirtualDesktopBounds() { - var localGraphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); - var localScreenDevices = localGraphicsEnvironment.getScreenDevices(); - var virtualDesktopBounds = new Rectangle(); - for (var screenDevice : localScreenDevices) { - var gcBounds = getHiDPIDeviceBounds(screenDevice); - virtualDesktopBounds = virtualDesktopBounds.union(gcBounds); + GraphicsEnvironment localGE = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] localSDs = localGE.getScreenDevices(); + Rectangle virtualDesktopBounds = new Rectangle(); + for (GraphicsDevice screenDevice : localSDs) { + Rectangle sdBounds = getHiDPIDeviceBounds(screenDevice); + virtualDesktopBounds = virtualDesktopBounds.union(sdBounds); + break; } virtualDesktopBounds.setLocation(0,0); return virtualDesktopBounds; } private static Rectangle getHiDPIDeviceBounds(GraphicsDevice screenDevice) { - var defaultGraphicsConfiguration = screenDevice.getDefaultConfiguration(); + GraphicsConfiguration defaultGC = screenDevice.getDefaultConfiguration(); // bounds for a DPI-aware GC return bounds with correctly scaled dimensions but unscaled screen-space // coordinates, for whatever reason // in fact, *everything* that returns screen-space coordinates does so without scaling 😭 - var defaultTransform = defaultGraphicsConfiguration.getDefaultTransform(); - var scaleX = defaultTransform.getScaleX(); - var scaleY = defaultTransform.getScaleY(); - var gcBounds = defaultGraphicsConfiguration.getBounds(); + AffineTransform defaultTransform = defaultGC.getDefaultTransform(); + double scaleX = defaultTransform.getScaleX(); + double scaleY = defaultTransform.getScaleY(); + Rectangle gcBounds = defaultGC.getBounds(); gcBounds.setLocation((int) Math.round(gcBounds.x / scaleX), (int) Math.round(gcBounds.y / scaleY)); return gcBounds; } @@ -380,20 +382,22 @@ private static Rectangle getHiDPIDeviceBounds(GraphicsDevice screenDevice) { private VolatileImage getAcceleratedBuffer(GraphicsConfiguration graphicsConfiguration) { // hardware-accelerated pipelines typically initialize on at least the default device, so we use that and hope // for the best 😭 - var width = VIRTUAL_DESKTOP_BOUNDS.width; - var height = VIRTUAL_DESKTOP_BOUNDS.height; - var bufferCapabilities = new ImageCapabilities(true); + int width = VIRTUAL_DESKTOP_BOUNDS.width; + int height = VIRTUAL_DESKTOP_BOUNDS.height; + ImageCapabilities bufferCaps = new ImageCapabilities(true); VolatileImage buffer; try { - buffer = graphicsConfiguration.createCompatibleVolatileImage(width, height, bufferCapabilities, Transparency.OPAQUE); + buffer = graphicsConfiguration.createCompatibleVolatileImage(width, height, bufferCaps, + Transparency.OPAQUE); } catch (AWTException e) { buffer = graphicsConfiguration.createCompatibleVolatileImage(width, height, Transparency.OPAQUE); } - var isBufferAccelerated = buffer.getCapabilities().isAccelerated(); + boolean isBufferAccelerated = buffer.getCapabilities().isAccelerated(); if (isBufferAccelerated) { buffer.setAccelerationPriority(1); } else { - Logging.warn("HW acceleration unavailable on screen device {0}", graphicsConfiguration.getDevice()); + Logging.warn("HW acceleration unavailable on screen device {0}", + graphicsConfiguration.getDevice().getIDstring()); } return buffer; } @@ -581,13 +585,13 @@ private void drawMapContent(Graphics2D g) { if (VolatileImage.IMAGE_INCOMPATIBLE == offscreenBuffer.validate(getGraphicsConfiguration())) { offscreenBuffer = getAcceleratedBuffer(getGraphicsConfiguration()); } - var g2 = offscreenBuffer.createGraphics(); + Graphics2D g2 = offscreenBuffer.createGraphics(); // TODO: clip to content visible in screen-space *only* g2.setClip(getBounds()); renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); g2.drawImage(unchangedLayersBuffer, 0, 0, null); - for (var layer : visibleLayers.subList(unchangedLayersCount, visibleLayers.size())) { + for (Layer layer : visibleLayers.subList(unchangedLayersCount, visibleLayers.size())) { paintLayer(layer, g2); } @@ -622,29 +626,29 @@ private void drawMapContent(Graphics2D g) { offscreenBuffer.flush(); } - private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, int nonChangedLayersCount) { + private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers, int unchangedLayersCount) { boolean canUseBuffer = !paintPreferencesChanged.getAndSet(false) && lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds()) - && unchangedLayers.size() <= nonChangedLayersCount + && unchangedLayers.size() <= unchangedLayersCount && unchangedLayers.equals(visibleLayers.subList(0, unchangedLayers.size())); do { if (VolatileImage.IMAGE_INCOMPATIBLE == unchangedLayersBuffer.validate(getGraphicsConfiguration())) { unchangedLayersBuffer = getAcceleratedBuffer(getGraphicsConfiguration()); canUseBuffer = false; } - if (!canUseBuffer || (unchangedLayers.size() != nonChangedLayersCount)) { - var g2 = unchangedLayersBuffer.createGraphics(); + if (!canUseBuffer || (unchangedLayers.size() != unchangedLayersCount)) { + Graphics2D g2 = unchangedLayersBuffer.createGraphics(); g2.setClip(getBounds()); if (!canUseBuffer) { g2.setColor(PaintColors.getBackgroundColor()); g2.fillRect(0, 0, getWidth(), getHeight()); - for (var layer : visibleLayers.subList(0, nonChangedLayersCount)) { + for (Layer layer : visibleLayers.subList(0, unchangedLayersCount)) { paintLayer(layer, g2); } } else { // If there were more unchanged layers than last time, draw them to the buffer - for (var layer : visibleLayers.subList(unchangedLayers.size(), nonChangedLayersCount)) { + for (Layer layer : visibleLayers.subList(unchangedLayers.size(), unchangedLayersCount)) { paintLayer(layer, g2); } } @@ -652,7 +656,7 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers } } while (unchangedLayersBuffer.contentsLost()); unchangedLayers.clear(); - unchangedLayers.addAll(visibleLayers.subList(0, nonChangedLayersCount)); + unchangedLayers.addAll(visibleLayers.subList(0, unchangedLayersCount)); lastViewID = getViewID(); lastClipBounds = g.getClipBounds(); } From bff53441d122adc2146907972f24eb06cdcdb1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 6 May 2025 02:34:37 -0500 Subject: [PATCH 18/25] Downgrade logging message from `WARNING` to `INFO` --- src/org/openstreetmap/josm/gui/MapView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 26491ffde4a..eafa0226ed0 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -396,7 +396,7 @@ private VolatileImage getAcceleratedBuffer(GraphicsConfiguration graphicsConfigu if (isBufferAccelerated) { buffer.setAccelerationPriority(1); } else { - Logging.warn("HW acceleration unavailable on screen device {0}", + Logging.info("HW acceleration unavailable on screen device {0}", graphicsConfiguration.getDevice().getIDstring()); } return buffer; From e2da62ce182895c608ad480bdf7731bf84725647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 6 May 2025 21:35:41 -0500 Subject: [PATCH 19/25] Recalculate virtual desktop bounds on `GraphicsConfiguration` invalidation --- src/org/openstreetmap/josm/gui/MapView.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index eafa0226ed0..029a3168466 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -104,7 +104,7 @@ public class MapView extends NavigatableComponent LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { private static final boolean IS_HEADLESS = GraphicsEnvironment.isHeadless(); - private static final Rectangle VIRTUAL_DESKTOP_BOUNDS = getVirtualDesktopBounds(); + private static Rectangle virtualDesktopBounds = getVirtualDesktopBounds(); static { MapPaintStyles.addMapPaintStylesUpdateListener(new MapPaintStylesUpdateListener() { @@ -287,6 +287,10 @@ public void componentResized(ComponentEvent e) { if (null == gc) { return; } + Rectangle currentVirtualDesktopBounds = getVirtualDesktopBounds(); + if (virtualDesktopBounds.equals(currentVirtualDesktopBounds)) { + return; + } offscreenBuffer = getAcceleratedBuffer(gc); unchangedLayersBuffer = getAcceleratedBuffer(gc); }); @@ -382,8 +386,8 @@ private static Rectangle getHiDPIDeviceBounds(GraphicsDevice screenDevice) { private VolatileImage getAcceleratedBuffer(GraphicsConfiguration graphicsConfiguration) { // hardware-accelerated pipelines typically initialize on at least the default device, so we use that and hope // for the best 😭 - int width = VIRTUAL_DESKTOP_BOUNDS.width; - int height = VIRTUAL_DESKTOP_BOUNDS.height; + int width = virtualDesktopBounds.width; + int height = virtualDesktopBounds.height; ImageCapabilities bufferCaps = new ImageCapabilities(true); VolatileImage buffer; try { From 05f13d81109570183aa89ae2e6d56046ac830eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 6 May 2025 21:36:41 -0500 Subject: [PATCH 20/25] Remove debugging break --- src/org/openstreetmap/josm/gui/MapView.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 029a3168466..67d668d11ec 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -364,7 +364,6 @@ private static Rectangle getVirtualDesktopBounds() { for (GraphicsDevice screenDevice : localSDs) { Rectangle sdBounds = getHiDPIDeviceBounds(screenDevice); virtualDesktopBounds = virtualDesktopBounds.union(sdBounds); - break; } virtualDesktopBounds.setLocation(0,0); return virtualDesktopBounds; From f7dde010d628f074f06266d9ff2fc11cf6b8de31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 6 May 2025 21:47:41 -0500 Subject: [PATCH 21/25] Clip map based on visible rectangle rather than parent-relative bounds --- src/org/openstreetmap/josm/gui/MapView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 67d668d11ec..854536b99d4 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -590,7 +590,7 @@ private void drawMapContent(Graphics2D g) { } Graphics2D g2 = offscreenBuffer.createGraphics(); // TODO: clip to content visible in screen-space *only* - g2.setClip(getBounds()); + g2.setClip(getVisibleRect()); renderUnchangedLayersBuffer(g, visibleLayers, unchangedLayersCount); g2.drawImage(unchangedLayersBuffer, 0, 0, null); @@ -642,7 +642,7 @@ private void renderUnchangedLayersBuffer(Graphics2D g, List visibleLayers } if (!canUseBuffer || (unchangedLayers.size() != unchangedLayersCount)) { Graphics2D g2 = unchangedLayersBuffer.createGraphics(); - g2.setClip(getBounds()); + g2.setClip(getVisibleRect()); if (!canUseBuffer) { g2.setColor(PaintColors.getBackgroundColor()); g2.fillRect(0, 0, getWidth(), getHeight()); From a28f5b13dad9c392b1dd61021dd33052e21e8c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 6 May 2025 22:53:44 -0500 Subject: [PATCH 22/25] fixup! Recalculate virtual desktop bounds on `GraphicsConfiguration` invalidation --- src/org/openstreetmap/josm/gui/MapView.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 854536b99d4..a2cddbb02b4 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -104,7 +104,7 @@ public class MapView extends NavigatableComponent LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { private static final boolean IS_HEADLESS = GraphicsEnvironment.isHeadless(); - private static Rectangle virtualDesktopBounds = getVirtualDesktopBounds(); + private static final Rectangle VIRTUAL_DESKTOP_BOUNDS = getVirtualDesktopBounds(); static { MapPaintStyles.addMapPaintStylesUpdateListener(new MapPaintStylesUpdateListener() { @@ -288,9 +288,10 @@ public void componentResized(ComponentEvent e) { return; } Rectangle currentVirtualDesktopBounds = getVirtualDesktopBounds(); - if (virtualDesktopBounds.equals(currentVirtualDesktopBounds)) { + if (VIRTUAL_DESKTOP_BOUNDS.equals(currentVirtualDesktopBounds)) { return; } + VIRTUAL_DESKTOP_BOUNDS.setRect(currentVirtualDesktopBounds); offscreenBuffer = getAcceleratedBuffer(gc); unchangedLayersBuffer = getAcceleratedBuffer(gc); }); @@ -385,8 +386,8 @@ private static Rectangle getHiDPIDeviceBounds(GraphicsDevice screenDevice) { private VolatileImage getAcceleratedBuffer(GraphicsConfiguration graphicsConfiguration) { // hardware-accelerated pipelines typically initialize on at least the default device, so we use that and hope // for the best 😭 - int width = virtualDesktopBounds.width; - int height = virtualDesktopBounds.height; + int width = VIRTUAL_DESKTOP_BOUNDS.width; + int height = VIRTUAL_DESKTOP_BOUNDS.height; ImageCapabilities bufferCaps = new ImageCapabilities(true); VolatileImage buffer; try { From abd24e0ad28ef6b3a87ae7b48d6df3a7dd4d11a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Mon, 12 May 2025 23:53:40 -0500 Subject: [PATCH 23/25] Resolve checkstyle warning --- src/org/openstreetmap/josm/gui/MapView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index a2cddbb02b4..1cf11154a53 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -366,7 +366,7 @@ private static Rectangle getVirtualDesktopBounds() { Rectangle sdBounds = getHiDPIDeviceBounds(screenDevice); virtualDesktopBounds = virtualDesktopBounds.union(sdBounds); } - virtualDesktopBounds.setLocation(0,0); + virtualDesktopBounds.setLocation(0, 0); return virtualDesktopBounds; } From 48f92858b997bb5fa9454290a7676f3db9785584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 13 May 2025 00:20:14 -0500 Subject: [PATCH 24/25] Convert unintuitive static constant to static field --- src/org/openstreetmap/josm/gui/MapView.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 1cf11154a53..86044694a84 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -104,7 +104,7 @@ public class MapView extends NavigatableComponent LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { private static final boolean IS_HEADLESS = GraphicsEnvironment.isHeadless(); - private static final Rectangle VIRTUAL_DESKTOP_BOUNDS = getVirtualDesktopBounds(); + private static Rectangle virtualDesktopBounds = getVirtualDesktopBounds(); static { MapPaintStyles.addMapPaintStylesUpdateListener(new MapPaintStylesUpdateListener() { @@ -288,10 +288,10 @@ public void componentResized(ComponentEvent e) { return; } Rectangle currentVirtualDesktopBounds = getVirtualDesktopBounds(); - if (VIRTUAL_DESKTOP_BOUNDS.equals(currentVirtualDesktopBounds)) { + if (virtualDesktopBounds.equals(currentVirtualDesktopBounds)) { return; } - VIRTUAL_DESKTOP_BOUNDS.setRect(currentVirtualDesktopBounds); + virtualDesktopBounds = currentVirtualDesktopBounds; offscreenBuffer = getAcceleratedBuffer(gc); unchangedLayersBuffer = getAcceleratedBuffer(gc); }); @@ -386,8 +386,8 @@ private static Rectangle getHiDPIDeviceBounds(GraphicsDevice screenDevice) { private VolatileImage getAcceleratedBuffer(GraphicsConfiguration graphicsConfiguration) { // hardware-accelerated pipelines typically initialize on at least the default device, so we use that and hope // for the best 😭 - int width = VIRTUAL_DESKTOP_BOUNDS.width; - int height = VIRTUAL_DESKTOP_BOUNDS.height; + int width = virtualDesktopBounds.width; + int height = virtualDesktopBounds.height; ImageCapabilities bufferCaps = new ImageCapabilities(true); VolatileImage buffer; try { From 7064a6ff5a70b8f26b62fa4e80db9fb56d626e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B5=90=E8=A1=A3?= <16128011+hfmkwi@users.noreply.github.com> Date: Tue, 13 May 2025 00:24:03 -0500 Subject: [PATCH 25/25] Remove unintentional guard against recreation of VI buffers These buffers must be recreated on every GC change regardless of the state of the virtual desktop. --- src/org/openstreetmap/josm/gui/MapView.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index 86044694a84..aab166f869d 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -288,10 +288,9 @@ public void componentResized(ComponentEvent e) { return; } Rectangle currentVirtualDesktopBounds = getVirtualDesktopBounds(); - if (virtualDesktopBounds.equals(currentVirtualDesktopBounds)) { - return; + if (!virtualDesktopBounds.equals(currentVirtualDesktopBounds)) { + virtualDesktopBounds = currentVirtualDesktopBounds; } - virtualDesktopBounds = currentVirtualDesktopBounds; offscreenBuffer = getAcceleratedBuffer(gc); unchangedLayersBuffer = getAcceleratedBuffer(gc); });