diff --git a/make/test/JtregNativeJdk.gmk b/make/test/JtregNativeJdk.gmk index 8576f942bd89..913316990b1b 100644 --- a/make/test/JtregNativeJdk.gmk +++ b/make/test/JtregNativeJdk.gmk @@ -97,7 +97,8 @@ ifeq ($(call isTargetOs, macosx), true) BUILD_JDK_JTREG_LIBRARIES_LIBS_libTestDynamicStore := \ -framework Cocoa -framework SystemConfiguration BUILD_JDK_JTREG_LIBRARIES_LIBS_libSharedTexturesTest := \ - -framework Cocoa -framework Metal + -framework Cocoa -framework Metal -framework OpenGL + BUILD_JDK_JTREG_EXCLUDE += libSharedTexturesTest.c else BUILD_JDK_JTREG_EXCLUDE += libTestMainKeyWindow.m BUILD_JDK_JTREG_EXCLUDE += libTestDynamicStore.m @@ -109,6 +110,7 @@ endif ifeq ($(OPENJDK_TARGET_OS), windows) BUILD_JDK_JTREG_LIBRARIES_LIBS_libwindows_touch_robot := user32.lib BUILD_JDK_JTREG_EXCLUDE += libtouchscreen_device.c + BUILD_JDK_JTREG_LIBRARIES_LIBS_libSharedTexturesTest := gdi32.lib opengl32.lib user32.lib else ifeq ($(OPENJDK_TARGET_OS), linux) BUILD_JDK_JTREG_EXCLUDE += libwindows_touch_robot.c @@ -124,6 +126,7 @@ ifeq ($(call isTargetOs, linux), true) BUILD_JDK_JTREG_LIBRARIES_STRIP_SYMBOLS_libFib := false # nio tests' libCreationTimeHelper native needs -ldl linker flag BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libCreationTimeHelper := -ldl + BUILD_JDK_JTREG_LIBRARIES_LIBS_libSharedTexturesTest := -lX11 -lGL -lGLX endif ifeq ($(ASAN_ENABLED), true) diff --git a/src/java.desktop/linux/classes/com/jetbrains/desktop/SharedTexturesService.java b/src/java.desktop/linux/classes/com/jetbrains/desktop/SharedTexturesService.java new file mode 100644 index 000000000000..2c04c05cdac6 --- /dev/null +++ b/src/java.desktop/linux/classes/com/jetbrains/desktop/SharedTexturesService.java @@ -0,0 +1,74 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.jetbrains.desktop; + +import com.jetbrains.desktop.image.TextureWrapperSurfaceManager; +import com.jetbrains.exported.JBRApi; +import sun.awt.image.SurfaceManager; +import sun.java2d.SurfaceData; + +import sun.java2d.opengl.*; + +import java.awt.GraphicsConfiguration; +import java.awt.Image; + +@JBRApi.Service +@JBRApi.Provides("SharedTextures") +public class SharedTexturesService extends SharedTextures { + @Override + public int getTextureType(GraphicsConfiguration gc) { + if (gc instanceof GLXGraphicsConfig) { + return OPENGL_TEXTURE_TYPE; + } + + return 0; + } + + @Override + public long[] getOpenGLContextInfo(GraphicsConfiguration gc) { + if (gc instanceof GLXGraphicsConfig glxGraphicsConfig) { + return new long[] { + GLXGraphicsConfigExt.getSharedContext(), + GLXGraphicsConfigExt.getAwtDisplay(), + GLXGraphicsConfigExt.getFBConfig(glxGraphicsConfig), + }; + } + + throw new UnsupportedOperationException("Unsupported graphics configuration: " + gc); + } + + @Override + public SurfaceManager createSurfaceManager(GraphicsConfiguration gc, Image image, long texture) { + SurfaceData sd; + if (gc instanceof GLXGraphicsConfig glxGraphicsConfig) { + sd = new GLXTextureWrapperSurfaceData(glxGraphicsConfig, image, texture); + } else { + throw new UnsupportedOperationException("Unsupported graphics configuration: " + gc); + } + + return new TextureWrapperSurfaceManager(sd); + } +} diff --git a/src/java.desktop/macosx/classes/com/jetbrains/desktop/SharedTexturesService.java b/src/java.desktop/macosx/classes/com/jetbrains/desktop/SharedTexturesService.java index 0842f4572c79..ac8a1c5e76be 100644 --- a/src/java.desktop/macosx/classes/com/jetbrains/desktop/SharedTexturesService.java +++ b/src/java.desktop/macosx/classes/com/jetbrains/desktop/SharedTexturesService.java @@ -31,36 +31,21 @@ import sun.java2d.SurfaceData; import sun.java2d.metal.MTLGraphicsConfig; import sun.java2d.metal.MTLTextureWrapperSurfaceData; +import sun.java2d.opengl.CGLGraphicsConfig; +import sun.java2d.opengl.CGLGraphicsConfigExt; +import sun.java2d.opengl.CGLTextureWrapperSurfaceData; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsEnvironment; -import java.awt.Image; +import java.awt.*; @JBRApi.Service @JBRApi.Provides("SharedTextures") public class SharedTexturesService extends SharedTextures { - private final int textureType; - - public SharedTexturesService() { - textureType = getTextureTypeImpl(); - if (textureType == 0) { - throw new JBRApi.ServiceNotAvailableException(); - } - } - @Override - public int getTextureType() { - return textureType; - } - - private static int getTextureTypeImpl() { - GraphicsConfiguration gc = GraphicsEnvironment - .getLocalGraphicsEnvironment() - .getDefaultScreenDevice() - .getDefaultConfiguration(); - + public int getTextureType(GraphicsConfiguration gc) { if (gc instanceof MTLGraphicsConfig) { return METAL_TEXTURE_TYPE; + } else if (gc instanceof CGLGraphicsConfig) { + return OPENGL_TEXTURE_TYPE; } return 0; @@ -71,10 +56,24 @@ protected SurfaceManager createSurfaceManager(GraphicsConfiguration gc, Image im SurfaceData sd; if (gc instanceof MTLGraphicsConfig mtlGraphicsConfig) { sd = new MTLTextureWrapperSurfaceData(mtlGraphicsConfig, image, texture); + } else if (gc instanceof CGLGraphicsConfig cglGraphicsConfig) { + sd = new CGLTextureWrapperSurfaceData(cglGraphicsConfig, image, texture); } else { - throw new IllegalArgumentException("Unsupported graphics configuration: " + gc); + throw new UnsupportedOperationException("Unsupported graphics configuration: " + gc); } return new TextureWrapperSurfaceManager(sd); } + + @Override + public long[] getOpenGLContextInfo(GraphicsConfiguration gc) { + if (gc instanceof CGLGraphicsConfig cglGraphicsConfig) { + return new long[] { + CGLGraphicsConfigExt.getSharedContext(), + CGLGraphicsConfigExt.getPixelFormat() + }; + } + + throw new UnsupportedOperationException("Unsupported graphics configuration: " + gc); + } } diff --git a/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLGraphicsConfigExt.java b/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLGraphicsConfigExt.java new file mode 100644 index 000000000000..2946cf6c9807 --- /dev/null +++ b/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLGraphicsConfigExt.java @@ -0,0 +1,6 @@ +package sun.java2d.opengl; + +public class CGLGraphicsConfigExt { + public static native long getSharedContext(); + public static native long getPixelFormat(); +} diff --git a/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLSurfaceData.java b/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLSurfaceData.java index 592d3783aba8..9c858c0fd6f5 100644 --- a/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLSurfaceData.java +++ b/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLSurfaceData.java @@ -43,8 +43,8 @@ private native void initOps(OGLGraphicsConfig gc, long pConfigInfo, long pPeerData, long layerPtr, int xoff, int yoff, boolean isOpaque); - private CGLSurfaceData(CGLLayer layer, CGLGraphicsConfig gc, - ColorModel cm, int type, int width, int height) { + protected CGLSurfaceData(CGLLayer layer, CGLGraphicsConfig gc, + ColorModel cm, int type, int width, int height) { super(gc, cm, type); // TEXTURE shouldn't be scaled, it is used for managed BufferedImages. scale = type == TEXTURE ? 1 : gc.getDevice().getScaleFactor(); diff --git a/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLTextureWrapperSurfaceData.java b/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLTextureWrapperSurfaceData.java new file mode 100644 index 000000000000..7500998d11fc --- /dev/null +++ b/src/java.desktop/macosx/classes/sun/java2d/opengl/CGLTextureWrapperSurfaceData.java @@ -0,0 +1,50 @@ +package sun.java2d.opengl; + +import sun.java2d.SurfaceData; + +import java.awt.*; +import java.awt.image.ColorModel; +import java.util.concurrent.atomic.AtomicBoolean; + +public class CGLTextureWrapperSurfaceData extends CGLSurfaceData { + + public CGLTextureWrapperSurfaceData(CGLGraphicsConfig gc, Image image, long textureId) { + super(null, gc, gc.getColorModel(TRANSLUCENT), RT_TEXTURE, 0, 0); + + OGLRenderQueue rq = OGLRenderQueue.getInstance(); + AtomicBoolean success = new AtomicBoolean(false); + rq.lock(); + try { + OGLContext.setScratchSurface(gc); + rq.flushAndInvokeNow(() -> success.set(OGLSurfaceDataExt.initWithTexture(this, textureId))); + } finally { + rq.unlock(); + } + + if (!success.get()) { + throw new IllegalArgumentException("Failed to init the surface data"); + } + } + + @Override + public SurfaceData getReplacement() { + throw new UnsupportedOperationException(); + } + + @Override + public Rectangle getBounds() { + return getNativeBounds(); + } + + @Override + public Object getDestination() { + return null; + } + + @Override + public void flush() { + // reset the texture id first to avoid the texture deallocation + OGLSurfaceDataExt.resetTextureId(this); + super.flush(); + } +} diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/opengl/CGLGraphicsConfigExt.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/opengl/CGLGraphicsConfigExt.m new file mode 100644 index 000000000000..311f4546f49e --- /dev/null +++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/opengl/CGLGraphicsConfigExt.m @@ -0,0 +1,20 @@ +#import "sun_java2d_opengl_CGLGraphicsConfigExt.h" + +#import "JNIUtilities.h" + +extern NSOpenGLContext *sharedContext; +extern NSOpenGLPixelFormat *sharedPixelFormat; + +JNIEXPORT jlong JNICALL +Java_sun_java2d_opengl_CGLGraphicsConfigExt_getSharedContext + (JNIEnv *env, jclass cls) +{ + return ptr_to_jlong(sharedContext.CGLContextObj); +} + +JNIEXPORT jlong JNICALL +Java_sun_java2d_opengl_CGLGraphicsConfigExt_getPixelFormat + (JNIEnv *env, jclass cls) +{ + return ptr_to_jlong(sharedPixelFormat.CGLPixelFormatObj); +} diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/SharedTextures.java b/src/java.desktop/share/classes/com/jetbrains/desktop/SharedTextures.java index aa8e96cd518f..8bf5e716b106 100644 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/SharedTextures.java +++ b/src/java.desktop/share/classes/com/jetbrains/desktop/SharedTextures.java @@ -32,12 +32,24 @@ public abstract class SharedTextures { public final static int METAL_TEXTURE_TYPE = 1; + public final static int OPENGL_TEXTURE_TYPE = 2; - public abstract int getTextureType(); + @Deprecated + public int getTextureType() { + GraphicsConfiguration gc = GraphicsEnvironment + .getLocalGraphicsEnvironment() + .getDefaultScreenDevice() + .getDefaultConfiguration(); + return getTextureType(gc); + } + + public abstract int getTextureType(GraphicsConfiguration gc); public final Image wrapTexture(GraphicsConfiguration gc, long texture) { return new TextureWrapperImage((img) -> createSurfaceManager(gc, img, texture)); } + public abstract long[] getOpenGLContextInfo(GraphicsConfiguration gc); + protected abstract SurfaceManager createSurfaceManager(GraphicsConfiguration gc, Image image, long texture); } diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/image/TextureWrapperSurfaceManager.java b/src/java.desktop/share/classes/com/jetbrains/desktop/image/TextureWrapperSurfaceManager.java index 2665ee61cb43..0e7088dfabdc 100644 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/image/TextureWrapperSurfaceManager.java +++ b/src/java.desktop/share/classes/com/jetbrains/desktop/image/TextureWrapperSurfaceManager.java @@ -33,7 +33,7 @@ public class TextureWrapperSurfaceManager extends SurfaceManager { - private final SurfaceData sd; + private SurfaceData sd; public TextureWrapperSurfaceManager(SurfaceData sd) { this.sd = sd; @@ -53,4 +53,10 @@ public SurfaceData restoreContents() { public ImageCapabilities getCapabilities(GraphicsConfiguration gc) { return new ImageCapabilities(true); } + + @Override + public synchronized void flush() { + sd.flush(); + sd = null; + } } diff --git a/src/java.desktop/share/classes/sun/java2d/opengl/OGLGraphicsConfigExt.java b/src/java.desktop/share/classes/sun/java2d/opengl/OGLGraphicsConfigExt.java new file mode 100644 index 000000000000..60bda257484b --- /dev/null +++ b/src/java.desktop/share/classes/sun/java2d/opengl/OGLGraphicsConfigExt.java @@ -0,0 +1,49 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.java2d.opengl; + +import java.awt.*; + +public class OGLGraphicsConfigExt { + public static long getSharedContext(GraphicsConfiguration gc) { + if (gc instanceof OGLGraphicsConfig) { + return getSharedContext(); + } + + throw new IllegalArgumentException("Not an OpenGL graphics config: " + gc); + } + + public static long getPixelFormat(GraphicsConfiguration gc) { + if (gc instanceof OGLGraphicsConfig oglGc) { + return getPixelFormat(oglGc.getNativeConfigInfo()); + } + + throw new IllegalArgumentException("Not an OpenGL graphics config: " + gc); + } + + private static native long getPixelFormat(long pConfigInfo); + private static native long getSharedContext(); +} diff --git a/src/java.desktop/share/classes/sun/java2d/opengl/OGLSurfaceDataExt.java b/src/java.desktop/share/classes/sun/java2d/opengl/OGLSurfaceDataExt.java new file mode 100644 index 000000000000..2735e006e8b5 --- /dev/null +++ b/src/java.desktop/share/classes/sun/java2d/opengl/OGLSurfaceDataExt.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.java2d.opengl; + +public class OGLSurfaceDataExt { + public static boolean initWithTexture(OGLSurfaceData surfaceData, long textureId) { + return OGLSurfaceDataExt.initWithTexture(surfaceData.getNativeOps(), textureId); + } + + public static void resetTextureId(OGLSurfaceData surfaceData) { + resetTextureId(surfaceData.getNativeOps()); + } + + private static native boolean initWithTexture(long pData, long textureId); + private static native void resetTextureId(long pData); +} diff --git a/src/java.desktop/share/native/common/java2d/opengl/OGLSurfaceDataExt.c b/src/java.desktop/share/native/common/java2d/opengl/OGLSurfaceDataExt.c new file mode 100644 index 000000000000..5f11933f4965 --- /dev/null +++ b/src/java.desktop/share/native/common/java2d/opengl/OGLSurfaceDataExt.c @@ -0,0 +1,144 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef HEADLESS + +#include "sun_java2d_opengl_OGLSurfaceDataExt.h" + +#include "jni_util.h" + +#include "OGLSurfaceData.h" + +void +OGLSD_DisposeTextureWrapper + (JNIEnv *env, SurfaceDataOps *ops) +{ + OGLSDOps *oglsdo = (OGLSDOps *)ops; + if (!oglsdo) { + J2dTraceLn(J2D_TRACE_ERROR, "OGLSD_DisposeTextureWrapper: oglsdo is null"); + return; + } + + if(oglsdo->textureID) { + oglsdo->textureID = 0; + J2dTraceLn(J2D_TRACE_VERBOSE, "OGLSD_DisposeTextureWrapper: texture %d is reset", oglsdo->textureID); + } else { + J2dTraceLn(J2D_TRACE_WARNING, "OGLSD_DisposeTextureWrapper: texture ID is 0"); + } + OGLSD_Dispose(env, ops); +} + +// from OGLSurfaceData.c +extern void +OGLSD_SetNativeDimensions +(JNIEnv *env, OGLSDOps *oglsdo, jint width, jint height); + +JNIEXPORT +jboolean JNICALL +Java_sun_java2d_opengl_OGLSurfaceDataExt_initWithTexture + (JNIEnv *env, jclass cls, jlong pData, jlong textureId) +{ + OGLSDOps *oglsdo = jlong_to_ptr(pData); + + if (oglsdo == NULL) { + J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSurfaceData_initWithTexture: ops are null"); + return JNI_FALSE; + } + + j2d_glBindTexture(GL_TEXTURE_2D, textureId); + GLenum error = j2d_glGetError(); + if (error != GL_NO_ERROR) { + J2dRlsTraceLn(J2D_TRACE_ERROR, + "OGLSurfaceData_initWithTexture: could not bind texture: id=%d error=%x", + textureId, error); + return JNI_FALSE; + } + + if (!j2d_glIsTexture(textureId)) { + J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSurfaceData_initWithTexture: textureId is not a valid texture id"); + j2d_glBindTexture(GL_TEXTURE_2D, 0); + return JNI_FALSE; + } + + GLsizei width, height; + j2d_glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + j2d_glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + j2d_glBindTexture(GL_TEXTURE_2D, 0); + + GLint texMax; + j2d_glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texMax); + if (width >= texMax || height >= texMax || width <= 0 || height <= 0) { + J2dRlsTraceLn(J2D_TRACE_ERROR, + "OGLSurfaceData_initWithTexture: wrong texture size %d x %d", + width, height); + return JNI_FALSE; + } + + oglsdo->xOffset = 0; + oglsdo->yOffset = 0; + oglsdo->width = width; + oglsdo->height = height; + oglsdo->textureID = textureId; + oglsdo->textureWidth = width; + oglsdo->textureHeight = height; + oglsdo->isOpaque = JNI_FALSE; + oglsdo->textureTarget = GL_TEXTURE_2D; + + // Add a custom disposer to reset the texture id before deleting it + oglsdo->sdOps.Dispose = OGLSD_DisposeTextureWrapper; + + GLuint fbobjectID, depthID; + if (!OGLSD_InitFBObject(&fbobjectID, &depthID, + oglsdo->textureID, oglsdo->textureTarget, + oglsdo->textureWidth, oglsdo->textureHeight)) + { + J2dRlsTraceLn(J2D_TRACE_ERROR, + "OGLSurfaceData_initWithTexture: could not init fbobject"); + return JNI_FALSE; + } + + oglsdo->drawableType = OGLSD_FBOBJECT; + oglsdo->fbobjectID = fbobjectID; + oglsdo->depthID = depthID; + + OGLSD_SetNativeDimensions(env, oglsdo, width, height); + + oglsdo->activeBuffer = GL_COLOR_ATTACHMENT0_EXT; + + J2dTraceLn(J2D_TRACE_VERBOSE, "OGLSurfaceData_initWithTexture: wrapped texture: w=%d h=%d id=%d", + width, height, textureId); + + return JNI_TRUE; +} + +JNIEXPORT void JNICALL +Java_sun_java2d_opengl_OGLSurfaceDataExt_resetTextureId + (JNIEnv *env, jclass cls, jlong pData) +{ + OGLSDOps *oglsdo = jlong_to_ptr(pData); + oglsdo->textureID = 0; +} + +#endif /* !HEADLESS */ diff --git a/src/java.desktop/unix/classes/sun/java2d/opengl/GLXGraphicsConfigExt.java b/src/java.desktop/unix/classes/sun/java2d/opengl/GLXGraphicsConfigExt.java new file mode 100644 index 000000000000..4211f6065249 --- /dev/null +++ b/src/java.desktop/unix/classes/sun/java2d/opengl/GLXGraphicsConfigExt.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.java2d.opengl; + +public class GLXGraphicsConfigExt { + public static long getFBConfig(GLXGraphicsConfig config) { + return getFBConfig(config.getNativeConfigInfo()); + } + + public native static long getAwtDisplay(); + + public native static long getSharedContext(); + + private static native long getFBConfig(long pNativeConfigInfo); +} diff --git a/src/java.desktop/unix/classes/sun/java2d/opengl/GLXTextureWrapperSurfaceData.java b/src/java.desktop/unix/classes/sun/java2d/opengl/GLXTextureWrapperSurfaceData.java new file mode 100644 index 000000000000..7753ee1f874e --- /dev/null +++ b/src/java.desktop/unix/classes/sun/java2d/opengl/GLXTextureWrapperSurfaceData.java @@ -0,0 +1,76 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.java2d.opengl; + +import sun.java2d.SurfaceData; + +import java.awt.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class GLXTextureWrapperSurfaceData extends GLXSurfaceData { + private final Image image; + + public GLXTextureWrapperSurfaceData(GLXGraphicsConfig gc, Image image, long textureId) { + super(null, gc, gc.getColorModel(TRANSLUCENT), RT_TEXTURE); + this.image = image; + + OGLRenderQueue rq = OGLRenderQueue.getInstance(); + AtomicBoolean success = new AtomicBoolean(false); + rq.lock(); + try { + OGLContext.setScratchSurface(gc); + rq.flushAndInvokeNow(() -> success.set(OGLSurfaceDataExt.initWithTexture(this, textureId))); + } finally { + rq.unlock(); + } + + if (!success.get()) { + throw new IllegalArgumentException("Failed to init the surface data"); + } + } + + @Override + public SurfaceData getReplacement() { + throw new UnsupportedOperationException(); + } + + @Override + public Rectangle getBounds() { + return getNativeBounds(); + } + + @Override + public Object getDestination() { + return image; + } + + @Override + public void flush() { + // reset the texture id first to avoid the texture deallocation + OGLSurfaceDataExt.resetTextureId(this); + super.flush(); + } +} diff --git a/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfig.c b/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfig.c index 7135c3ced02e..bcb159535d9e 100644 --- a/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfig.c +++ b/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfig.c @@ -435,6 +435,10 @@ GLXGC_FindBestVisual(JNIEnv *env, jint screen) return visualid; } +GLXContext GLXGC_GetSharedContext() { + return sharedContext; +} + /** * Creates a scratch pbuffer, which can be used to make a context current * for extension queries, etc. diff --git a/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfig.h b/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfig.h index cc903db9481c..73bcdbe84c5a 100644 --- a/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfig.h +++ b/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfig.h @@ -88,6 +88,7 @@ typedef struct _GLXCtxInfo { jboolean GLXGC_IsGLXAvailable(jboolean glxRecommended); VisualID GLXGC_FindBestVisual(JNIEnv *env, jint screen); +GLXContext GLXGC_GetSharedContext(); #endif /* HEADLESS */ diff --git a/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfigExt.c b/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfigExt.c new file mode 100644 index 000000000000..b1352ce07f8c --- /dev/null +++ b/src/java.desktop/unix/native/common/java2d/opengl/GLXGraphicsConfigExt.c @@ -0,0 +1,47 @@ +#include + +#include "jni_util.h" + +#include "sun_java2d_opengl_GLXGraphicsConfigExt.h" + +#include "GLXGraphicsConfig.h" + +#ifndef HEADLESS +#include +extern Display *awt_display; +#endif /* !HEADLESS */ + +JNIEXPORT jlong JNICALL +Java_sun_java2d_opengl_GLXGraphicsConfigExt_getSharedContext + (JNIEnv *env, jclass class) +{ +#ifndef HEADLESS + return ptr_to_jlong(GLXGC_GetSharedContext()); +#else + return 0; +#endif +} + +JNIEXPORT jlong JNICALL Java_sun_java2d_opengl_GLXGraphicsConfigExt_getAwtDisplay + (JNIEnv *env, jclass class) { +#ifndef HEADLESS + return ptr_to_jlong(awt_display); +#else + return 0; +#endif +} + +JNIEXPORT jlong JNICALL Java_sun_java2d_opengl_GLXGraphicsConfigExt_getFBConfig + (JNIEnv *env, jclass class, jlong pGlxNativeConfig) +{ +#ifndef HEADLESS + if (!pGlxNativeConfig) { + J2dTraceLn(J2D_TRACE_ERROR, "GLXGraphicsConfigExt_getPixelFormat: pGlxNativeConfig is nullptr"); + } + + GLXGraphicsConfigInfo *glxinfo = jlong_to_ptr(pGlxNativeConfig); + return ptr_to_jlong(glxinfo->fbconfig); +#else + return 0; +#endif +} \ No newline at end of file diff --git a/src/java.desktop/windows/classes/com/jetbrains/desktop/SharedTexturesService.java b/src/java.desktop/windows/classes/com/jetbrains/desktop/SharedTexturesService.java new file mode 100644 index 000000000000..0529eb092fb8 --- /dev/null +++ b/src/java.desktop/windows/classes/com/jetbrains/desktop/SharedTexturesService.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.jetbrains.desktop; + +import com.jetbrains.desktop.image.TextureWrapperImage; +import com.jetbrains.desktop.image.TextureWrapperSurfaceManager; +import com.jetbrains.exported.JBRApi; +import sun.awt.image.SurfaceManager; +import sun.java2d.SurfaceData; + +import sun.java2d.opengl.*; + +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.Image; + +@JBRApi.Service +@JBRApi.Provides("SharedTextures") +public class SharedTexturesService extends SharedTextures { + @Override + public int getTextureType(GraphicsConfiguration gc) { + if (gc instanceof WGLGraphicsConfig) { + return OPENGL_TEXTURE_TYPE; + } + + return 0; + } + + @Override + public long[] getOpenGLContextInfo(GraphicsConfiguration gc) { + if (gc instanceof WGLGraphicsConfig wglGraphicsConfig) { + return new long[] { + WGLGraphicsConfigExt.getSharedOpenGLContext(), + WGLGraphicsConfigExt.getSharedOpenGLPixelFormat(wglGraphicsConfig), + }; + } + + throw new UnsupportedOperationException("Unsupported graphics configuration: " + gc); + } + + @Override + public SurfaceManager createSurfaceManager(GraphicsConfiguration gc, Image image, long texture) { + SurfaceData sd; + if (gc instanceof WGLGraphicsConfig wglGraphicsConfig) { + sd = new WGLTextureWrapperSurfaceData(wglGraphicsConfig, image, texture); + } else { + throw new UnsupportedOperationException("Unsupported graphics configuration: " + gc); + } + + return new TextureWrapperSurfaceManager(sd); + } +} diff --git a/src/java.desktop/windows/classes/sun/java2d/opengl/WGLGraphicsConfigExt.java b/src/java.desktop/windows/classes/sun/java2d/opengl/WGLGraphicsConfigExt.java new file mode 100644 index 000000000000..870c6a422ed2 --- /dev/null +++ b/src/java.desktop/windows/classes/sun/java2d/opengl/WGLGraphicsConfigExt.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.java2d.opengl; + +import java.awt.*; + +public class WGLGraphicsConfigExt { + public static native long getSharedOpenGLContext(); + public static long getSharedOpenGLPixelFormat(WGLGraphicsConfig gc) { + return getSharedOpenGLPixelFormat(gc.getNativeConfigInfo()); + } + + private static native long getSharedOpenGLPixelFormat(long pCongigInfo); +} diff --git a/src/java.desktop/windows/classes/sun/java2d/opengl/WGLTextureWrapperSurfaceData.java b/src/java.desktop/windows/classes/sun/java2d/opengl/WGLTextureWrapperSurfaceData.java new file mode 100644 index 000000000000..52c839a08a27 --- /dev/null +++ b/src/java.desktop/windows/classes/sun/java2d/opengl/WGLTextureWrapperSurfaceData.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.java2d.opengl; + +import sun.java2d.SurfaceData; + +import java.awt.*; +import java.awt.image.ColorModel; +import java.util.concurrent.atomic.AtomicBoolean; + +public class WGLTextureWrapperSurfaceData extends WGLSurfaceData { + private final Image image; + + public WGLTextureWrapperSurfaceData(WGLGraphicsConfig gc, Image image, long textureId) { + super(null, gc, ColorModel.getRGBdefault(), RT_TEXTURE); + this.image = image; + + OGLRenderQueue rq = OGLRenderQueue.getInstance(); + AtomicBoolean success = new AtomicBoolean(false); + rq.lock(); + try { + OGLContext.setScratchSurface(gc); + rq.flushAndInvokeNow(() -> success.set(OGLSurfaceDataExt.initWithTexture(this, textureId))); + } finally { + rq.unlock(); + } + + if (!success.get()) { + throw new IllegalArgumentException("Failed to init the surface data"); + } + } + + @Override + public SurfaceData getReplacement() { + throw new UnsupportedOperationException(); + } + + @Override + public Rectangle getBounds() { + return getNativeBounds(); + } + + @Override + public Object getDestination() { + return null; + } + + @Override + public void flush() { + // reset the texture id first to avoid the texture deallocation + OGLSurfaceDataExt.resetTextureId(this); + super.flush(); + } +} diff --git a/src/java.desktop/windows/native/libawt/java2d/opengl/WGLGraphicsConfigJbrApi.c b/src/java.desktop/windows/native/libawt/java2d/opengl/WGLGraphicsConfigJbrApi.c new file mode 100644 index 000000000000..ff82bdad61db --- /dev/null +++ b/src/java.desktop/windows/native/libawt/java2d/opengl/WGLGraphicsConfigJbrApi.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2004, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include + +#include "jni.h" +#include "jni_util.h" +#include "jlong.h" + +#include "WGLGraphicsConfig.h" +#include "sun_java2d_opengl_WGLGraphicsConfigExt.h" + +extern HGLRC sharedContext; + +JNIEXPORT jlong JNICALL +Java_sun_java2d_opengl_WGLGraphicsConfigExt_getSharedOpenGLContext(JNIEnv *env, + jclass wglcl, + jlong pConfigInfo) +{ + return (jlong)sharedContext; +} + +JNIEXPORT jlong JNICALL +Java_sun_java2d_opengl_WGLGraphicsConfigExt_getSharedOpenGLPixelFormat(JNIEnv *env, + jclass wglcl, + jlong pConfigInfo) +{ + WGLGraphicsConfigInfo *wglinfo = + (WGLGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo); + + J2dTraceLn(J2D_TRACE_VERBOSE, "OGLGraphicsConfigJbrApi_getPixelFormat"); + + if (wglinfo == NULL) { + J2dRlsTraceLn(J2D_TRACE_ERROR, + "OGLGraphicsConfigJbrApi_getPixelFormat: config info is null"); + return 0; + } + + return wglinfo->pixfmt; +} diff --git a/test/jdk/jb/SharedTextures/SharedTexturesTest.java b/test/jdk/jb/SharedTextures/SharedTexturesTest.java index e20548e9d0ab..86e0f015c8b9 100644 --- a/test/jdk/jb/SharedTextures/SharedTexturesTest.java +++ b/test/jdk/jb/SharedTextures/SharedTexturesTest.java @@ -3,6 +3,7 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; import java.awt.image.VolatileImage; import java.io.File; import java.io.IOException; @@ -20,49 +21,93 @@ * the same content. * @library /test/lib * @compile --add-exports java.desktop/com.jetbrains.desktop=ALL-UNNAMED SharedTexturesTest.java - * @run main/othervm/native -Dsun.java2d.uiScale=1 -Dsun.java2d.metal=True --add-exports java.desktop/com.jetbrains.desktop=ALL-UNNAMED SharedTexturesTest + * @run main/othervm/native -Dsun.java2d.uiScale=1 -Dsun.java2d.metal=True --enable-native-access=ALL-UNNAMED --add-exports java.desktop/com.jetbrains.desktop=ALL-UNNAMED SharedTexturesTest + * @run main/othervm/native -Dsun.java2d.uiScale=1 -Dsun.java2d.opengl=True --enable-native-access=ALL-UNNAMED --add-exports java.desktop/com.jetbrains.desktop=ALL-UNNAMED SharedTexturesTest * @requires (os.family=="mac") */ + +/** + * @test + * @key headful + * @summary The test creates a BufferedImage and makes a texture from its content. + * The texture gets wrapped into a TextureWrapperImage image by SharedTextures JBR API service. + * The TextureWrapperImage is copied into a BufferedImage and VolatileImage and expects that all images have + * the same content. + * @library /test/lib + * @compile --add-exports java.desktop/com.jetbrains.desktop=ALL-UNNAMED SharedTexturesTest.java + * @run main/othervm/native -Dsun.java2d.uiScale=1 -Dsun.java2d.opengl=True --enable-native-access=ALL-UNNAMED --add-exports java.desktop/com.jetbrains.desktop=ALL-UNNAMED SharedTexturesTest + * @requires (os.family=="windows") + */ + +/** + * @test + * @key headful + * @summary The test creates a BufferedImage and makes a texture from its content. + * The texture gets wrapped into a TextureWrapperImage image by SharedTextures JBR API service. + * The TextureWrapperImage is copied into a BufferedImage and VolatileImage and expects that all images have + * the same content. + * @library /test/lib + * @compile --add-exports java.desktop/com.jetbrains.desktop=ALL-UNNAMED SharedTexturesTest.java + * @run main/othervm/native -Dsun.java2d.uiScale=1 -Dsun.java2d.opengl=True --enable-native-access=ALL-UNNAMED --add-exports java.desktop/com.jetbrains.desktop=ALL-UNNAMED SharedTexturesTest + * @requires (os.family=="linux") + */ + public class SharedTexturesTest { static { System.loadLibrary("SharedTexturesTest"); } - public static void main(String[] args) throws IOException { - BufferedImage originalImage = createImage(); - byte[] bytes = getPixelData(originalImage); + public static void main(String[] args) throws IOException, InterruptedException { + GraphicsConfiguration gc = GraphicsEnvironment + .getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); + + SharedTextures sharedTextures = new SharedTexturesService(); + int textureType = sharedTextures.getTextureType(gc); + if ("True".equals(System.getProperty("sun.java2d.opengl"))) { + Asserts.assertEquals(textureType, SharedTextures.OPENGL_TEXTURE_TYPE); + } else if ("True".equals(System.getProperty("sun.java2d.metal"))) { + Asserts.assertEquals(textureType, SharedTextures.METAL_TEXTURE_TYPE); + } else { + throw new RuntimeException("The rendering pipeline has to be specified explicitly"); + } - SharedTextures sharedTexturesService = new SharedTexturesService(); - Asserts.assertEquals(sharedTexturesService.getTextureType(), SharedTexturesService.METAL_TEXTURE_TYPE); + BufferedImage originalImage = createImage(); + boolean flipY = textureType == SharedTextures.OPENGL_TEXTURE_TYPE; + byte[] bytes = getPixelData(originalImage, TexturePixelLayout.get(textureType), flipY); BufferedImage bufferedImageContent; BufferedImage volatileImageContent; + if (textureType == SharedTextures.OPENGL_TEXTURE_TYPE) { + setSharedContextInfo(sharedTextures.getOpenGLContextInfo(gc)); + } + initNative(textureType); long textureId = createTexture(bytes, originalImage.getWidth(), originalImage.getHeight()); + try { - GraphicsConfiguration gc = GraphicsEnvironment - .getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); - Image textureImage = sharedTexturesService.wrapTexture(gc, textureId); + Image textureImage = sharedTextures.wrapTexture(gc, textureId); - // Create a BufferedImage containing a copy of textureImage + // Render the shared texture image onto a BufferedImage bufferedImageContent = new BufferedImage( textureImage.getWidth(null), textureImage.getHeight(null), BufferedImage.TYPE_INT_ARGB); copyImage(textureImage, bufferedImageContent); - // Create a VolatileImage containing a copy of textureImage and dump it to a BufferedImage + // Render the shared texture image onto a VolatileImage and copy it to another BufferedImage + // to check the content later volatileImageContent = new BufferedImage( textureImage.getWidth(null), textureImage.getHeight(null), BufferedImage.TYPE_INT_ARGB); - VolatileImage volatileImage = gc.createCompatibleVolatileImage( + VolatileImage volatileImageTarget = gc.createCompatibleVolatileImage( textureImage.getWidth(null), textureImage.getHeight(null), Transparency.TRANSLUCENT); int attempts = 10; do { - copyImage(textureImage, volatileImage); - copyImage(volatileImage, volatileImageContent); + copyImage(textureImage, volatileImageTarget); + copyImage(volatileImageTarget, volatileImageContent); attempts--; - } while (volatileImage.contentsLost() && attempts > 0); + } while (volatileImageTarget.contentsLost() && attempts > 0); Asserts.assertNotEquals(attempts, 0, "Failed to draw the VolatileImage"); } finally { disposeTexture(textureId); + releaseContext(); } try { @@ -77,6 +122,8 @@ public static void main(String[] args) throws IOException { saveImage(originalImage, "original_image.png"); saveImage(bufferedImageContent, "buffered_image.png"); saveImage(volatileImageContent, "volatile_image.png"); + saveImage(imagesDiff(originalImage, bufferedImageContent), "buffered_image_diff.png"); + saveImage(imagesDiff(originalImage, volatileImageContent), "volatile_image_diff.png"); throw e; } } @@ -106,6 +153,10 @@ private static BufferedImage createImage() { g.setColor(new Color(255, 0, 0, 128)); g.fillOval(0, 0, width, height); + + g.setColor(Color.BLUE); + g.drawLine(0, 0, width, height - 50); + g.dispose(); return bufferedImage; @@ -117,19 +168,34 @@ static void saveImage(BufferedImage image, String name) throws IOException { ImageIO.write(image, formatName, outputFile); } - private native static long createTexture(byte[] data, int width, int height); - - private native static void disposeTexture(long textureId); + private static class TexturePixelLayout { + int r, g, b, a; // color component indexes + private TexturePixelLayout(int r, int g, int b, int a) {this.r = r; this.g = g; this.b = b; this.a = a;} + final static TexturePixelLayout GL_RGBA_UINT_8_8_8_8 = new TexturePixelLayout(3, 2, 1, 0); + final static TexturePixelLayout MTL_BGRA8Unorm = new TexturePixelLayout(2, 1, 0, 3); + + static TexturePixelLayout get(int textureType) { + return switch (textureType) { + case SharedTextures.OPENGL_TEXTURE_TYPE -> TexturePixelLayout.GL_RGBA_UINT_8_8_8_8; + case SharedTextures.METAL_TEXTURE_TYPE -> TexturePixelLayout.MTL_BGRA8Unorm; + default -> throw new RuntimeException("Unexpected texture type: " + textureType); + }; + } + } - private static byte[] getPixelData(BufferedImage image) { + private static byte[] getPixelData(BufferedImage image, TexturePixelLayout pixelLayout, boolean flipY) { byte[] rawPixels = new byte[image.getWidth() * image.getHeight() * 4]; int[] argbPixels = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth()); + ColorModel cm = image.getColorModel(); for (int i = 0; i < argbPixels.length; i++) { + int x = i % image.getWidth(); + int y = flipY ? image.getHeight() - (i / image.getWidth()) - 1 : i / image.getWidth(); + int targetIndex = (x + y * image.getWidth()) * 4; int argb = argbPixels[i]; - rawPixels[i * 4] = (byte) (argb & 0xFF); - rawPixels[i * 4 + 1] = (byte) ((argb >> 8) & 0xFF); - rawPixels[i * 4 + 2] = (byte) ((argb >> 16) & 0xFF); - rawPixels[i * 4 + 3] = (byte) ((argb >> 24) & 0xFF); + rawPixels[targetIndex + pixelLayout.r] = (byte) cm.getRed(argb); + rawPixels[targetIndex + pixelLayout.g] = (byte) cm.getGreen(argb); + rawPixels[targetIndex + pixelLayout.b] = (byte) cm.getBlue(argb); + rawPixels[targetIndex + pixelLayout.a] = (byte) cm.getAlpha(argb); } return rawPixels; @@ -147,4 +213,29 @@ private static int countImageDiff(BufferedImage lhs, BufferedImage rhs) { return count; } + + private static BufferedImage imagesDiff(BufferedImage lhs, BufferedImage rhs) { + int width = Math.min(lhs.getWidth(), rhs.getWidth()); + int height = Math.min(lhs.getHeight(), rhs.getHeight()); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + ColorModel lhsCM = lhs.getColorModel(); + ColorModel rhsCM = rhs.getColorModel(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + result.setRGB(x, y, lhsCM.getRGB(lhs.getRGB(x, y)) ^ rhsCM.getRGB(rhs.getRGB(x, y))); + } + } + return result; + } + + private native static void initNative(int textureType); + + private native static void setSharedContextInfo(long[] sharedContextInfo); + + private native static long createTexture(byte[] data, int width, int height); + + private native static void disposeTexture(long textureId); + + private native static void releaseContext(); } diff --git a/test/jdk/jb/SharedTextures/libSharedTexturesTest.c b/test/jdk/jb/SharedTextures/libSharedTexturesTest.c new file mode 100644 index 000000000000..aded7237721a --- /dev/null +++ b/test/jdk/jb/SharedTextures/libSharedTexturesTest.c @@ -0,0 +1,280 @@ +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include + +#ifdef LINUX +#include +#include +#include +#endif + +static int gTextureType = -1; + +static const int METAL_TEXTURE_TYPE = 1; +static const int OPENGL_TEXTURE_TYPE = 2; + +static void check_gl_error(JNIEnv *env, const char* msgPrefix) { + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + char msg[256]; + snprintf(msg, sizeof(msg), "%s: OpenGL error %d", msgPrefix, err); + jclass excCls = (*env)->FindClass(env, "java/lang/RuntimeException"); + (*env)->ThrowNew(env, excCls, msg); + } +} + +#ifdef _WIN32 +static HWND gHwnd = 0; +static HGLRC gSharedContext = 0; +static int gPixelFormat = 0; + +static HDC dc = 0; +static HGLRC gGLContext = 0; + +static int initOpenGL() { + gHwnd = CreateWindowEx(0, "STATIC", "Hidden", WS_POPUP, 0,0,1,1, NULL, NULL, GetModuleHandle(NULL), NULL); + if (gHwnd == 0) { + return FALSE; + } + + dc = GetDC(gHwnd); + if (!dc) { + DestroyWindow(gHwnd); + return FALSE; + } + + if (!SetPixelFormat(dc, gPixelFormat, 0)) { + ReleaseDC(0, dc); + DestroyWindow(gHwnd); + return FALSE; + } + + gGLContext = wglCreateContext(dc); + if (!gGLContext) { + ReleaseDC(0, dc); + DestroyWindow(gHwnd); + return FALSE; + } + + if (!wglShareLists(gSharedContext, gGLContext)) { + ReleaseDC(0, dc); + DestroyWindow(gHwnd); + return FALSE; + } + + if (!wglMakeCurrent(dc, gGLContext)) { + wglDeleteContext(gGLContext); + gGLContext = 0; + ReleaseDC(0, dc); + DestroyWindow(gHwnd); + return FALSE; + } + + wglMakeCurrent(0, 0); + return 1; +} + +static int makeContextCurrent(int isCurrent) { + if (isCurrent) { + return wglMakeCurrent(dc, gGLContext); + } + return wglMakeCurrent(0, 0); +} + +JNIEXPORT void JNICALL Java_SharedTexturesTest_setSharedContextInfo + (JNIEnv *env, jclass clazz, jlongArray sharedContextInfo) { + jsize length = (*env)->GetArrayLength(env, sharedContextInfo); + if (length != 2) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"), + "Unexpected shared context info size"); + } + + jlong contextInfo[2]; + (*env)->GetLongArrayRegion(env, sharedContextInfo, 0, length, contextInfo); + gSharedContext = (HGLRC)contextInfo[0]; + gPixelFormat = (int)contextInfo[1]; +} + +JNIEXPORT void JNICALL Java_SharedTexturesTest_releaseContext + (JNIEnv *env, jclass clazz) { + if (gTextureType == OPENGL_TEXTURE_TYPE) { + if (gGLContext) { + wglMakeCurrent(0, 0); + wglDeleteContext(gGLContext); + gGLContext = 0; + } + + if (dc) { + ReleaseDC(0, dc); + DestroyWindow(gHwnd); + } + } +} + +#endif + +#ifdef LINUX + +static Display *gDisplay = NULL; +static GLXContext gSharedContext = 0; +static GLXFBConfig gFBConfig = 0; + +static GLXPbuffer gPbuffer = 0; +static GLXContext gGLContext = 0; + +static int initOpenGL() { + int pbAttribs[] = { + GLX_PBUFFER_WIDTH, 1000, + GLX_PBUFFER_HEIGHT, 1000, + None + }; + + gPbuffer = glXCreatePbuffer(gDisplay, gFBConfig, pbAttribs); + gGLContext = glXCreateNewContext(gDisplay, gFBConfig, GLX_RGBA_TYPE, gSharedContext, /* direct = */ GL_TRUE); + + if (!gGLContext) { + printf("Failed to glXCreateNewContext: %d\n", glGetError()); + return 0; + } + + if (!glXMakeCurrent(gDisplay, gPbuffer, gGLContext)) { + printf("Failed to glXMakeCurrent: %d\n", glGetError()); + glXDestroyContext(gDisplay, gGLContext); + gGLContext = 0; + return 0; + } + + glXMakeCurrent(gDisplay, None, NULL); + return 1; +} + +static int makeContextCurrent(int isCurrent) { + if (isCurrent) { + return glXMakeCurrent(gDisplay, gPbuffer, gGLContext); + } + return glXMakeCurrent(gDisplay, None, NULL); +} + +JNIEXPORT void JNICALL Java_SharedTexturesTest_setSharedContextInfo + (JNIEnv *env, jclass clazz, jlongArray sharedContextInfo) { + jsize length = (*env)->GetArrayLength(env, sharedContextInfo); + if (length != 3) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"), + "Unexpected shared context info size"); + } + + jlong contextInfo[3]; + (*env)->GetLongArrayRegion(env, sharedContextInfo, 0, length, contextInfo); + + gSharedContext = (GLXContext)contextInfo[0]; + gDisplay = (Display*)contextInfo[1]; + gFBConfig = (GLXFBConfig)contextInfo[2]; +} + +JNIEXPORT void JNICALL Java_SharedTexturesTest_releaseContext + (JNIEnv *env, jclass clazz) { + if (gTextureType == OPENGL_TEXTURE_TYPE) { + if (gGLContext) { + glXMakeCurrent(gDisplay, None, NULL); + glXDestroyContext(gDisplay, gGLContext); + gGLContext = 0; + } + + if (gPbuffer) { + glXDestroyPbuffer(gDisplay, gPbuffer); + gPbuffer = 0; + } + } +} + +#endif + +JNIEXPORT void JNICALL Java_SharedTexturesTest_initNative + (JNIEnv *env, jclass clazz, jint textureType) { + if (textureType != OPENGL_TEXTURE_TYPE) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"), + "Unsupported texture type"); + return; + } + + if (!initOpenGL()) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"), + "Failed to init OpenGL"); + return; + } + + gTextureType = textureType; +} + +JNIEXPORT jlong JNICALL Java_SharedTexturesTest_createTexture + (JNIEnv *env, jclass clazz, jbyteArray byteArray, jint width, jint height) { + if (!makeContextCurrent(1)) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"), + "SharedTexturesTest: can't make the context current"); + return 0; + } + + if (gTextureType == -1) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"), + "SharedTexturesTest: native is not initialized"); + return 0; + } + + if (gTextureType != OPENGL_TEXTURE_TYPE) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"), + "Unsupported texture type"); + return 0; + } + + jsize length = (*env)->GetArrayLength(env, byteArray); + jbyte *pixels = (*env)->GetByteArrayElements(env, byteArray, NULL); + + GLuint texId = 0; + glGenTextures(1, &texId); + check_gl_error(env, "glGenTextures"); + glBindTexture(GL_TEXTURE_2D, texId); + check_gl_error(env, "glBindTexture"); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + check_gl_error(env, "glTexParameteri GL_TEXTURE_MAG_FILTER"); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + check_gl_error(env, "glTexParameteri GL_TEXTURE_MIN_FILTER"); + + #ifndef GL_CLAMP_TO_EDGE + #define GL_CLAMP_TO_EDGE 0x812F + #endif + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + +#ifndef GL_UNSIGNED_INT_8_8_8_8 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#endif + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, + GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, (const void*)pixels); + check_gl_error(env, "glTexImage2D"); + + (*env)->ReleaseByteArrayElements(env, byteArray, pixels, JNI_ABORT); + glBindTexture(GL_TEXTURE_2D, 0); + glFinish(); + check_gl_error(env, "glFinish"); + + makeContextCurrent(0); + + return (jlong)texId; +} + +JNIEXPORT void JNICALL Java_SharedTexturesTest_disposeTexture + (JNIEnv *env, jclass clazz, jlong texture) { + if (gTextureType == OPENGL_TEXTURE_TYPE) { + GLuint texId = (GLuint)texture; + glDeleteTextures(1, &texId); + } +} diff --git a/test/jdk/jb/SharedTextures/libSharedTexturesTest.m b/test/jdk/jb/SharedTextures/libSharedTexturesTest.m index 53e07e0e182b..271d6ee526f1 100644 --- a/test/jdk/jb/SharedTextures/libSharedTexturesTest.m +++ b/test/jdk/jb/SharedTextures/libSharedTexturesTest.m @@ -1,7 +1,50 @@ +#define GL_SILENCE_DEPRECATION + +#import + #import + #import -#include +#import +#import +#import +#import + +static int METAL_TEXTURE_TYPE = 1; +static int OPENGL_TEXTURE_TYPE = 2; + +static int gTextureType = -1; + +static CGLContextObj gOpenGLContext = 0; + +static CGLContextObj gSharedContext = 0; +static CGLPixelFormatObj gPixelFormat = 0; + +static void throw(JNIEnv *env, const char* msg) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"), msg); +} + +static const char* gl_error_string(GLenum err) { + switch (err) { + case GL_NO_ERROR: return "GL_NO_ERROR"; + case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; + case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; + case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; + + default: return "Unknown GL error"; + } +} + +static void check_gl_error(JNIEnv *env, const char* msgPrefix) { + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + char msg[256]; + snprintf(msg, sizeof(msg), "%s: OpenGL error %d (%s)", msgPrefix, err, gl_error_string(err)); + throw(env, msg); + } +} static id createMTLTextureFromNSData(id device, NSData *textureData, NSUInteger width, NSUInteger height) { @@ -31,8 +74,7 @@ return texture; } -JNIEXPORT jlong JNICALL Java_SharedTexturesTest_createTexture - (JNIEnv *env, jclass clazz, jbyteArray byteArray, jint width, jint height) { +static jlong createTextureMTL(JNIEnv *env, jbyteArray byteArray, jint width, jint height) { @autoreleasepool { id device = MTLCreateSystemDefaultDevice(); if (!device) { @@ -50,10 +92,107 @@ } } +static jlong createTextureOpenGL(JNIEnv *env, jbyteArray byteArray, jint width, jint height) { + CGLSetCurrentContext(gSharedContext); + + jbyte *pixels = (*env)->GetByteArrayElements(env, byteArray, NULL); + + GLuint texId = 0; + glGenTextures(1, &texId); + check_gl_error(env, "glGenTextures"); + glBindTexture(GL_TEXTURE_2D, texId); + check_gl_error(env, "glBindTexture"); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + check_gl_error(env, "glTexParameteri GL_TEXTURE_MAG_FILTER"); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + check_gl_error(env, "glTexParameteri GL_TEXTURE_MIN_FILTER"); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + check_gl_error(env, "glTexParameteri GL_TEXTURE_WRAP_S GL_CLAMP_TO_EDGE"); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + check_gl_error(env, "glTexParameteri GL_TEXTURE_WRAP_T GL_CLAMP_TO_EDGE"); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, + GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, (const void*)pixels); + check_gl_error(env, "glTexImage2D"); + + (*env)->ReleaseByteArrayElements(env, byteArray, pixels, JNI_ABORT); + + glBindTexture(GL_TEXTURE_2D, 0); + + glFinish(); + check_gl_error(env, "glFinish"); + + CGLSetCurrentContext(0); + + return texId; +} + +JNIEXPORT void JNICALL Java_SharedTexturesTest_setSharedContextInfo + (JNIEnv *env, jclass clazz, jlongArray sharedContextInfo) { + jsize length = (*env)->GetArrayLength(env, sharedContextInfo); + if (length != 2) { + (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"), + "Unexpected shared context info size"); + } + + jlong contextInfo[2]; + (*env)->GetLongArrayRegion(env, sharedContextInfo, 0, length, contextInfo); + gSharedContext = (CGLContextObj)contextInfo[0]; + gPixelFormat = (CGLPixelFormatObj)contextInfo[1]; +} + +JNIEXPORT void JNICALL Java_SharedTexturesTest_initNative + (JNIEnv *env, jclass clazz, jint textureType) { + gTextureType = textureType; + if (textureType == METAL_TEXTURE_TYPE) { + // nothing + } else if (textureType == OPENGL_TEXTURE_TYPE) { + CGLError error = CGLCreateContext(gPixelFormat, gSharedContext, &gOpenGLContext); + if (error != kCGLNoError || !gOpenGLContext) { + throw(env, "Failed to init OpenGL context"); + } + } else { + throw(env, "Unknown texture type"); + } +} + +JNIEXPORT jlong JNICALL Java_SharedTexturesTest_createTexture + (JNIEnv *env, jclass clazz, jbyteArray byteArray, jint width, jint height) { + if (gTextureType == METAL_TEXTURE_TYPE) { + return createTextureMTL(env, byteArray, width, height); + } else if (gTextureType == OPENGL_TEXTURE_TYPE) { + return createTextureOpenGL(env, byteArray, width, height); + } + + throw(env, "Unknown texture type"); + return 0; +} + JNIEXPORT void JNICALL Java_SharedTexturesTest_disposeTexture - (JNIEnv *env, jclass clazz, jlong pTexture) { - id texture = (__bridge id ) (void *) pTexture; - if (texture != nil) { - [texture release]; + (JNIEnv *env, jclass clazz, jlong texture) { + if (gTextureType == METAL_TEXTURE_TYPE) { + id mtlTexture = (__bridge id ) (void *) texture; + if (mtlTexture != nil) { + [mtlTexture release]; + } + } else if (gTextureType == OPENGL_TEXTURE_TYPE) { + GLuint texId = (GLuint)texture; + glDeleteTextures(1, &texId); + } +} + +JNIEXPORT void JNICALL Java_SharedTexturesTest_releaseContext + (JNIEnv *env, jclass clazz) { + if (gTextureType == OPENGL_TEXTURE_TYPE) { + if (gOpenGLContext != 0) { + CGLSetCurrentContext(0); + CGLDestroyContext(gOpenGLContext); + gOpenGLContext = 0; + } } } +