From d47b2254e1dce98d31cfe12b90c8d75627ae01f0 Mon Sep 17 00:00:00 2001 From: "Klare, Heiko" Date: Thu, 21 May 2026 08:50:47 +0200 Subject: [PATCH 1/2] [Win32] Copy patterns in GC only when necessary GC operations for background/foreground Pattern currently copy the passed pattern upon creation. In case the GC is short living and the passed Pattern outlives it, this copy (including a handle allocation) is unnecessary. With this change, the passed Pattern is only copied/recreated when it became disposed. Contributes to https://github.com/eclipse-platform/eclipse.platform.swt/issues/3296 --- .../win32/org/eclipse/swt/graphics/GC.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java index 1f4fbb1a9b..a23bd697ba 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java @@ -4901,16 +4901,31 @@ public void setBackgroundPattern (Pattern pattern) { storeAndApplyOperationForExistingHandle(new SetBackgroundPatternOperation(pattern)); } -private class SetBackgroundPatternOperation extends Operation { - private final Pattern pattern; +private abstract class PatternOperation extends Operation { + private Pattern pattern; + PatternOperation(Pattern pattern) { + this.pattern = pattern; + } + + protected Pattern getPattern() { + if (pattern != null && pattern.isDisposed()) { + // recreate locally managed pattern if the original was disposed + pattern = pattern.copy(); + registerForDisposal(pattern); + } + return pattern; + } +} + +private class SetBackgroundPatternOperation extends PatternOperation { SetBackgroundPatternOperation(Pattern pattern) { - this.pattern = pattern == null ? null : pattern.copy(); - registerForDisposal(this.pattern); + super(pattern); } @Override void apply() { + Pattern pattern = getPattern(); if (data.gdipGraphics == 0 && pattern == null) return; initGdip(); if (data.backgroundPattern == pattern) return; @@ -5249,16 +5264,15 @@ public void setForegroundPattern (Pattern pattern) { storeAndApplyOperationForExistingHandle(new SetForegroundPatternOperation(pattern)); } -private class SetForegroundPatternOperation extends Operation { - private final Pattern pattern; +private class SetForegroundPatternOperation extends PatternOperation { SetForegroundPatternOperation(Pattern pattern) { - this.pattern = pattern == null ? null : pattern.copy(); - registerForDisposal(this.pattern); + super(pattern); } @Override void apply() { + Pattern pattern = getPattern(); if (data.gdipGraphics == 0 && pattern == null) return; initGdip(); if (data.foregroundPattern == pattern) return; From 9b5c11a586d4fb8810a4493d0229038e396a89e7 Mon Sep 17 00:00:00 2001 From: "Klare, Heiko" Date: Thu, 21 May 2026 13:43:45 +0200 Subject: [PATCH 2/2] [Win32] Avoid unnecessary creation of GC operations The storage of executed operations in a GC has been added to support the situation that a Drawable is created with a GC without knowing the final zoom in which the Drawable is supposed to be used. A common example is the creation of a (buffer) image, which is later to be drawn on a control, but at the time of drawing the image that zoom is not known. The operations allow to recreate the image for that required zoom. In most use cases of a GC, however, there is no need and no possibility to recreate the underlying drawable from a GC. In particular, when drawing to a control, the zoom of that control is known while drawing such that no storage of GC operations is necessary. The storage of GC operations can become rather expensive, in particular if the drawing of images is involved and copies of those images need to be created upon their disposal. For that reason, this change reduces the cases in which operations are actually stored to those where they are really required for recreation of the underlying drawable. Fixes https://github.com/eclipse-platform/eclipse.platform.swt/issues/3296 --- .../win32/org/eclipse/swt/graphics/GC.java | 13 ++++++++++--- .../win32/org/eclipse/swt/graphics/GCData.java | 1 + .../win32/org/eclipse/swt/graphics/Image.java | 3 ++- .../junit/Test_org_eclipse_swt_graphics_GC.java | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java index a23bd697ba..b8acce453e 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java @@ -6004,8 +6004,11 @@ Point textExtentInPixels(String string, int flags) { return new Point(rect.right, rect.bottom); } -void refreshFor(Drawable drawable, ImageHandle imageHandle) { +void reapplyTo(Drawable drawable, ImageHandle imageHandle) { if (drawable == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (!data.reapplicable) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } destroy(); GCData newData = new GCData(); originalData.copyTo(newData); @@ -6111,9 +6114,13 @@ int getZoom() { } private void storeAndApplyOperationForExistingHandle(Operation operation) { - removePreviousOperationIfSupercededBy(operation); - operations.add(operation); operation.apply(); + if (data.reapplicable) { + removePreviousOperationIfSupercededBy(operation); + operations.add(operation); + } else { + operation.disposeAll(); + } } private void removePreviousOperationIfSupercededBy(Operation operation) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GCData.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GCData.java index a8ccae520a..d6aa237167 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GCData.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GCData.java @@ -49,6 +49,7 @@ public final class GCData { public int alpha = 0xFF; public int nativeZoom; int imageZoom; + boolean reapplicable; public Image image; public PAINTSTRUCT ps; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java index 76ccd1c7b9..fe0c3a3f31 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java @@ -2333,6 +2333,7 @@ public Collection getPreservedZoomLevels() { @Override protected long configureGCData(GCData data) { + data.reapplicable = true; return configureGC(data, new ZoomContext(DPIUtil.getDeviceZoom(), DPIUtil.getNativeDeviceZoom())); } @@ -2374,7 +2375,7 @@ protected DestroyableImageHandle newImageHandle(ZoomContext zoomContext) { GC currentGC = memGC; memGC = null; DestroyableImageHandle imageHandle = createHandle(targetZoom); - currentGC.refreshFor(new DrawableWrapper(Image.this, zoomContext), imageHandle); + currentGC.reapplyTo(new DrawableWrapper(Image.this, zoomContext), imageHandle); return imageHandle; } diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java index cafb4f9146..2806c1d865 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java @@ -487,8 +487,8 @@ public void test_drawImageLorg_eclipse_swt_graphics_ImageIIII_ImageDataAtSizePro Exception e = assertThrows(IllegalArgumentException.class, () -> gc.drawImage(image, 0, 0, 1, 1)); assertSWTProblem("Incorrect exception thrown for provider == null", SWT.ERROR_INVALID_ARGUMENT, e); } finally { - image.dispose(); drawToImage.dispose(); + image.dispose(); } }