[Win32] Avoid redundant handle creation in GC.drawImage() by tracking effective zoom#3429
Conversation
There was a problem hiding this comment.
Pull request overview
Improves Win32 GC.drawImage() performance by ensuring the 100% image handle is persisted (instead of repeatedly creating temporary handles) when an image is requested at exactly 100% scale.
Changes:
- Persist the 100% handle when
getOrCreateImageHandleAtClosestSize(...)computesimageZoom == 100. - Refactor handle retrieval logic into a shared private helper (
getHandleInternal(...)) and delegategetHandle(...)to it.
8664275 to
3659105
Compare
bdb6f93 to
2155425
Compare
| 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.
Shouldn't a handle at exact size have zoom 100?
There was a problem hiding this comment.
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%.
| // 20x20 pixels → exactly 200% zoom for the 10x10 base image; uses 200% data | ||
| image.executeOnImageHandleAtBestFittingSize(h -> firstHandle[0] = h.handle(), 20, 20); | ||
| // 25x25 pixels → 250% zoom equivalent; nearest available is 200%, so the | ||
| // previously cached 200% handle should be reused | ||
| image.executeOnImageHandleAtBestFittingSize(h -> secondHandle[0] = h.handle(), 25, 25); |
There was a problem hiding this comment.
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.
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).
GC.drawImage() operations use a temporary image handle mechanism that creates a bitmap handle scaled to the pixel size required for each drawing operation. When an image is not available at the requested zoom, the closest available zoom is used instead. Previously, a fresh handle was created for every new drawing size, even when the same underlying image data would be used. For example, an image only available at 100% zoom (such as a plain PNG without HiDPI variants) caused a new handle to be allocated and immediately discarded on every drawImage() call that requested a different size. This change introduces the concept of "nearest available zoom" on image providers: each provider now reports, for a given requested zoom, the effective zoom at which it would actually supply data. This information is stored alongside the cached temporary handle after each drawImage(). Before allocating a new temporary handle for a different draw size, the nearest available zoom is used to look up an already existing handle: first in the image's persistent handle cache, then by comparing it against the zoom recorded with the previously cached temporary handle. If a matching handle is found, it is reused instead of creating a new one. If no handle exists and the nearest available zoom is 100%, a persistent handle is created eagerly: this frees any image data previously retained for API calls such as getImageData() and makes the handle available through the regular persistent handle lookup for all subsequent calls. This applies to any image where consecutive drawImage() calls at different sizes map to the same underlying data — e.g. a 100%-only image drawn at 200% and 300%, or an image with 100% and 200% variants when drawing at sizes between 200% and 300%. Four regression tests are added to ImagesWin32Tests to verify handle reuse (positive cases), the absence of unintended reuse across different nearest-available-zoom regions (negative case), and that a persistent handle created outside of drawImage() is found and reused by the nearest-available-zoom lookup. Fixes eclipse-platform#3419
Motivation
GC.drawImage()creates a temporary bitmap handle scaled to the pixel size needed for each drawing operation. When an image does not have data at the requested zoom, the closest available zoom is used instead.Previously, a fresh handle was created for every distinct draw size, even when the exact same underlying image data would be served. For example, an image only available at 100% zoom — such as a plain PNG without HiDPI variants — caused a new handle to be allocated and immediately discarded on every
drawImage()call that used a different size. This is unnecessary work: the provider would return the same 100% data regardless of the requested size, so there is no benefit in replacing the cached handle.Solution
This change introduces the concept of nearest available zoom on image providers: each provider now reports, for a given requested zoom, the effective zoom at which it would actually supply data. This information is stored alongside the cached temporary handle after each
drawImage()call.Before allocating a new temporary handle, the nearest available zoom is used to look up an already existing handle: first in the image's persistent handle cache, then by comparing it against the zoom recorded with the previously cached temporary handle. If a matching handle is found, it is reused instead of creating a new one. If no handle exists and the nearest available zoom is 100%, a persistent handle is created eagerly: this frees any image data previously retained for API calls such as
getImageData()and makes the handle available through the regular persistent handle lookup for all subsequent calls.This applies to any image whose consecutive
drawImage()calls at different sizes map to the same underlying data — e.g.:Two regression tests are added to
ImagesWin32Teststo verify both scenarios.Fixes #3419
Addresses #3445