RFR: 8271024: Implement macOS Metal Rendering Pipeline [v8]
Andy Goryachev
angorya at openjdk.org
Wed Jul 16 19:06:56 UTC 2025
On Tue, 15 Jul 2025 05:41:32 GMT, Ambarish Rapte <arapte at 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
More information about the openjfx-dev
mailing list