On Tue, 15 Jul 2025 05:41:32 GMT, Ambarish Rapte <ara...@openjdk.org> wrote:

>> ### Description
>> This is the implementation of new graphics rendering pipeline for JavaFX 
>> using Metal APIs on MacOS.
>> We released two Early Access (EA) builds and have reached a stage where it 
>> is ready to be integrated.
>> Default rendering pipeline on macOS has not been changed by this PR. OpenGL 
>> still stays as the default rendering pipeline and Metal rendering pipeline 
>> is optional to choose (by providing  `-Dprism.order=mtl`)
>> The `-Dprism.verbose=true` option can be used to verify the rendering 
>> pipeline in use.
>> 
>> ### Details about the changes
>> 
>> **Shader changes**
>> - MSLBackend class: This is the primary class that parses (Prism and Decora) 
>> jsl shaders into Metal shaders(msl)
>> - There are a few additional Metal shader files added under directory : 
>> modules/javafx.graphics/src/main/native-prism-mtl/msl
>> 
>> **Build changes** - There are new tasks added to build.gradle for
>> - Generation/ Compilation/ linking of Metal shaders
>> - Compilation of Prism Java and Objective C files
>> 
>> **Prism** - Prism is the rendering engine of JavaFX.
>> - Added Metal specific Java classes and respective native implementation 
>> which use Metal APIs
>> - Java side changes:
>>   - New Metal specific classes: Classes prefixed with MTL, are added here : 
>> modules/javafx.graphics/src/main/java/com/sun/prism/mtl 
>>   - Modification to Prism common classes: A few limited changes were 
>> required in Prism common classes to support Metal classes. 
>> - Native side changes:
>>   - New Metal specific Objective C implementation is added here: 
>> modules/javafx.graphics/src/main/native-prism-mtl
>> 
>> **Glass** - Glass is the windowing toolkit of JavaFX
>> - Existing Glass classes are refactored to support both OpenGL and Metal 
>> pipelines
>> - Added Metal specific Glass implementation.
>> 
>> ### Testing
>> - Testing performed on different hardware and macOS versions.
>>   - HW - macBooks with Intel, Intel with discrete graphics card, Apple 
>> Silicon (M1, M2 and M4)
>>   - macOS versions - macOS 13, macOS 14 and macOS 15
>> - Functional Testing:
>>   - All headful tests pass with Metal pipeline.
>>   - Verified samples applications: Ensemble and toys
>> - Performance Testing:
>>   - Tested with RenderPerfTest: Almost all tests match/exceed es2 
>> performance except a few that fall a little short on different hardwares. 
>> These will be addressed separately (there are open bugs already filed)
>> 
>> ### Note to reviewers :
>> - Providing `-Dprism.order=mtl` option is a must for testing the Metal 
>> pipeline
>> - It woul...
>
> Ambarish Rapte has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   changes for running apps in eclipse

first batch of comments (java), mostly questions and minor suggestions.  will 
review the native code next.

modules/javafx.graphics/src/jslc/java/com/sun/scenario/effect/compiler/backend/hw/MSLBackend.java
 line 334:

> 332: 
> 333:             try {
> 334:                 FileWriter fragmentShaderHeaderFile = new 
> FileWriter(headerFilesDir + FRAGMENT_SHADER_HEADER_FILE_NAME);

This writer is not buffered, meaning it will be too slow.
I'd say either to wrap it into `BufferedWriter`, or better yet - use 
`StringBuilder`s everywhere and write the single generated string in one 
operation.

modules/javafx.graphics/src/jslc/java/com/sun/scenario/effect/compiler/backend/hw/MSLBackend.java
 line 336:

> 334:                 FileWriter fragmentShaderHeaderFile = new 
> FileWriter(headerFilesDir + FRAGMENT_SHADER_HEADER_FILE_NAME);
> 335:                 
> fragmentShaderHeaderFile.write(fragmentShaderHeader.toString());
> 336:                 fragmentShaderHeaderFile.write("#endif\n");

this line is weird - why is it here and not after L331?

modules/javafx.graphics/src/jslc/java/com/sun/scenario/effect/compiler/backend/hw/MSLBackend.java
 line 337:

> 335:                 
> fragmentShaderHeaderFile.write(fragmentShaderHeader.toString());
> 336:                 fragmentShaderHeaderFile.write("#endif\n");
> 337:                 fragmentShaderHeaderFile.close();

an exception on lines 335-336 will cause the writer not being closed. 
can we use try-with-resources?

modules/javafx.graphics/src/jslc/java/com/sun/scenario/effect/compiler/backend/hw/MSLBackend.java
 line 346:

> 344: 
> 345:         try {
> 346:             FileWriter objCHeaderFile = new FileWriter(headerFilesDir + 
> objCHeaderFileName);

same issue here, need to close the writer properly.

modules/javafx.graphics/src/jslc/java/com/sun/scenario/effect/compiler/backend/hw/MSLBackend.java
 line 423:

> 421: 
> 422:             objCHeaderFile.write("    return nil;\n");
> 423:             objCHeaderFile.write("};\n\n");

Not sure if it's worth doing, but perhaps this code can use a single 
StringBuilder to build the code, then use `Files.writeString()` to write it 
(this might require a bit of refactoring).

modules/javafx.graphics/src/jslc/java/com/sun/scenario/effect/compiler/backend/hw/MSLBackend.java
 line 444:

> 442:         }
> 443: 
> 444:         uniformsForShaderFile = uniformsForShaderFile.replace(" float2", 
> " vector_float2");

feels like this code was written in 1997.  Use a StringBuilder perhaps?

modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2SwapChain.java line 
53:

> 51:     // a value of zero corresponds to the windowing system-provided
> 52:     // framebuffer object
> 53:     long nativeDestHandle = 0;

`= 0` is not strictly necessary.

modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2SwapChain.java line 
240:

> 238:     @Override
> 239:     public int getFboID() {
> 240:         return (int)nativeDestHandle;

this results in loss of information.  any possibility that it may backfire 
somehow, by mapping two different `nativeDestHandle`s to the same `int`?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLContext.java line 
159:

> 157:     @Override
> 158:     protected State updateRenderTarget(RenderTarget target, NGCamera 
> camera, boolean depthTest) {
> 159:         MTLLog.Debug("MTLContext.updateRenderTarget() :target = " + 
> target + ", camera = " + camera + ", depthTest = " + depthTest);

general question: is this temporary?  can we use maybe a better logging 
mechanism, one that does not concatenate strings when disabled?  works case, 
surround this by `if(debug)` ?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLContext.java line 
172:

> 170: 
> 171:         // Validate the camera before getting its computed data
> 172:         if (camera instanceof NGDefaultCamera) {

suggestion: `if (camera instanceof NGDefaultCamera nc)` to avoid explicit cast 
in L173

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLContext.java line 
180:

> 178:             double vw = camera.getViewWidth();
> 179:             double vh = camera.getViewHeight();
> 180:             if (targetWidth != vw || targetHeight != vh) {

would it make sense to detect the case when target(w/h) is very close to (w/h) 
and skip scaling?  or is this scenario unlikely?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLContext.java line 
339:

> 337: 
> 338:     public long getMetalCommandQueue()
> 339:     {

I like this style, but the rest of the file uses K&R braces...

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLContext.java line 
350:

> 348:         // if (checkDisposed()) return;
> 349:         // nSetDeviceParametersFor2D(pContext);
> 350:     }

remove the commented out code LL348-349, leave the comment?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLContext.java line 
357:

> 355:         // result of this call, hence the method is no-op.
> 356:         // But overriding the method here for any future reference.
> 357:         // if (checkDisposed()) return;

same here?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLContext.java line 
489:

> 487:     }
> 488: 
> 489:     private void printRawMatrix(String mesg) {

unused method?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLLog.java line 31:

> 29: 
> 30: public class MTLLog {
> 31:     public static void Debug(String str) {

Since `MTTLLog` is a home-grown facility:

perhaps we should consider allowing `Supplier<String>` lambdas (or 
`Supplier<Object>`), or allow for `MessageFormat`'ted strings, to avoid 
concatenations.

Lambdas may be faster, but they do require effectively final parameters.

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLMesh.java line 60:

> 58:     @Override
> 59:     public void dispose() {
> 60:         disposerRecord.dispose();

two questions here:
1. why is `disposerRecord.dispose()`; here (and in `D3DMesh`, `ES2Mesh`) and 
not in the `BaseGraphicResource` ?
2. any danger in the `dispose()` being called twice?  (could it happen at all?)

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLMesh.java line 93:

> 91:         }
> 92: 
> 93:         void traceDispose() {}

what's the purpose of this method?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLPhongMaterial.java 
line 104:

> 102:                 String logname = PhongMaterial.class.getName();
> 103:                 PlatformLogger.getLogger(logname).warning(
> 104:                         "Warning: Low on texture resources. Cannot 
> create texture.");

I suppose this message is the same as elsewhere.  Is it clear enough for the 
user?  Does the user have a clear idea of how to correct the issue?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLPipeline.java line 
90:

> 88: 
> 89:             // This enables sharing of MTLCommandQueue between PRISM and 
> GLASS
> 90:             HashMap devDetails = (HashMap) 
> MTLPipeline.getInstance().getDeviceDetails();

cast is unnecessary.  suggestion: 
`Map devDetails = MTLPipeline.getInstance().getDeviceDetails();`

I guess we've inherited the API based on the raw object here?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLRTTextureData.java 
line 35:

> 33:     @Override
> 34:     public void dispose() {
> 35:         if (pTexture != 0L) {

this code is weird: why doesn't call super.dispose()?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLResourceFactory.java 
line 75:

> 73:             i *= 2;
> 74:         }
> 75:         return i;

interesting...  would `1 << val` be simpler (barring overflow)?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLResourceFactory.java 
line 128:

> 126:             return (Shader) m.invoke(null, new Object[]{this, 
> shaderName, nameStream});
> 127:         } catch (Throwable e) {
> 128:             e.printStackTrace();

do we want to use a logger or the `stderr` is the best decision in this case?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLResourceFactory.java 
line 183:

> 181: 
> 182:         long pResource = nCreateTexture(context.getContextHandle() ,
> 183:                 (int) formatHint.ordinal(), (int) usageHint.ordinal(),

unnecessary casts to (int) x2

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLResourceFactory.java 
line 274:

> 272:         Texture tex = createTexture(texFormat, Usage.DEFAULT, 
> WrapMode.CLAMP_TO_EDGE, texWidth, texHeight);
> 273: 
> 274:         frame.releaseFrame();

could all these `frame.releaseFrame()` be moved to a `finally` block?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLResourceFactory.java 
line 305:

> 303:     @Override
> 304:     public int getRTTWidth(int w, Texture.WrapMode wrapMode) {
> 305:         // Below debugging logic replicates D3DResoureFactory

is this code still needed?  also L314

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLResourceFactory.java 
line 396:

> 394:     public void dispose() {
> 395:         super.dispose();
> 396:         context.dispose();

Q: should we call super.dispose() _after_ context.dispose()?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLShader.java line 57:

> 55:             shaderMap.put(fragmentFunctionName, this);
> 56:         } else {
> 57:             throw new AssertionError("Failed to create Shader");

is `AssertionError` the right type here?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLSwapChain.java line 
128:

> 126: 
> 127:         if (pState.getNativeFrameBuffer() == 0) {
> 128:             System.err.println("Native backbuffer texture from Glass is 
> nil.");

is `stderr` ok here?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLSwapChain.java line 
143:

> 141:                 stableBackbuffer = null;
> 142:             } /*else {
> 143:                 // RT-27554

is this still relevant?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLTexture.java line 97:

> 95:                         int srcscan, boolean skipFlush) {
> 96: 
> 97:         if (format.getDataType() == PixelFormat.DataType.INT) {

would a `switch` be better here?

modules/javafx.graphics/src/main/java/com/sun/prism/mtl/MTLTexture.java line 
122:

> 120:             byte[] arr = buf.hasArray() ? buf.array() : null;
> 121: 
> 122:             if (format == PixelFormat.BYTE_BGRA_PRE || format == 
> PixelFormat.BYTE_ALPHA) {

also here, `switch` ?

modules/javafx.graphics/src/main/java/com/sun/scenario/effect/impl/hw/mtl/MTLShaderSource.java
 line 36:

> 34:     @Override
> 35:     public InputStream loadSource(String name) {
> 36:         // MSL shaders are compilend and linked into a MTLLibrary at 
> build time.

-> compiled

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

PR Review: https://git.openjdk.org/jfx/pull/1824#pullrequestreview-3025473708
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210708247
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210709590
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210695663
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210697664
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210751940
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210757844
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210763281
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210767379
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210942089
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210945178
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210961660
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210974725
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210977355
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210977769
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2210983716
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211116140
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211133718
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211139193
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211147969
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211163123
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211170894
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211174999
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211177828
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211182961
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211193965
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211197823
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211205783
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211211057
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211305810
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211306463
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211310013
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211311714
PR Review Comment: https://git.openjdk.org/jfx/pull/1824#discussion_r2211315024

Reply via email to