On Mon, 10 Nov 2025 17:20:46 GMT, Andy Goryachev <[email protected]> wrote:
> This is interesting, similar to `BufferedImage.createGraphics()` in AWT.
>
> Questions:
>
> * so this will **aways** be slower than Canvas?
See my mailinglist answer, but in short, there are cases where `WritableImage`
(which is not a Node) can be faster, especially when you want to access the
results with say a `PixelReader`.
> * are the results going to be exactly the same, or there will be
> platform-specific differences in anti-aliasing etc?
I think I missed a nuance here in my mailinglist reply. I think the software
renderer will always give the same results, regardless of platform (interesting
for certain use cases I suppose, and perhaps for testing as well). It will
almost certainly differ however from GPU renderings of the same operations, but
for supported operations it may be hard to distinguish what was GPU and what
was CPU rendered.
> The size argument is a good one.
>
> One last question: would it make more sense to update the `Canvas` instead to
> remove the size limitation, instead of creating some parallel way of doing
> the same thing, possibly introducing subtle and not so subtle differences?
I thought about that, but this is a lot harder than it is to do this for
writable image. Writable image does tiling only **after** drawing operations
have completed (it splits the normal RAM buffer into textures that overlap by 1
pixel, all pre-existing code).
In contrast, Canvas let's the GPU do the drawing, and drawing across multiple
tiles is a lot harder than drawing in a single buffer, then tiling later. For
example, a thick brush line, or some effect like blur, is really hard to get
right when it crosses textures. The software renderer doesn't have this problem
as it still has a full normal RAM buffer.
So, although we may be a bit smarter about what Canvas does, it doesn't look
like a trivial quick win. I think it is better to see `Canvas` as a gateway to
a single GPU texture, with all limitations that apply there (size
restrictions). The automatic scale up is an unfortunate implementation
decision IMHO that perhaps we can have an option for to disable, so the user
can handle this themselves (with `setScaleX/Y` and a `Group` if need be, so
they get an exact 1:1 pixel backed texture).
Also, I may do a short investigation to see if we can have `Canvas` gracefully
degrade (and perhaps `ImageView` as well) when the allocation of a texture
fails due to size restrictions or insufficient VRAM. Currently, it starts
throwing NPE's and the UI is borked. I mean it would be infinitely better to
just show an empty square or something, instead of having the whole layout
break.
> Another aspect: let's say we decide to go with two parallel APIs - should we
> then make sure they mirror each other exactly? If so, would it create another
> maintenance burden?
So far I've mirrored the API of `GraphicsContext` in the `DrawingContext`
interface. My intention is to have `GraphicsContext` implement
`DrawingContext`, and document it similar to how the collections API is
documented with methods throwing `UnsupportedOperationException` when a
specific drawing context can't handle a specific operation.
So for example, the `DrawingContext` would document the `setGlobalBlendMode`
like this:
/**
* Sets the global blend mode.
* ...
* @param op the {@code BlendMode} that will be set or null.
* @throws UnsupportedOperationException if the given blend mode is not
supported by this implementation
*/
void setGlobalBlendMode(BlendMode op);
The software renderer will then throw `UnsupportedOperationException` for blend
modes it doesn't support.
The main differences between the GPU renderer and the software renderer are a
tiny list:
- Software renderer doesn't support more than 1 blend mode
- Software renderer can't do arbitrary clips (ie. a path clip)
- Software renderer can't do transform rotations/shears + a rectangular clip at
the same time
For clipping therefore I've documented it like this:
/**
* Sets the current transform. Only 2D transforms are supported. The only
* ...
* @param xform The affine to be copied and used as the current transform.
* @throws UnsupportedOperationException if a transform containing rotation
* or shear is set while a clip is active and the implementation does
* not support such clips
*/
void setTransform(Affine xform);
In the end, it would mean a `DrawingContext` is a full fledged API, implemented
for both the GPU and SW pipelines, with differences that are minor enough that
most users will be able to switch between the two without any adjustments. Only
when using arbitrary clips and/or special blend modes do they need to make
adjustments.
-------------
PR Comment: https://git.openjdk.org/jfx/pull/1969#issuecomment-3513555471
PR Comment: https://git.openjdk.org/jfx/pull/1969#issuecomment-5379537870