Skip to content

[Win32] Avoid redundant handle creation in GC.drawImage() by tracking effective zoom#3429

Merged
HeikoKlare merged 1 commit into
eclipse-platform:masterfrom
HeikoKlare:issue-3419
Jul 22, 2026
Merged

[Win32] Avoid redundant handle creation in GC.drawImage() by tracking effective zoom#3429
HeikoKlare merged 1 commit into
eclipse-platform:masterfrom
HeikoKlare:issue-3419

Conversation

@HeikoKlare

@HeikoKlare HeikoKlare commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

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.:

  • A 100%-only image (plain PNG) drawn at 200% and then 300% — both fall back to 100% data, so the same handle is reused.
  • An image with 100% and 200% variants drawn at 200% and then 250% — both use the 200% data, so the same handle is reused.

Two regression tests are added to ImagesWin32Tests to verify both scenarios.

Fixes #3419

Addresses #3445

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (win32)

   33 files  ±0     33 suites  ±0   4m 30s ⏱️ -8s
4 722 tests +4  4 646 ✅ +4  76 💤 ±0  0 ❌ ±0 
1 251 runs  +4  1 227 ✅ +4  24 💤 ±0  0 ❌ ±0 

Results for commit 61ee2c1. ± Comparison against base commit f81b654.

♻️ This comment has been updated with latest results.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(...) computes imageZoom == 100.
  • Refactor handle retrieval logic into a shared private helper (getHandleInternal(...)) and delegate getHandle(...) to it.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Test Results

  200 files  ±0    200 suites  ±0   27m 38s ⏱️ +6s
4 746 tests +2  4 722 ✅ +2   24 💤 ±0  0 ❌ ±0 
6 894 runs  +2  6 726 ✅ +2  168 💤 ±0  0 ❌ ±0 

Results for commit d786a6d. ± Comparison against base commit c76abe1.

♻️ This comment has been updated with latest results.

@HeikoKlare
HeikoKlare force-pushed the issue-3419 branch 2 times, most recently from 8664275 to 3659105 Compare July 20, 2026 16:34
@HeikoKlare HeikoKlare changed the title [Win32] Store 100% handle when drawing single-zoom bitmap [Win32] Avoid redundant handle creation in GC.drawImage() by tracking effective zoom Jul 20, 2026
@HeikoKlare
HeikoKlare requested a review from Copilot July 20, 2026 20:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

Comment thread bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java Outdated
@HeikoKlare
HeikoKlare force-pushed the issue-3419 branch 4 times, most recently from bdb6f93 to 2155425 Compare July 21, 2026 07:48
@HeikoKlare
HeikoKlare marked this pull request as ready for review July 21, 2026 07:59
if (imageData.isPresent()) {
temporaryHandleContainer = init(imageData.get(), -1);
return Optional.of(temporaryHandleContainer);
temporaryHandleContainer = new TemporaryHandleForZoom(init(imageData.get(), -1), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't a handle at exact size have zoom 100?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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%.

Comment on lines +90 to +94
// 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);

Copy link
Copy Markdown

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%

Copy link
Copy Markdown
Contributor Author

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).

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
@HeikoKlare
HeikoKlare merged commit 7429274 into eclipse-platform:master Jul 22, 2026
18 checks passed
@HeikoKlare
HeikoKlare deleted the issue-3419 branch July 22, 2026 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SWT drawImage Performance Regression On Windows

4 participants