On Tue, 22 Sep 2026 20:58:01 GMT, Andy Goryachev <[email protected]> wrote:

>> John Hendrikx has updated the pull request incrementally with one additional 
>> commit since the last revision:
>> 
>>   Optimized dirty rect handling (benefits more than just SWDraingContext)
>
> modules/javafx.graphics/src/main/java/javafx/scene/image/WritableImage.java 
> line 175:
> 
>> 173:     public final DrawingContext getDrawingContext() {
>> 174:         if (drawingContext == null) {
>> 175:             drawingContext = 
>> Toolkit.getToolkit().createDrawingContext(getWritablePlatformImage(), 
>> this::notifyDrawingContextDirty);
> 
> `setPlatformImage()` L1086 can replace the platform image (as a result of 
> `snapshot`, for instance).  this will disassociate the cached 
> `drawingContext` from the platform image.

It seems that the same happens when the user supplies their own `PixelBuffer` 
-- snapshot just replaces the platform image, effectively orphaning the storage 
provided by the user -- that's bad, as the user can easily hold on to this and 
expect it to be updated.

So IMHO, `snapshot` is broken, and `setPlatformImage` should be removed for two 
reasons:

- A user supplied buffer must be honored, and it currently is just silently 
replaced; and this already shows up as a bug (`PixelBuffer.updateBuffer` still 
targetting the user storage, while  `image.getPixelReader().getArgb(...)` reads 
the new snapshot provided storage
- The GPU and CPU path currently both allocate a new massive heap object for 
the snapshot, which basically already exists. The GPU path copies the data from 
a direct memory allocation to this new heap buffer (even though it could have 
just copied it to the existing one), and the CPU path renders into a new heap 
buffer, even though it could have rendered into the existing one

Also the tiled path (when the snapshot would be larger than the maximum texture 
size) already does in-place updates, so having the normal path do so as well 
would make it consistent.

So, I'd like to make a real fix for this in a different PR, rather than 
stacking this into this one. The fix would entail:

- Add `Tookit.renderToImage` method that accepts a target platform image, and 
use that for `snapshot`
- For the CPU/SW path, if the format matches, use the target as-is; if not 
allocate a temp buffer, and copy -- either way, the user supplied buffer is 
updated as expected, not silently orphaned
- For the GPU path, it always allocates a new off-heap buffer regardless for 
the GPU to access, but it doesn't need to also allocate a new heap buffer to 
copy that buffer into; it can just copy it into the given target instead -- 
same amount of work, but saves a (potentially huge) buffer allocation
- The `loadTkImage`/`getTkImageLoader`/`Image.setPlatformImage` can all be 
removed (all only used by snapshots)
- After snapshotting into the existing buffer, mark it dirty
- Stop holding on to a render target for the snapshot (currently, the GPU path 
holds on to a buffer the size of the snapshot even after it is copied to the 
heap, potentially holding on to several MB's of memory to avoid a relatively 
cheap render target creation 'just in case' another snapshot is made... -- if 
the snapshot is 20 MB, then the current GPU path allocates another 20 MB as 
render target (and never discards it) and during a snapshot holds on to another 
20 MB; so memory use goes like this:
  - Pre-snapshot there is just the Image: 20 MB
  - During snapshot a render target is created: +20 MB (40 MB retained)
  - After the GPU render completes a heap buffer is allocated: +20 MB (60 MB 
retained)
  - The render target is copied to the new buffer
  - The new buffer replaces the user buffer (or initial buffer if not user 
supplied)
  - When GC decides to run the old buffer is collected: -20 MB (but still 40 MB 
retained forever as the render target is kept)

**The end result of the above changes:**

- User suppled buffers are correctly honored even for snapshots (this is a 
pre-existing bug given that the documentation states that it renders the 
snapshot into the provided image, which can have a user supplied buffer)
- Less GC churn when snapshotting on both the CPU and GPU paths, unless the 
user buffer type  mismatches (but for correctness this must then be honored 
anyway)
- The platform image is stable, so no need to query it each time and wrapping 
it with `DrawingContext` is safe -- `PixelWriter` can also then just wrap it 
instead of querying it each time

-------------

PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r4171290341

Reply via email to