-
Notifications
You must be signed in to change notification settings - Fork 203
[Win32] Avoid redundant handle creation in GC.drawImage() by tracking effective zoom #3429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -201,28 +201,39 @@ public String toString() { | |
| } | ||
|
|
||
| private class HandleAtSize { | ||
| record TemporaryHandleForZoom(DestroyableImageHandle handle, int zoom) {} | ||
|
|
||
| private InternalImageHandle handleContainer = null; | ||
| private DestroyableImageHandle temporaryHandleContainer = null; | ||
| private TemporaryHandleForZoom temporaryHandleContainer = null; | ||
| private int requestedWidth = -1; | ||
| private int requestedHeight = -1; | ||
|
|
||
| public void destroy() { | ||
| if (temporaryHandleContainer != null) { | ||
| temporaryHandleContainer.destroy(); | ||
| temporaryHandleContainer = null; | ||
| TemporaryHandleForZoom previousHandle = reset(); | ||
| if (previousHandle != null) { | ||
| previousHandle.handle().destroy(); | ||
| } | ||
| } | ||
|
|
||
| private TemporaryHandleForZoom reset() { | ||
| TemporaryHandleForZoom previousHandle = temporaryHandleContainer; | ||
| temporaryHandleContainer = null; | ||
| handleContainer = null; | ||
| requestedWidth = -1; | ||
| requestedHeight = -1; | ||
| return previousHandle; | ||
| } | ||
|
|
||
| public ImageHandle refresh(int width, int height) { | ||
| if (!isReusable(width, height)) { | ||
| destroy(); | ||
| TemporaryHandleForZoom previousHandle = reset(); | ||
| requestedWidth = width; | ||
| requestedHeight = height; | ||
| handleContainer = createHandleAtExactSize(width, height) | ||
| .orElseGet(() -> getOrCreateImageHandleAtClosestSize(width, height)); | ||
| .orElseGet(() -> getOrCreateImageHandleAtClosestSize(width, height, previousHandle)); | ||
| if (previousHandle != null && previousHandle.handle() != handleContainer) { | ||
| previousHandle.handle().destroy(); | ||
| } | ||
| } | ||
| return handleContainer; | ||
| } | ||
|
|
@@ -238,23 +249,32 @@ private boolean isReusable(int width, int height) { | |
| private Optional<InternalImageHandle> createHandleAtExactSize(int width, int height) { | ||
| Optional<ImageData> imageData = imageProvider.loadImageDataAtExactSize(width, height); | ||
| if (imageData.isPresent()) { | ||
| temporaryHandleContainer = init(imageData.get(), -1); | ||
| return Optional.of(temporaryHandleContainer); | ||
| temporaryHandleContainer = new TemporaryHandleForZoom(init(imageData.get(), -1), 0); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't a handle at exact size have zoom 100?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This zoom is only used for identifying if a temporary handle can be reused when requesting it for a specific zoom. But an image that can be created at exact size will be precisely created for every requested size, such that there won't be any reuse based on the zoom. So in practice, it does not really matter what we put here. If there was a scenario where this is not the case (an image provider that gives you an image at exact size for a specific set of width/height combinations and only a fixed-size or zoom-based versions for the rest), you would need to know the exact size at 100% to identify if you should reuse the currently created handle for a specific width/height combination when requesting the image at 100%. |
||
| return Optional.of(temporaryHandleContainer.handle()); | ||
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| private InternalImageHandle getOrCreateImageHandleAtClosestSize(int widthHint, int heightHint) { | ||
| private InternalImageHandle getOrCreateImageHandleAtClosestSize(int widthHint, int heightHint, TemporaryHandleForZoom previousHandle) { | ||
| Rectangle bounds = getBounds(100); | ||
| int imageZoomForWidth = 100 * widthHint / bounds.width; | ||
| int imageZoomForHeight = 100 * heightHint / bounds.height; | ||
| int imageZoom = DPIUtil.getZoomForAutoscaleProperty(Math.max(imageZoomForWidth, imageZoomForHeight)); | ||
| InternalImageHandle bestFittingHandle = imageHandleManager.get(imageZoom); | ||
| if (bestFittingHandle == null) { | ||
| ImageData bestFittingImageData = imageProvider.loadImageData(imageZoom).element(); | ||
| bestFittingHandle = temporaryHandleContainer = init(bestFittingImageData, -1); | ||
| int nearestAvailableZoom = imageProvider.nearestAvailableZoom(imageZoom); | ||
| InternalImageHandle bestFittingHandle = imageHandleManager.get(nearestAvailableZoom); | ||
| if (bestFittingHandle != null) { | ||
| return bestFittingHandle; | ||
| } | ||
| if (nearestAvailableZoom == 100) { | ||
| return getHandleInternal(100, 100); | ||
| } | ||
| return bestFittingHandle; | ||
| if (previousHandle != null && previousHandle.zoom() == nearestAvailableZoom) { | ||
| temporaryHandleContainer = previousHandle; | ||
| return previousHandle.handle(); | ||
| } | ||
| ElementAtZoom<ImageData> imageData = imageProvider.loadImageData(imageZoom); | ||
| temporaryHandleContainer = new TemporaryHandleForZoom(init(imageData.element(), -1), imageData.zoom()); | ||
| return temporaryHandleContainer.handle(); | ||
| } | ||
|
|
||
| } | ||
|
|
@@ -957,6 +977,10 @@ public static long win32_getHandle (Image image, int zoom) { | |
| } | ||
|
|
||
| ImageHandle getHandle (int targetZoom, int nativeZoom) { | ||
| return getHandleInternal(targetZoom, nativeZoom); | ||
| } | ||
|
|
||
| InternalImageHandle getHandleInternal (int targetZoom, int nativeZoom) { | ||
| if (isDisposed()) { | ||
| return null; | ||
| } | ||
|
|
@@ -2090,6 +2114,8 @@ protected boolean isPersistentImageHandleRequriedForImageData() { | |
| return false; | ||
| } | ||
|
|
||
| abstract int nearestAvailableZoom(int zoom); | ||
|
|
||
| /** | ||
| * Returns image data at the best-fitting available zoom for the given zoom. | ||
| * The returned data will have a potential gray/disable style applied. | ||
|
|
@@ -2102,7 +2128,7 @@ protected boolean isPersistentImageHandleRequriedForImageData() { | |
|
|
||
| ElementAtZoom<ImageData> getClosestAvailableImageData(int zoom) { | ||
| TreeSet<Integer> availableZooms = new TreeSet<>(imageHandleManager.getAllZooms()); | ||
| int closestZoom = Optional.ofNullable(availableZooms.higher(zoom)).orElse(availableZooms.lower(zoom)); | ||
| int closestZoom = availableZooms.contains(zoom) ? zoom : Optional.ofNullable(availableZooms.higher(zoom)).orElse(availableZooms.lower(zoom)); | ||
| ImageData imageData = imageHandleManager.get(closestZoom).getImageData(); | ||
| return new ElementAtZoom<>(imageData, closestZoom); | ||
| } | ||
|
|
@@ -2179,6 +2205,11 @@ protected DestroyableImageHandle newImageHandle(ZoomContext zoomContext) { | |
| ImageData resizedData = newImageData (zoomContext.targetZoom()); | ||
| return newImageHandle(resizedData, zoomContext); | ||
| } | ||
|
|
||
| @Override | ||
| int nearestAvailableZoom(int zoom) { | ||
| return zoomForHandle; | ||
| } | ||
| } | ||
|
|
||
| private abstract class ImageFromImageDataProviderWrapper extends AbstractImageProviderWrapper { | ||
|
|
@@ -2249,6 +2280,11 @@ protected ElementAtZoom<ImageData> loadImageData(int zoom) { | |
| AbstractImageProviderWrapper createCopy(Image image) { | ||
| return image.new PlainImageDataProviderWrapper(this.imageDataAtBaseZoom); | ||
| } | ||
|
|
||
| @Override | ||
| int nearestAvailableZoom(int zoom) { | ||
| return baseZoom; | ||
| } | ||
| } | ||
|
|
||
| private class MaskedImageDataProviderWrapper extends ImageFromImageDataProviderWrapper { | ||
|
|
@@ -2281,6 +2317,11 @@ protected ElementAtZoom<ImageData> loadImageData(int zoom) { | |
| AbstractImageProviderWrapper createCopy(Image image) { | ||
| return image.new MaskedImageDataProviderWrapper(this.srcAt100, this.maskAt100); | ||
| } | ||
|
|
||
| @Override | ||
| int nearestAvailableZoom(int zoom) { | ||
| return 100; | ||
| } | ||
| } | ||
|
|
||
| private class ImageDataLoaderStreamProviderWrapper extends ImageFromImageDataProviderWrapper { | ||
|
|
@@ -2325,6 +2366,14 @@ protected Optional<ImageData> loadImageDataAtExactSize(int targetWidth, int targ | |
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| @Override | ||
| int nearestAvailableZoom(int zoom) { | ||
| if (ImageDataLoader.isDynamicallySizable(new ByteArrayInputStream(this.inputStreamData))) { | ||
| return zoom; | ||
| } | ||
| return FileFormat.DEFAULT_ZOOM; | ||
| } | ||
| } | ||
|
|
||
| private class PlainImageProviderWrapper extends AbstractImageProviderWrapper { | ||
|
|
@@ -2388,6 +2437,11 @@ protected ElementAtZoom<ImageData> loadImageData(int zoom) { | |
| return getClosestAvailableImageData(zoom); | ||
| } | ||
|
|
||
| @Override | ||
| int nearestAvailableZoom(int zoom) { | ||
| return getClosestAvailableImageData(zoom).zoom(); | ||
| } | ||
|
|
||
| @Override | ||
| protected DestroyableImageHandle newImageHandle(ZoomContext zoomContext) { | ||
| int targetZoom = zoomContext.targetZoom(); | ||
|
|
@@ -2533,6 +2587,7 @@ private class ImageFileNameProviderWrapper extends BaseImageProviderWrapper<Imag | |
| newImageData(DPIUtil.getDeviceZoom()); | ||
| } | ||
|
|
||
|
|
||
| @Override | ||
| protected ElementAtZoom<ImageData> loadImageData(int zoom) { | ||
| ElementAtZoom<String> fileForZoom = DPIUtil.validateAndGetImagePathAtZoom(provider, zoom); | ||
|
|
@@ -2564,6 +2619,14 @@ protected ElementAtZoom<ImageData> loadImageData(int zoom) { | |
| return adaptImageDataIfDisabledOrGray(imageDataAtZoom); | ||
| } | ||
|
|
||
| @Override | ||
| int nearestAvailableZoom(int zoom) { | ||
| if (provider instanceof ImageDataAtSizeProvider) { | ||
| return zoom; | ||
| } | ||
| return DPIUtil.validateAndGetImagePathAtZoom(provider, zoom).zoom(); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(provider, styleFlag); | ||
|
|
@@ -2807,6 +2870,14 @@ protected Optional<ImageData> loadImageDataAtExactSize(int targetWidth, int targ | |
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| @Override | ||
| int nearestAvailableZoom(int zoom) { | ||
| if (provider instanceof ImageDataAtSizeProvider) { | ||
| return zoom; | ||
| } | ||
| return DPIUtil.validateAndGetImageDataAtZoom (provider, zoom).zoom(); | ||
| } | ||
| } | ||
|
|
||
| private class ImageGcDrawerWrapper extends DynamicImageProviderWrapper { | ||
|
|
@@ -2848,6 +2919,11 @@ protected ElementAtZoom<ImageData> loadImageData(int zoom) { | |
| return new ElementAtZoom<>(loadImageData(new ZoomContext(zoom)), zoom); | ||
| } | ||
|
|
||
| @Override | ||
| int nearestAvailableZoom(int zoom) { | ||
| return zoom; | ||
| } | ||
|
|
||
| private ImageData loadImageData(ZoomContext zoomContext) { | ||
| currentZoom = zoomContext; | ||
| int targetZoom = zoomContext.targetZoom(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the inverse test case could be added as well:
When providing imageData for 100% and 200% zoom, ensure that the handle is not reused when the first nearest available zoom is 100% and the second nearest available zoom is 200%
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. I have amended the commit with an according test (and an additional one testing the reuse of persisted handles to more completely cover the changes in this PR).