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

Reply via email to