<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>If you enable the software pipeline, that would not change
anything, apart from the libraries already being loaded and the
software renderer being used more often. The renderer used in the
example would be independent and so it would not interfere.</p>
<p>As far as the headless platform goes, if it allows for the
software pipeline to be loaded, then this code should function as
well as the Pisces renderer used here is pure Java. A mock Screen
may need to be provided. I'm not well-versed currently in the
specifics of the headless platform. Perhaps somebody working on
it could comment?</p>
<p>--John</p>
<p><br>
</p>
<div class="moz-cite-prefix">On 01/07/2025 18:40, Andy Goryachev
wrote:<br>
</div>
<blockquote type="cite"
cite="mid:CY8PR10MB72655335B8AD0CD6FC2D2BD4E541A@CY8PR10MB7265.namprd10.prod.outlook.com">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="Generator"
content="Microsoft Word 15 (filtered medium)">
<style>@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;}@font-face
{font-family:"Yu Gothic";
panose-1:2 11 4 0 0 0 0 0 0 0;}@font-face
{font-family:Aptos;
panose-1:2 11 0 4 2 2 2 2 2 4;}@font-face
{font-family:"Iosevka Fixed SS16";
panose-1:2 0 5 9 3 0 0 0 0 4;}@font-face
{font-family:"Times New Roman \(Body CS\)";
panose-1:2 11 6 4 2 2 2 2 2 4;}@font-face
{font-family:Consolas;
panose-1:2 11 6 9 2 2 4 3 2 4;}@font-face
{font-family:"\@Yu Gothic";
panose-1:2 11 4 0 0 0 0 0 0 0;}p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0in;
font-size:10.0pt;
font-family:"Aptos",sans-serif;}span.EmailStyle19
{mso-style-type:personal-reply;
font-family:"Iosevka Fixed SS16";
color:windowtext;}.MsoChpDefault
{mso-style-type:export-only;
font-size:10.0pt;
mso-ligatures:none;}div.WordSection1
{page:WordSection1;}</style>
<div class="WordSection1">
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">The
devil is in detail, but I like this proposal very much,
especially if most of the machinery is already there.
<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">How
would it work if one does enable the software pipeline? Or
the headless platform?<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">-andy<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<div id="mail-editor-reference-message-container">
<div>
<div>
<div
style="border:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0in 0in 0in">
<p class="MsoNormal" style="margin-bottom:12.0pt"><b><span
style="font-size:12.0pt;color:black">From:
</span></b><span
style="font-size:12.0pt;color:black">openjfx-dev
<a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev-retn@openjdk.org"><openjfx-dev-retn@openjdk.org></a> on behalf of
John Hendrikx <a class="moz-txt-link-rfc2396E" href="mailto:john.hendrikx@gmail.com"><john.hendrikx@gmail.com></a><br>
<b>Date: </b>Friday, June 27, 2025 at 03:15<br>
<b>To: </b><a class="moz-txt-link-abbreviated" href="mailto:openjfx-dev@openjdk.org">openjfx-dev@openjdk.org</a>
<a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev@openjdk.org"><openjfx-dev@openjdk.org></a><br>
<b>Subject: </b>Proposal: Exposing a
GraphicsContext-like API for WritableImage via
Software Renderer<o:p></o:p></span></p>
</div>
<p>Hi list,<o:p></o:p></p>
<p>I'm exploring whether there is interest in exposing a
(Canvas) GraphicsContext-like interface for
WritableImage backed by the existing software rendering
pipeline. Currently, JavaFX offers two main choices for
drawing and pixel manipulation, each with different
trade-offs:<o:p></o:p></p>
<p>- Canvas provides rich drawing primitives via
GraphicsContext, but offers no direct pixel
access—requiring costly GPU readbacks (e.g.,
snapshot()).<br>
- WritableImage, on the other hand, allows direct pixel
manipulation via PixelReader/PixelWriter, but has no
built-in support for drawing operations like shapes,
fills, or blending.<o:p></o:p></p>
<p>My proposal would combine the strengths of both:<o:p></o:p></p>
<p>- Expose drawing operations (shapes, fills, etc.) for
WritableImage <br>
- Direct access to image data before or after rendering
without GPU readbacks / snapshots<br>
- Reuse of JavaFX’s software rendering stack
(SWGraphics, PiscesRenderer, etc.) without activating
the software pipeline globally<o:p></o:p></p>
<p>I’ve successfully tested this approach in a non-modular
FX application by accessing internal APIs from
com.sun.prism.sw. With minor adjustments
(--add-exports), it may also work in modular
environments.<br>
<br>
Work needed to support this as a public API might
include:<o:p></o:p></p>
<p>- Creating a new GraphicsContext-like interface
("DrawingContext" ?)<br>
- Exposing a method on WritableImage to obtain such a
context<br>
- Optionally, refactoring Canvas' GraphicsContext to
implement this new interface (method signatures are
likely compatible)<br>
- Implementing the new interface on top of the software
renderer<o:p></o:p></p>
<p>See the end of the post for a working example (assuming
you place the code in the "com.sun.prism.sw" package and
deal with the module restrictions). Note that you do
not need to enable the software pipeline (and you don't
want to either, as the whole point is to remain GPU
accelerated but have software renderer backed drawing
primitives for images).<o:p></o:p></p>
<p>Any feedback appreciated!<o:p></o:p></p>
<p style="margin-bottom:12.0pt">--John<o:p></o:p></p>
<div>
<div>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">package</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
com.sun.prism.sw;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
com.sun.glass.ui.Screen;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
com.sun.glass.utils.NativeLibLoader;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
com.sun.pisces.JavaSurface;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
com.sun.pisces.PiscesRenderer;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
com.sun.pisces.RendererBase;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
com.sun.prism.paint.Color;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
java.nio.IntBuffer;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
javafx.application.Application;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
javafx.scene.Scene;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
javafx.scene.image.ImageView;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
javafx.scene.image.PixelWriter;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
javafx.scene.image.WritableImage;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
javafx.scene.layout.StackPane;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">import</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
javafx.stage.Stage;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">public</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">class</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
SWRendererExample {<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">static</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> {<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">NativeLibLoader.<i>loadLibrary</i>(</span><span
style="font-size:11.0pt;font-family:Consolas;color:#2A00FF">"prism_sw"</span><span
style="font-size:11.0pt;font-family:Consolas;color:black">);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">}<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">public</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">static</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">void</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> main(String[]
args) {<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">Application.<i>launch</i>(App.</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">class</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">}<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">public</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">static</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">class</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> App
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">extends</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> Application {<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:#646464">@Override</span><span
style="font-size:11.0pt;font-family:Consolas;color:black"><o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">public</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">void</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> start(Stage
primaryStage) {<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">WritableImage
writableImage =
<i>createImageWithSWPipeline</i>();<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">Scene scene =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> Scene(</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> StackPane(</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
ImageView(writableImage)));<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">primaryStage.setScene(scene);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">primaryStage.show();<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">}<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">}<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">public</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">static</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> WritableImage
createImageWithSWPipeline() {<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">int</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> width = 400;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">int</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black"> height = 300;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">SWResourceFactory
resourceFactory =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
SWResourceFactory(Screen.<i>getMainScreen</i>());<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">SWRTTexture
texture =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
SWRTTexture(resourceFactory, width, height);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">SWContext
swContext =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
SWContext(resourceFactory);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:#3F7F5F">// Set up a
surface to draw on and create the renderer:</span><span
style="font-size:11.0pt;font-family:Consolas;color:black"><o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">int</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">[]
backingArray =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">int</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">[width *
height];<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">IntBuffer
pixelBuffer = IntBuffer.<i>wrap</i>(backingArray);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">JavaSurface
surface =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
JavaSurface(backingArray, RendererBase.</span><span
style="font-size:11.0pt;font-family:Consolas;color:#0000C0">TYPE_INT_ARGB_PRE</span><span
style="font-size:11.0pt;font-family:Consolas;color:black">, width,
height);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">PiscesRenderer
renderer =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
PiscesRenderer(surface);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:#3F7F5F">// Create
SWGraphics for drawing (software renderer)</span><span
style="font-size:11.0pt;font-family:Consolas;color:black"><o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">SWGraphics
swGraphics =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
SWGraphics(texture, swContext, renderer);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">swGraphics.clear(Color.</span><span
style="font-size:11.0pt;font-family:Consolas;color:#0000C0">WHITE</span><span
style="font-size:11.0pt;font-family:Consolas;color:black">);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">swGraphics.setPaint(Color.</span><span
style="font-size:11.0pt;font-family:Consolas;color:#0000C0">BLUE</span><span
style="font-size:11.0pt;font-family:Consolas;color:black">);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">swGraphics.fillRect(50,
50, 100, 100);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">swGraphics.setPaint(Color.</span><span
style="font-size:11.0pt;font-family:Consolas;color:#0000C0">RED</span><span
style="font-size:11.0pt;font-family:Consolas;color:black">);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">swGraphics.fillEllipse(75,
75, 10, 20);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:#3F7F5F">// Take the
result and place it in a writable image:</span><span
style="font-size:11.0pt;font-family:Consolas;color:black"><o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">WritableImage
writableImage =
</span><b><span
style="font-size:11.0pt;font-family:Consolas;color:#0000A0">new</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
WritableImage(width, height);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">PixelWriter pw
= writableImage.getPixelWriter();<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">pw.setPixels(0,
0, width, height, javafx.scene.image.PixelFormat.<i>getIntArgbPreInstance</i>(),
pixelBuffer.array(), 0, width);<o:p></o:p></span></p>
<p style="margin:0in;background:white"><b><span
style="font-size:11.0pt;font-family:Consolas;color:#7F0055">return</span></b><span
style="font-size:11.0pt;font-family:Consolas;color:black">
writableImage;<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">}<o:p></o:p></span></p>
<p style="margin:0in;background:white"><span
style="font-size:11.0pt;font-family:Consolas;color:black">}<o:p></o:p></span></p>
</div>
</div>
</div>
</div>
</div>
</div>
</blockquote>
</body>
</html>