mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2026-01-27 02:40:52 +01:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d8c5d7ebe |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,4 +23,3 @@ NashornProfile.txt
|
||||
/.cproject
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
/jbr-api/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
1.0.0
|
||||
13
jb/project/idea-project-files/jetbrains.api.iml
Normal file
13
jb/project/idea-project-files/jetbrains.api.iml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/src/jetbrains.api">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/jetbrains.api/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/jetbrains.api/templates" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/jdk.iml" filepath="$PROJECT_DIR$/.idea/jdk.iml" />
|
||||
###MODULE_IMLS###
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/jetbrains.api.iml" filepath="$PROJECT_DIR$/.idea/jetbrains.api.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/test.iml" filepath="$PROJECT_DIR$/.idea/test.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
|
||||
18
jb/project/tools/common/scripts/build-jbr-api.sh
Normal file
18
jb/project/tools/common/scripts/build-jbr-api.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/bin//bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# $1 - Boot JDK
|
||||
# $2 - JBR part of API version
|
||||
|
||||
cd "`dirname "$0"`/../../../../.."
|
||||
PWD="`pwd`"
|
||||
CONF="$PWD/build/jbr-api.conf"
|
||||
./configure --with-debug-level=release --with-boot-jdk=$1 || exit $?
|
||||
make jbr-api CONF=release MAKEOVERRIDES= "JBR_API_CONF_FILE=$CONF" JBR_API_JBR_VERSION=$2 || exit $?
|
||||
. $CONF || exit $?
|
||||
echo "##teamcity[buildNumber '$VERSION']"
|
||||
cp "$JAR" ./jbr-api-${VERSION}.jar || exit $?
|
||||
cp "$SOURCES_JAR" ./jbr-api-${VERSION}-sources.jar || exit $?
|
||||
echo "##teamcity[publishArtifacts '$PWD/jbr-api-${VERSION}.jar']"
|
||||
echo "##teamcity[publishArtifacts '$PWD/jbr-api-${VERSION}-sources.jar']"
|
||||
@@ -161,7 +161,8 @@ if [ $do_maketest -eq 1 ]; then
|
||||
JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-aarch64-b${build_number}
|
||||
echo Creating "$JBRSDK_TEST" ...
|
||||
[ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java
|
||||
make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test"
|
||||
tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
|
||||
[ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz"
|
||||
gzip "$JBRSDK_TEST".tar || do_exit $?
|
||||
|
||||
@@ -169,7 +169,8 @@ if [ $do_maketest -eq 1 ]; then
|
||||
JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-x64-b${build_number}
|
||||
echo Creating "$JBRSDK_TEST" ...
|
||||
[ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java
|
||||
make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test"
|
||||
tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
|
||||
[ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz"
|
||||
gzip "$JBRSDK_TEST".tar || do_exit $?
|
||||
|
||||
@@ -137,7 +137,8 @@ if [ $do_maketest -eq 1 ]; then
|
||||
JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-x86-b${build_number}
|
||||
echo Creating "$JBRSDK_TEST" ...
|
||||
[ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java
|
||||
make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test"
|
||||
tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
|
||||
[ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz"
|
||||
gzip "$JBRSDK_TEST".tar || do_exit $?
|
||||
|
||||
@@ -160,7 +160,8 @@ if [ $do_maketest -eq 1 ]; then
|
||||
JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-osx-test-${architecture}-b${build_number}
|
||||
echo Creating "$JBRSDK_TEST" ...
|
||||
[ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java
|
||||
make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test"
|
||||
[ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz"
|
||||
COPYFILE_DISABLE=1 tar -pczf "$JBRSDK_TEST".tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
|
||||
fi
|
||||
|
||||
@@ -104,13 +104,13 @@ esac
|
||||
if [ -z "${INC_BUILD:-}" ]; then
|
||||
do_configure || do_exit $?
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
else
|
||||
make LOG=info CONF=$RELEASE_NAME clean images || do_exit $?
|
||||
fi
|
||||
else
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
else
|
||||
make LOG=info CONF=$RELEASE_NAME images || do_exit $?
|
||||
fi
|
||||
|
||||
@@ -97,13 +97,13 @@ esac
|
||||
if [ -z "${INC_BUILD:-}" ]; then
|
||||
do_configure || do_exit $?
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
else
|
||||
make LOG=info CONF=$RELEASE_NAME clean images || do_exit $?
|
||||
fi
|
||||
else
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
else
|
||||
make LOG=info CONF=$RELEASE_NAME images || do_exit $?
|
||||
fi
|
||||
|
||||
@@ -92,13 +92,13 @@ esac
|
||||
if [ -z "${INC_BUILD:-}" ]; then
|
||||
do_configure || do_exit $?
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
else
|
||||
make LOG=info CONF=$RELEASE_NAME clean images || do_exit $?
|
||||
fi
|
||||
else
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
|
||||
else
|
||||
make LOG=info CONF=$RELEASE_NAME images || do_exit $?
|
||||
fi
|
||||
|
||||
@@ -51,6 +51,7 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk
|
||||
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-aarch64-b$build_number
|
||||
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $?
|
||||
echo Creating $JBRSDK_TEST.tar.gz ...
|
||||
/usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
|
||||
fi
|
||||
@@ -51,6 +51,7 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk
|
||||
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-x64-b$build_number
|
||||
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $?
|
||||
echo Creating $JBRSDK_TEST.tar.gz ...
|
||||
/usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
|
||||
fi
|
||||
@@ -47,6 +47,7 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk
|
||||
|
||||
if [ $do_maketest -eq 1 ]; then
|
||||
JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-x86-b$build_number
|
||||
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $?
|
||||
echo Creating $JBRSDK_TEST.tar.gz ...
|
||||
/usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $BASE_DIR --exclude='test/jdk/demos' test || do_exit $?
|
||||
fi
|
||||
@@ -100,7 +100,6 @@ $(eval $(call SetupJavaCompilation, $(MODULE), \
|
||||
BIN := $(if $($(MODULE)_BIN), $($(MODULE)_BIN), $(JDK_OUTPUTDIR)/modules), \
|
||||
HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \
|
||||
CREATE_API_DIGEST := true, \
|
||||
PROCESS_JBR_API := true, \
|
||||
CLEAN := $(CLEAN), \
|
||||
CLEAN_FILES := $(CLEAN_FILES), \
|
||||
COPY := $(COPY), \
|
||||
|
||||
@@ -84,7 +84,7 @@ $(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \
|
||||
TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \
|
||||
SRC := $(TOPDIR)/make/jdk/src/classes, \
|
||||
INCLUDES := build/tools/depend, \
|
||||
BIN := $(BUILDTOOLS_OUTPUTDIR)/plugins, \
|
||||
BIN := $(BUILDTOOLS_OUTPUTDIR)/depend, \
|
||||
DISABLED_WARNINGS := options, \
|
||||
JAVAC_FLAGS := \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
|
||||
@@ -97,21 +97,13 @@ $(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \
|
||||
--add-exports jdk.internal.opt/jdk.internal.opt=jdk.javadoc.interim, \
|
||||
))
|
||||
|
||||
$(eval $(call SetupJavaCompilation, COMPILE_JBR_API_PLUGIN, \
|
||||
TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \
|
||||
SRC := $(TOPDIR)/make/jdk/src/classes, \
|
||||
INCLUDES := build/tools/jbrapi, \
|
||||
BIN := $(BUILDTOOLS_OUTPUTDIR)/plugins, \
|
||||
))
|
||||
DEPEND_SERVICE_PROVIDER := $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services/com.sun.source.util.Plugin
|
||||
|
||||
PLUGINS_SERVICE_PROVIDER := $(BUILDTOOLS_OUTPUTDIR)/plugins/META-INF/services/com.sun.source.util.Plugin
|
||||
|
||||
$(PLUGINS_SERVICE_PROVIDER):
|
||||
$(call MakeDir, $(BUILDTOOLS_OUTPUTDIR)/plugins/META-INF/services)
|
||||
$(DEPEND_SERVICE_PROVIDER):
|
||||
$(call MakeDir, $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services)
|
||||
$(ECHO) build.tools.depend.Depend > $@
|
||||
$(ECHO) build.tools.jbrapi.JBRApiPlugin >> $@
|
||||
|
||||
TARGETS += $(COMPILE_DEPEND) $(COMPILE_JBR_API_PLUGIN) $(PLUGINS_SERVICE_PROVIDER)
|
||||
TARGETS += $(COMPILE_DEPEND) $(DEPEND_SERVICE_PROVIDER)
|
||||
|
||||
################################################################################
|
||||
|
||||
|
||||
100
make/JBRApi.gmk
100
make/JBRApi.gmk
@@ -25,49 +25,69 @@
|
||||
|
||||
include $(SPEC)
|
||||
include MakeBase.gmk
|
||||
include Utils.gmk
|
||||
include JavaCompilation.gmk
|
||||
|
||||
JBR_API_ORIGIN := https://github.com/JetBrains/JetBrainsRuntimeApi.git
|
||||
JBR_API_DIR := $(TOPDIR)/jbr-api
|
||||
JBR_API_ROOT_DIR := $(TOPDIR)/src/jetbrains.api
|
||||
JBR_API_TOOLS_DIR := $(JBR_API_ROOT_DIR)/tools
|
||||
JBR_API_SRC_DIR := $(JBR_API_ROOT_DIR)/src
|
||||
JBR_API_OUTPUT_DIR := $(OUTPUTDIR)/jbr-api
|
||||
JBR_API_GENSRC_DIR := $(JBR_API_OUTPUT_DIR)/gensrc
|
||||
JBR_API_BIN_DIR := $(JBR_API_OUTPUT_DIR)/bin
|
||||
JBR_API_VERSION_PROPERTIES := $(JBR_API_ROOT_DIR)/version.properties
|
||||
JBR_API_VERSION_GENSRC := $(JBR_API_OUTPUT_DIR)/jbr-api.version
|
||||
JBR_API_GENSRC_BATCH := $(JBR_API_VERSION_GENSRC)
|
||||
|
||||
ARTIFACT_NAME := jbr-api-SNAPSHOT
|
||||
ifeq ($(call isBuildOsEnv, windows.cygwin windows.msys2), true)
|
||||
HOME := $$USERPROFILE
|
||||
M2_REPO := $(shell $(PATHTOOL) $(HOME))/.m2/repository
|
||||
else ifeq ($(call isBuildOsEnv, windows.wsl1 windows.wsl2), true)
|
||||
HOME := `cmd.exe /C "echo %USERPROFILE%" 2> /dev/null`
|
||||
M2_REPO := $(shell $(PATHTOOL) $(HOME))/.m2/repository
|
||||
JBR_API_SRC_FILES := $(call FindFiles, $(JBR_API_SRC_DIR))
|
||||
JBR_API_GENSRC_FILES := $(foreach f, $(call FindFiles, $(JBR_API_SRC_DIR)), \
|
||||
$(JBR_API_GENSRC_DIR)/$(call RelativePath, $f, $(JBR_API_SRC_DIR)))
|
||||
|
||||
ifeq ($(JBR_API_JBR_VERSION),)
|
||||
JBR_API_JBR_VERSION := DEVELOPMENT
|
||||
JBR_API_FAIL_ON_HASH_MISMATCH := false
|
||||
else
|
||||
M2_REPO := $(HOME)/.m2/repository
|
||||
.PHONY: $(JBR_API_VERSION_PROPERTIES)
|
||||
JBR_API_FAIL_ON_HASH_MISMATCH := true
|
||||
endif
|
||||
M2_ARTIFACT := $(M2_REPO)/com/jetbrains/jbr-api/SNAPSHOT
|
||||
M2_POM_CONTENT := \
|
||||
<?xml version="1.0" encoding="UTF-8"?> \
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" \
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> \
|
||||
<modelVersion>4.0.0</modelVersion> \
|
||||
<groupId>com.jetbrains</groupId> \
|
||||
<artifactId>jbr-api</artifactId> \
|
||||
<version>SNAPSHOT</version> \
|
||||
</project> \
|
||||
|
||||
jbr-api:
|
||||
if [ -d "$(JBR_API_DIR)" ]; then \
|
||||
$(GIT) -C "$(JBR_API_DIR)" fetch; \
|
||||
$(GIT) -C "$(JBR_API_DIR)" merge-base --is-ancestor origin/main HEAD || \
|
||||
$(ECHO) "!!! Current JBR API revision is outdated, update the branch in $(JBR_API_DIR) !!!"; \
|
||||
else \
|
||||
$(ECHO) "JBR API directory does not exist. Initializing..."; \
|
||||
$(GIT) clone "$(JBR_API_ORIGIN)" "$(JBR_API_DIR)" --config core.autocrlf=false; \
|
||||
fi
|
||||
$(BASH) "$(JBR_API_DIR)/tools/build.sh" dev "$(BOOT_JDK)"
|
||||
if [ -d "$(M2_REPO)" ]; then \
|
||||
$(MKDIR) -p $(M2_ARTIFACT); \
|
||||
$(ECHO) "$(M2_POM_CONTENT)" > $(M2_ARTIFACT)/$(ARTIFACT_NAME).pom; \
|
||||
$(CP) "$(JBR_API_DIR)/out/$(ARTIFACT_NAME).jar" "$(M2_ARTIFACT)"; \
|
||||
$(ECHO) "Installed into local Maven repository as com.jetbrains:jbr-api:SNAPSHOT"; \
|
||||
else \
|
||||
$(ECHO) "No Maven repository found at $(M2_REPO) - skipping local installation"; \
|
||||
fi
|
||||
ARCHIVE_BUILD_JBR_API_BIN := $(JBR_API_BIN_DIR)
|
||||
$(eval $(call SetupJavaCompilation, BUILD_JBR_API, \
|
||||
SMALL_JAVA := true, \
|
||||
COMPILER := bootjdk, \
|
||||
SRC := $(JBR_API_GENSRC_DIR), \
|
||||
EXTRA_FILES := $(JBR_API_GENSRC_FILES), \
|
||||
BIN := $(JBR_API_BIN_DIR), \
|
||||
JAR := $(JBR_API_OUTPUT_DIR)/jbr-api.jar, \
|
||||
))
|
||||
|
||||
.PHONY: jbr-api
|
||||
$(eval $(call SetupJarArchive, BUILD_JBR_API_SOURCES_JAR, \
|
||||
DEPENDENCIES := $(JBR_API_GENSRC_FILES), \
|
||||
SRCS := $(JBR_API_GENSRC_DIR), \
|
||||
JAR := $(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar, \
|
||||
SUFFIXES := .java, \
|
||||
BIN := $(JBR_API_BIN_DIR), \
|
||||
))
|
||||
|
||||
# Grouped targets may not be supported, so hack dependencies: sources -> version file -> generated sources
|
||||
$(JBR_API_VERSION_GENSRC): $(JBR_API_SRC_FILES) $(JBR_API_VERSION_PROPERTIES) $(JBR_API_TOOLS_DIR)/Gensrc.java
|
||||
$(ECHO) Generating sources for JBR API
|
||||
$(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_TOOLS_DIR)/Gensrc.java" \
|
||||
"$(TOPDIR)/src" "$(JBR_API_OUTPUT_DIR)" "$(JBR_API_JBR_VERSION)"
|
||||
$(JBR_API_GENSRC_FILES): $(JBR_API_VERSION_GENSRC)
|
||||
$(TOUCH) $@
|
||||
|
||||
jbr-api-check-version: $(JBR_API_GENSRC_FILES) $(JBR_API_VERSION_PROPERTIES)
|
||||
$(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_TOOLS_DIR)/CheckVersion.java" \
|
||||
"$(JBR_API_ROOT_DIR)" "$(JBR_API_GENSRC_DIR)" "$(JBR_API_FAIL_ON_HASH_MISMATCH)"
|
||||
|
||||
jbr-api: $(BUILD_JBR_API) $(BUILD_JBR_API_SOURCES_JAR) jbr-api-check-version
|
||||
|
||||
.PHONY: jbr-api jbr-api-check-version
|
||||
|
||||
ifneq ($(JBR_API_CONF_FILE),)
|
||||
$(JBR_API_CONF_FILE): $(JBR_API_GENSRC_FILES)
|
||||
$(ECHO) "VERSION=`$(CAT) $(JBR_API_VERSION_GENSRC)`" > $(JBR_API_CONF_FILE)
|
||||
$(ECHO) "JAR=$(JBR_API_OUTPUT_DIR)/jbr-api.jar" >> $(JBR_API_CONF_FILE)
|
||||
$(ECHO) "SOURCES_JAR=$(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar" >> $(JBR_API_CONF_FILE)
|
||||
jbr-api: $(JBR_API_CONF_FILE)
|
||||
.PHONY: $(JBR_API_CONF_FILE)
|
||||
endif
|
||||
|
||||
@@ -106,6 +106,9 @@ AC_DEFUN_ONCE([LIB_SETUP_WAYLAND],
|
||||
AC_ARG_WITH(vulkan-include, [AS_HELP_STRING([--with-vulkan-include],
|
||||
[specify directory for the vulkan include files ({with-vulkan-include}/vulkan/vulkan.h)])])
|
||||
|
||||
AC_ARG_WITH(vulkan-hpp, [AS_HELP_STRING([--with-vulkan-hpp],
|
||||
[specify directory for the vulkan-hpp include files ({with-vulkan-hpp}/vulkan/vulkan_raii.hpp)])])
|
||||
|
||||
AC_ARG_WITH(vulkan-shader-compiler, [AS_HELP_STRING([--with-vulkan-shader-compiler],
|
||||
[specify which shader compiler to use: glslc/glslangValidator])])
|
||||
|
||||
@@ -136,11 +139,27 @@ AC_DEFUN_ONCE([LIB_SETUP_WAYLAND],
|
||||
AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([Can't find 'vulkan/vulkan.h' under '${with_vulkan_include}'])
|
||||
fi
|
||||
AC_MSG_CHECKING([for vulkan_raii.hpp])
|
||||
if test "x${with_vulkan_hpp}" != x; then
|
||||
VULKAN_FLAGS="-I${with_vulkan_hpp} ${VULKAN_FLAGS}"
|
||||
VULKAN_HPP_DIR=${with_vulkan_hpp}
|
||||
else
|
||||
VULKAN_HPP_DIR=${with_vulkan_include}
|
||||
fi
|
||||
if test -s "$VULKAN_HPP_DIR/vulkan/vulkan_raii.hpp"; then
|
||||
VULKAN_FOUND=yes
|
||||
AC_MSG_RESULT([yes])
|
||||
else
|
||||
VULKAN_FOUND=no
|
||||
AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([Can't find 'vulkan/vulkan_raii.hpp' under '$VULKAN_HPP_DIR'])
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_LANG_PUSH([C++])
|
||||
if test "x$VULKAN_FOUND" = xno; then
|
||||
# Check vulkan sdk location
|
||||
AC_CHECK_HEADERS([$VULKAN_SDK/include/vulkan/vulkan.h],
|
||||
AC_CHECK_HEADERS([$VULKAN_SDK/include/vulkan/vulkan.h $VULKAN_SDK/include/vulkan/vulkan_raii.hpp],
|
||||
[ VULKAN_FOUND=yes
|
||||
VULKAN_FLAGS="-DVK_USE_PLATFORM_WAYLAND_KHR -I${VULKAN_SDK}/include -DVULKAN_ENABLED"
|
||||
],
|
||||
@@ -150,13 +169,14 @@ AC_DEFUN_ONCE([LIB_SETUP_WAYLAND],
|
||||
|
||||
if test "x$VULKAN_FOUND" = xno; then
|
||||
# Check default /usr/include location
|
||||
AC_CHECK_HEADERS([vulkan/vulkan.h],
|
||||
AC_CHECK_HEADERS([vulkan/vulkan.h vulkan/vulkan_raii.hpp],
|
||||
[ VULKAN_FOUND=yes
|
||||
VULKAN_FLAGS="-DVK_USE_PLATFORM_WAYLAND_KHR -DVULKAN_ENABLED"
|
||||
],
|
||||
[ VULKAN_FOUND=no; break ]
|
||||
)
|
||||
fi
|
||||
AC_LANG_POP([C++])
|
||||
|
||||
if test "x$VULKAN_FOUND" = xno; then
|
||||
HELP_MSG_MISSING_DEPENDENCY([vulkan])
|
||||
|
||||
@@ -168,7 +168,6 @@ endef
|
||||
# CREATE_API_DIGEST:=Set to true to use a javac plugin to generate a public API
|
||||
# hash which can be used for down stream dependencies to only rebuild
|
||||
# when the API changes.
|
||||
# PROCESS_JBR_API:=Set to true to use an annotation processor to generate JBR API bindings.
|
||||
# KEEP_ALL_TRANSLATIONS:=Set to true to skip translation filtering
|
||||
SetupJavaCompilation = $(NamedParamsMacroTemplate)
|
||||
define SetupJavaCompilationBody
|
||||
@@ -443,22 +442,13 @@ define SetupJavaCompilationBody
|
||||
|
||||
ifeq ($$($1_CREATE_API_DIGEST), true)
|
||||
$1_API_DIGEST_FLAGS := \
|
||||
-classpath $$(BUILDTOOLS_OUTPUTDIR)/depend \
|
||||
-Xplugin:"depend $$($1_API_TARGET)" \
|
||||
"-XDinternalAPIPath=$$($1_API_INTERNAL)" \
|
||||
"-XDLOG_LEVEL=$(LOG_LEVEL)" \
|
||||
#
|
||||
|
||||
$1_EXTRA_DEPS := $$($1_EXTRA_DEPS) $$(BUILDTOOLS_OUTPUTDIR)/plugins/_the.COMPILE_DEPEND_batch
|
||||
endif
|
||||
|
||||
ifeq ($$($1_PROCESS_JBR_API), true)
|
||||
# Automatic path conversion doesn't work for two arguments, so call fixpath manually
|
||||
$1_JBR_API_FLAGS := -Xplugin:"jbr-api $$(call FixPath, $$($1_BIN)/java.base/META-INF/jbrapi.registry) $$(call FixPath, $(TOPDIR)/jb/jbr-api.version)"
|
||||
$1_EXTRA_DEPS := $$($1_EXTRA_DEPS) $$(BUILDTOOLS_OUTPUTDIR)/plugins/_the.COMPILE_JBR_API_PLUGIN_batch
|
||||
endif
|
||||
|
||||
ifeq ($$(call Or, $$($1_CREATE_API_DIGEST) $$($1_PROCESS_JBR_API)), true)
|
||||
$1_PROCESSORPATH := -processorpath $$(BUILDTOOLS_OUTPUTDIR)/plugins
|
||||
$1_EXTRA_DEPS := $$(BUILDTOOLS_OUTPUTDIR)/depend/_the.COMPILE_DEPEND_batch
|
||||
endif
|
||||
|
||||
# Create a file with all sources, to pass to javac in an @file.
|
||||
@@ -497,7 +487,7 @@ define SetupJavaCompilationBody
|
||||
$$(call MakeDir, $$(@D))
|
||||
$$(call ExecuteWithLog, $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$$($1_SAFE_NAME)_batch, \
|
||||
$$($1_JAVAC_CMD) $$($1_FLAGS) \
|
||||
$$($1_PROCESSORPATH) $$($1_API_DIGEST_FLAGS) $$($1_JBR_API_FLAGS) \
|
||||
$$($1_API_DIGEST_FLAGS) \
|
||||
-XDmodifiedInputs=$$($1_MODFILELIST_FIXED) \
|
||||
-d $$($1_BIN) $$($1_HEADERS_ARG) @$$($1_FILELIST)) && \
|
||||
$(TOUCH) $$@
|
||||
|
||||
@@ -1,451 +0,0 @@
|
||||
package build.tools.jbrapi;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.ArrayType;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.ExecutableType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.ElementScanner14;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class JBRApiPlugin implements Plugin {
|
||||
|
||||
enum Binding {
|
||||
SERVICE,
|
||||
PROVIDES,
|
||||
PROVIDED,
|
||||
TWO_WAY
|
||||
}
|
||||
|
||||
record DiagnosticTree(CompilationUnitTree root, Tree tree) {}
|
||||
record TypeBinding(DiagnosticTree diagnostic, TypeElement element, String currentType, String bindType, Binding binding) {}
|
||||
record MethodBinding(DiagnosticTree diagnostic, ExecutableElement element, Registry.StaticDescriptor currentMethod, Registry.StaticMethod bindMethod) {}
|
||||
|
||||
final Map<String, TypeBinding> typeBindings = new HashMap<>();
|
||||
final List<MethodBinding> methodBindings = new ArrayList<>();
|
||||
Elements elements;
|
||||
Trees trees;
|
||||
Types types;
|
||||
|
||||
class Registry {
|
||||
|
||||
record Type(String type, Binding binding) {}
|
||||
record StaticMethod(String type, String name) {}
|
||||
record StaticDescriptor(StaticMethod method, String descriptor) {}
|
||||
|
||||
final Map<String, Type> types = new HashMap<>();
|
||||
final Map<StaticDescriptor, StaticMethod> methods = new HashMap<>();
|
||||
final Set<Object> internal = new HashSet<>();
|
||||
|
||||
void validateInternal(DiagnosticTree diagnostic, String currentType, Binding binding, TypeElement bindType) {
|
||||
if (bindType.getKind() != ElementKind.CLASS && bindType.getKind() != ElementKind.INTERFACE) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " +
|
||||
bindType.getQualifiedName().toString() + " (not a class or interface)",
|
||||
diagnostic.tree, diagnostic.root);
|
||||
} else if (bindType.getModifiers().contains(Modifier.FINAL) || bindType.getModifiers().contains(Modifier.SEALED)) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " +
|
||||
bindType.getQualifiedName().toString() + " (not inheritable)",
|
||||
diagnostic.tree, diagnostic.root);
|
||||
}
|
||||
if (binding != Binding.SERVICE) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " +
|
||||
bindType.getQualifiedName().toString() + " (internal, non-service)",
|
||||
diagnostic.tree, diagnostic.root);
|
||||
}
|
||||
}
|
||||
|
||||
void validateInternalMethod(DiagnosticTree diagnostic, StaticDescriptor currentMethod, TypeElement bindType, String bindMethod) {
|
||||
boolean methodFound = false;
|
||||
for (Element m : bindType.getEnclosedElements()) {
|
||||
if (m.getKind() == ElementKind.METHOD &&
|
||||
!m.getModifiers().contains(Modifier.STATIC) &&
|
||||
!m.getModifiers().contains(Modifier.FINAL) &&
|
||||
m.getSimpleName().contentEquals(bindMethod) &&
|
||||
descriptor(m.asType()).equals(currentMethod.descriptor)) {
|
||||
methodFound = true;
|
||||
}
|
||||
}
|
||||
if (!methodFound) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid static binding: " +
|
||||
currentMethod.method.type + "#" + currentMethod.method.name + " -> " +
|
||||
bindType.getQualifiedName().toString() + "#" + bindMethod +
|
||||
" (no matching method found, type conversions are not allowed for internal bindings)",
|
||||
diagnostic.tree, diagnostic.root);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> addBindings() {
|
||||
List<String> unresolvedErrors = new ArrayList<>();
|
||||
List<TypeBinding> addedTypes = new ArrayList<>();
|
||||
List<MethodBinding> addedMethods = new ArrayList<>();
|
||||
Set<Object> validated = new HashSet<>();
|
||||
// Remove changed bindings.
|
||||
for (TypeBinding type : typeBindings.values()) {
|
||||
if (type.bindType != null) addedTypes.add(type);
|
||||
types.remove(type.currentType);
|
||||
}
|
||||
for (MethodBinding method : methodBindings) {
|
||||
if (method.bindMethod != null) addedMethods.add(method);
|
||||
methods.remove(method.currentMethod);
|
||||
}
|
||||
methods.entrySet().removeIf(m -> typeBindings.containsKey(m.getKey().method.type));
|
||||
// Build inverse binding map.
|
||||
Map<String, String> inverseTypes = types.entrySet().stream().collect(Collectors.toMap(
|
||||
e -> e.getValue().type, Map.Entry::getKey, (a, b) -> {
|
||||
unresolvedErrors.add("Conflicting JBR API binding: " + a + " and "+ b + " binds to the same type");
|
||||
return a + "," + b;
|
||||
}));
|
||||
Map<StaticDescriptor, StaticMethod> inverseMethods = methods.entrySet().stream().collect(Collectors.toMap(
|
||||
e -> new StaticDescriptor(e.getValue(), e.getKey().descriptor), e -> e.getKey().method, (a, b) -> {
|
||||
unresolvedErrors.add("Conflicting JBR API binding: " +
|
||||
a.type + "#" + a.name + " and "+ b.type + "#" + b.name + " binds to the same method");
|
||||
return new StaticMethod(a.type + "," + b.type, a.name + "," + b.name);
|
||||
}));
|
||||
// Add new bindings.
|
||||
for (TypeBinding type : addedTypes) types.put(type.currentType, new Type(type.bindType, type.binding));
|
||||
for (MethodBinding method : addedMethods) methods.put(method.currentMethod, method.bindMethod);
|
||||
// Validate type bindings.
|
||||
for (TypeBinding type : addedTypes) {
|
||||
String inv = inverseTypes.get(type.bindType);
|
||||
if (inv != null) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"Conflicting JBR API binding: " + type.currentType + " -> " + type.bindType + " <- " + inv,
|
||||
type.diagnostic.tree, type.diagnostic.root);
|
||||
inverseTypes.put(type.bindType, inv + "," + type.currentType);
|
||||
} else inverseTypes.put(type.bindType, type.currentType);
|
||||
Type next = types.get(type.bindType);
|
||||
if (next != null) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"Conflicting JBR API binding: " + type.currentType + " -> " + type.bindType + " -> " + next,
|
||||
type.diagnostic.tree, type.diagnostic.root);
|
||||
}
|
||||
String prev = inverseTypes.get(type.currentType);
|
||||
if (prev != null) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"Conflicting JBR API binding: " + prev + " -> " + type.currentType + " -> " + type.bindType,
|
||||
type.diagnostic.tree, type.diagnostic.root);
|
||||
}
|
||||
if (validated.add(type.currentType)) {
|
||||
TypeElement bindElement = elements.getTypeElement(type.bindType);
|
||||
if (bindElement != null) {
|
||||
internal.add(type.currentType);
|
||||
validateInternal(type.diagnostic, type.currentType, type.binding, bindElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Validate method bindings.
|
||||
for (MethodBinding method : addedMethods) {
|
||||
StaticDescriptor invDescriptor = new StaticDescriptor(method.bindMethod, method.currentMethod.descriptor);
|
||||
StaticMethod inv = inverseMethods.get(invDescriptor);
|
||||
if (inv != null) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR, "Conflicting JBR API binding: " +
|
||||
method.currentMethod.method.type + "#" + method.currentMethod.method.name + " -> " +
|
||||
method.bindMethod.type + "#" + method.bindMethod.name + " <- " +
|
||||
inv.type + "#" + inv.name,
|
||||
method.diagnostic.tree, method.diagnostic.root);
|
||||
inverseMethods.put(invDescriptor, new StaticMethod(
|
||||
inv.type + "," + method.currentMethod.method.type, inv.name + "," + method.currentMethod.method.name));
|
||||
} else inverseMethods.put(invDescriptor, method.currentMethod.method);
|
||||
if (validated.add(method.currentMethod)) {
|
||||
TypeElement bindElement = elements.getTypeElement(method.bindMethod.type);
|
||||
if (bindElement != null) {
|
||||
internal.add(method.currentMethod);
|
||||
validateInternalMethod(method.diagnostic, method.currentMethod, bindElement, method.bindMethod.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
// [Re]validate remaining.
|
||||
types.forEach((k, v) -> {
|
||||
if (validated.add(k)) {
|
||||
TypeBinding type = typeBindings.get(v.type);
|
||||
if (type != null) {
|
||||
internal.add(k);
|
||||
validateInternal(type.diagnostic, k, v.binding, type.element);
|
||||
} else if (elements.getTypeElement(v.type) != null) {
|
||||
internal.add(k); // Couldn't validate, but at least found the type.
|
||||
if (v.binding != Binding.SERVICE) {
|
||||
unresolvedErrors.add("Invalid JBR API binding:" + k + " -> " + v.type + " (internal, non-service)");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
methods.forEach((k, v) -> {
|
||||
if (validated.add(k)) {
|
||||
TypeBinding type = typeBindings.get(v.type);
|
||||
if (type != null) {
|
||||
internal.add(k);
|
||||
validateInternalMethod(type.diagnostic, k, type.element, v.name);
|
||||
} else if (elements.getTypeElement(v.type) != null) {
|
||||
internal.add(k); // Couldn't validate, but at least found the type.
|
||||
}
|
||||
}
|
||||
});
|
||||
return unresolvedErrors;
|
||||
}
|
||||
|
||||
void read(RandomAccessFile file) throws IOException {
|
||||
String s;
|
||||
while ((s = file.readLine()) != null) {
|
||||
String[] tokens = s.split(" ");
|
||||
switch (tokens[0]) {
|
||||
case "TYPE" -> {
|
||||
types.put(tokens[1], new Type(tokens[2], Binding.valueOf(tokens[3])));
|
||||
if (tokens.length > 4 && tokens[4].equals("INTERNAL")) internal.add(tokens[1]);
|
||||
}
|
||||
case "STATIC" -> {
|
||||
StaticDescriptor descriptor = new StaticDescriptor(new StaticMethod(
|
||||
tokens[1], tokens[2]), tokens[3]);
|
||||
methods.put(descriptor, new StaticMethod(tokens[4], tokens[5]));
|
||||
if (tokens.length > 6 && tokens[6].equals("INTERNAL")) internal.add(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write(RandomAccessFile file) throws IOException {
|
||||
for (var t : types.entrySet()) {
|
||||
file.writeBytes("TYPE " + t.getKey() + " " + t.getValue().type + " " + t.getValue().binding +
|
||||
(internal.contains(t.getKey()) ? " INTERNAL\n" : "\n"));
|
||||
}
|
||||
for (var t : methods.entrySet()) {
|
||||
file.writeBytes("STATIC " + t.getKey().method.type + " " + t.getKey().method.name + " " +
|
||||
t.getKey().descriptor + " " + t.getValue().type + " " + t.getValue().name +
|
||||
(internal.contains(t.getKey()) ? " INTERNAL\n" : "\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String descriptor(TypeMirror t) {
|
||||
return switch (t.getKind()) {
|
||||
case VOID -> "V";
|
||||
case BOOLEAN -> "Z";
|
||||
case BYTE -> "B";
|
||||
case CHAR -> "C";
|
||||
case SHORT -> "S";
|
||||
case INT -> "I";
|
||||
case LONG -> "J";
|
||||
case FLOAT -> "F";
|
||||
case DOUBLE -> "D";
|
||||
case ARRAY -> "[" + descriptor(((ArrayType) t).getComponentType());
|
||||
case DECLARED -> "L" + elements.getBinaryName((TypeElement) ((DeclaredType) t).asElement())
|
||||
.toString().replace('.', '/') + ";";
|
||||
case EXECUTABLE -> "(" + ((ExecutableType) t).getParameterTypes().stream().map(this::descriptor)
|
||||
.collect(Collectors.joining()) + ")" + descriptor(((ExecutableType) t).getReturnType());
|
||||
case TYPEVAR, WILDCARD, UNION, INTERSECTION -> descriptor(types.erasure(t));
|
||||
default -> throw new RuntimeException("Cannot generate descriptor for type: " + t);
|
||||
};
|
||||
}
|
||||
|
||||
Registry.StaticDescriptor staticDescriptor(String type, ExecutableElement e) {
|
||||
return new Registry.StaticDescriptor(new Registry.StaticMethod(type, e.getSimpleName().toString()), descriptor(e.asType()));
|
||||
}
|
||||
|
||||
AnnotationValue annotationValue(AnnotationMirror m) {
|
||||
if (m == null) return null;
|
||||
return m.getElementValues().entrySet().stream()
|
||||
.filter(t -> t.getKey().getSimpleName().contentEquals("value"))
|
||||
.map(Map.Entry::getValue).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
static boolean isJavaIdentifier(String name, int from, int to) {
|
||||
if (!Character.isJavaIdentifierStart(name.charAt(from))) return false;
|
||||
for (int i = from + 1; i < to; i++) {
|
||||
if (!Character.isJavaIdentifierPart(name.charAt(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static boolean isJavaIdentifier(String name) {
|
||||
if (name == null || name.isEmpty()) return false;
|
||||
return isJavaIdentifier(name, 0, name.length());
|
||||
}
|
||||
static boolean isJavaTypeIdentifier(String name) {
|
||||
if (name == null || name.isEmpty()) return false;
|
||||
for (int i = 0; i < name.length();) {
|
||||
int next = name.indexOf('.', i);
|
||||
if (next == -1) next = name.length();
|
||||
if (!isJavaIdentifier(name, i, next)) return false;
|
||||
i = next + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void scan(CompilationUnitTree root, Element e) {
|
||||
// Get current type name.
|
||||
String currentType;
|
||||
if (e.getKind() == ElementKind.CLASS || e.getKind() == ElementKind.INTERFACE) {
|
||||
currentType = ((TypeElement) e).getQualifiedName().toString();
|
||||
} else if (e.getKind() == ElementKind.METHOD && e.getModifiers().contains(Modifier.STATIC)) {
|
||||
currentType = ((QualifiedNameable) e.getEnclosingElement()).getQualifiedName().toString();
|
||||
} else currentType = null;
|
||||
|
||||
// Find the annotation.
|
||||
AnnotationMirror providedMirror = null, providesMirror = null, serviceMirror = null;
|
||||
for (AnnotationMirror m : elements.getAllAnnotationMirrors(e)) {
|
||||
switch (m.getAnnotationType().toString()) {
|
||||
case "com.jetbrains.exported.JBRApi.Provided" -> providedMirror = m;
|
||||
case "com.jetbrains.exported.JBRApi.Provides" -> providesMirror = m;
|
||||
case "com.jetbrains.exported.JBRApi.Service" -> serviceMirror = m;
|
||||
}
|
||||
}
|
||||
|
||||
AnnotationMirror mirror = null;
|
||||
AnnotationValue value = null;
|
||||
Binding binding = null;
|
||||
if (serviceMirror != null) {
|
||||
if (providesMirror == null) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"@Service also requires @Provides", trees.getTree(e, serviceMirror), root);
|
||||
return;
|
||||
}
|
||||
if (providedMirror != null) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"@Service cannot be used with @Provided", trees.getTree(e, serviceMirror), root);
|
||||
return;
|
||||
}
|
||||
value = annotationValue(mirror = providesMirror);
|
||||
binding = Binding.SERVICE;
|
||||
} else if (providesMirror != null) {
|
||||
value = annotationValue(mirror = providesMirror);
|
||||
if (providedMirror != null) {
|
||||
AnnotationValue v = annotationValue(providedMirror);
|
||||
if (!value.getValue().toString().equals(v.getValue().toString())) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"@Provided and @Provides doesn't match", trees.getTree(e, mirror, value), root);
|
||||
return;
|
||||
}
|
||||
binding = Binding.TWO_WAY;
|
||||
} else binding = Binding.PROVIDES;
|
||||
} else if (providedMirror != null) {
|
||||
value = annotationValue(mirror = providedMirror);
|
||||
binding = Binding.PROVIDED;
|
||||
}
|
||||
|
||||
if (value != null && value.getValue().toString().isEmpty()) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"Empty JBR API binding",
|
||||
trees.getTree(e, mirror, value), root);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentType == null) {
|
||||
if (value != null) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"JBR API annotations are only allowed on classes, interfaces and static methods",
|
||||
trees.getTree(e, mirror), root);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (value != null && e.getKind() == ElementKind.METHOD && binding != Binding.PROVIDES) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR,
|
||||
"Only @Provides is allowed for static methods",
|
||||
trees.getTree(e, mirror), root);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine class/method names.
|
||||
String bindType = null, bindMethod = null;
|
||||
if (value != null) {
|
||||
bindType = value.getValue().toString();
|
||||
if (e.getKind() == ElementKind.METHOD) {
|
||||
int splitIndex = bindType.indexOf('#');
|
||||
if (splitIndex != -1) {
|
||||
bindMethod = bindType.substring(splitIndex + 1);
|
||||
bindType = bindType.substring(0, splitIndex);
|
||||
if (!isJavaIdentifier(bindMethod)) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid method identifier: " + bindMethod,
|
||||
trees.getTree(e, mirror, value), root);
|
||||
return;
|
||||
}
|
||||
} else bindMethod = e.getSimpleName().toString();
|
||||
}
|
||||
if (!isJavaTypeIdentifier(bindType)) {
|
||||
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid type identifier: " + bindType,
|
||||
trees.getTree(e, mirror, value), root);
|
||||
return;
|
||||
}
|
||||
if (Character.isUpperCase(bindType.charAt(0))) bindType = "com.jetbrains." + bindType; // Short form
|
||||
}
|
||||
|
||||
// Add entry.
|
||||
DiagnosticTree diagnostic = new DiagnosticTree(root, trees.getTree(e, mirror, value));
|
||||
if (e.getKind() == ElementKind.METHOD) {
|
||||
ExecutableElement m = (ExecutableElement) e;
|
||||
methodBindings.add(new MethodBinding(diagnostic, m, staticDescriptor(currentType, m),
|
||||
bindType == null ? null : new Registry.StaticMethod(bindType, bindMethod)));
|
||||
} else {
|
||||
typeBindings.put(currentType, new TypeBinding(diagnostic, (TypeElement) e, currentType, bindType, binding));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "jbr-api";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(JavacTask jt, String... args) {
|
||||
Path output = Path.of(args[0]);
|
||||
String implVersion;
|
||||
try {
|
||||
implVersion = Files.readString(Path.of(args[1])).strip();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
elements = jt.getElements();
|
||||
trees = Trees.instance(jt);
|
||||
types = jt.getTypes();
|
||||
jt.addTaskListener(new TaskListener() {
|
||||
@Override
|
||||
public void finished(TaskEvent te) {
|
||||
if (te.getKind() == TaskEvent.Kind.ANALYZE && te.getTypeElement() != null) {
|
||||
new ElementScanner14<Void, CompilationUnitTree>() {
|
||||
@Override
|
||||
public Void visitModule(ModuleElement e, CompilationUnitTree unused) { return null; }
|
||||
@Override
|
||||
public Void visitPackage(PackageElement e, CompilationUnitTree unused) { return null; }
|
||||
@Override
|
||||
public Void scan(Element e, CompilationUnitTree root) {
|
||||
JBRApiPlugin.this.scan(root, e);
|
||||
e.accept(this, root);
|
||||
return null;
|
||||
}
|
||||
}.scan(te.getTypeElement(), te.getCompilationUnit());
|
||||
} else if (te.getKind() == TaskEvent.Kind.COMPILATION) {
|
||||
try (RandomAccessFile file = new RandomAccessFile(output.toFile(), "rw");
|
||||
FileChannel channel = file.getChannel()) {
|
||||
for (;;) {
|
||||
try { if (channel.lock() != null) break; } catch (OverlappingFileLockException ignore) {}
|
||||
LockSupport.parkNanos(10_000000);
|
||||
}
|
||||
Registry r = new Registry();
|
||||
r.read(file);
|
||||
var unresolvedErrors = r.addBindings();
|
||||
file.setLength(0);
|
||||
file.writeBytes("VERSION " + implVersion + "\n");
|
||||
r.write(file);
|
||||
if (!unresolvedErrors.isEmpty()) {
|
||||
throw new RuntimeException(String.join("\n", unresolvedErrors));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -671,10 +671,8 @@ else
|
||||
# noexcept-type required for GCC 7 builds. Not required for GCC 8+.
|
||||
# expansion-to-defined required for GCC 9 builds. Not required for GCC 10+.
|
||||
# maybe-uninitialized required for GCC 8 builds. Not required for GCC 9+.
|
||||
# calloc-transposed-args required for GCC 14 builds. (fixed upstream in Harfbuzz 032c931e1c0cfb20f18e5acb8ba005775242bd92)
|
||||
HARFBUZZ_DISABLED_WARNINGS_CXX_gcc := class-memaccess noexcept-type \
|
||||
expansion-to-defined dangling-reference maybe-uninitialized \
|
||||
calloc-transposed-args
|
||||
expansion-to-defined dangling-reference maybe-uninitialized
|
||||
HARFBUZZ_DISABLED_WARNINGS_clang := missing-field-initializers range-loop-analysis
|
||||
HARFBUZZ_DISABLED_WARNINGS_microsoft := 4267 4244
|
||||
|
||||
@@ -1136,8 +1134,7 @@ ifeq ($(call isTargetOs, macosx), true)
|
||||
-framework ExceptionHandling \
|
||||
-framework JavaRuntimeSupport \
|
||||
-framework OpenGL \
|
||||
-framework QuartzCore \
|
||||
-framework UniformTypeIdentifiers -ljava, \
|
||||
-framework QuartzCore -ljava, \
|
||||
))
|
||||
|
||||
TARGETS += $(BUILD_LIBAWT_LWAWT)
|
||||
|
||||
@@ -96,7 +96,7 @@ ZActivatedArray<T>::ZActivatedArray(bool locked)
|
||||
_array() {}
|
||||
|
||||
template <typename T>
|
||||
ZActivatedArray<T>::~ZActivatedArray() {
|
||||
ZActivatedArray<T>::~ZActivatedArray<T>() {
|
||||
FreeHeap(_lock);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ template <class T, MEMFLAGS F> class ChunkedList : public CHeapObj<F> {
|
||||
}
|
||||
|
||||
public:
|
||||
ChunkedList() : _top(_values), _next_used(nullptr), _next_free(nullptr) {}
|
||||
ChunkedList<T, F>() : _top(_values), _next_used(nullptr), _next_free(nullptr) {}
|
||||
|
||||
bool is_full() const {
|
||||
return _top == end();
|
||||
|
||||
@@ -99,7 +99,7 @@ template <class T> class EventLogBase : public EventLog {
|
||||
EventRecord<T>* _records;
|
||||
|
||||
public:
|
||||
EventLogBase(const char* name, const char* handle, int length = LogEventsBufferEntries):
|
||||
EventLogBase<T>(const char* name, const char* handle, int length = LogEventsBufferEntries):
|
||||
_mutex(Mutex::event, name),
|
||||
_name(name),
|
||||
_handle(handle),
|
||||
|
||||
@@ -118,7 +118,7 @@ class GrowableArrayView : public GrowableArrayBase {
|
||||
protected:
|
||||
E* _data; // data array
|
||||
|
||||
GrowableArrayView(E* data, int capacity, int initial_len) :
|
||||
GrowableArrayView<E>(E* data, int capacity, int initial_len) :
|
||||
GrowableArrayBase(capacity, initial_len), _data(data) {}
|
||||
|
||||
~GrowableArrayView() {}
|
||||
@@ -126,7 +126,7 @@ protected:
|
||||
public:
|
||||
const static GrowableArrayView EMPTY;
|
||||
|
||||
bool operator==(const GrowableArrayView& rhs) const {
|
||||
bool operator==(const GrowableArrayView<E>& rhs) const {
|
||||
if (_len != rhs._len)
|
||||
return false;
|
||||
for (int i = 0; i < _len; i++) {
|
||||
@@ -137,7 +137,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator!=(const GrowableArrayView& rhs) const {
|
||||
bool operator!=(const GrowableArrayView<E>& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ template <typename E>
|
||||
class GrowableArrayFromArray : public GrowableArrayView<E> {
|
||||
public:
|
||||
|
||||
GrowableArrayFromArray(E* data, int len) :
|
||||
GrowableArrayFromArray<E>(E* data, int len) :
|
||||
GrowableArrayView<E>(data, len, len) {}
|
||||
};
|
||||
|
||||
@@ -480,7 +480,7 @@ public:
|
||||
return this->at(location);
|
||||
}
|
||||
|
||||
void swap(GrowableArrayWithAllocator* other) {
|
||||
void swap(GrowableArrayWithAllocator<E, Derived>* other) {
|
||||
::swap(this->_data, other->_data);
|
||||
::swap(this->_len, other->_len);
|
||||
::swap(this->_capacity, other->_capacity);
|
||||
@@ -682,8 +682,8 @@ public:
|
||||
// See: init_checks.
|
||||
|
||||
template <typename E>
|
||||
class GrowableArray : public GrowableArrayWithAllocator<E, GrowableArray<E>> {
|
||||
friend class GrowableArrayWithAllocator<E, GrowableArray>;
|
||||
class GrowableArray : public GrowableArrayWithAllocator<E, GrowableArray<E> > {
|
||||
friend class GrowableArrayWithAllocator<E, GrowableArray<E> >;
|
||||
friend class GrowableArrayTest;
|
||||
|
||||
static E* allocate(int max) {
|
||||
@@ -731,7 +731,7 @@ public:
|
||||
GrowableArray() : GrowableArray(2 /* initial_capacity */) {}
|
||||
|
||||
explicit GrowableArray(int initial_capacity) :
|
||||
GrowableArrayWithAllocator<E, GrowableArray>(
|
||||
GrowableArrayWithAllocator<E, GrowableArray<E> >(
|
||||
allocate(initial_capacity),
|
||||
initial_capacity),
|
||||
_metadata() {
|
||||
@@ -739,7 +739,7 @@ public:
|
||||
}
|
||||
|
||||
GrowableArray(int initial_capacity, MEMFLAGS memflags) :
|
||||
GrowableArrayWithAllocator<E, GrowableArray>(
|
||||
GrowableArrayWithAllocator<E, GrowableArray<E> >(
|
||||
allocate(initial_capacity, memflags),
|
||||
initial_capacity),
|
||||
_metadata(memflags) {
|
||||
@@ -747,7 +747,7 @@ public:
|
||||
}
|
||||
|
||||
GrowableArray(int initial_capacity, int initial_len, const E& filler) :
|
||||
GrowableArrayWithAllocator<E, GrowableArray>(
|
||||
GrowableArrayWithAllocator<E, GrowableArray<E> >(
|
||||
allocate(initial_capacity),
|
||||
initial_capacity, initial_len, filler),
|
||||
_metadata() {
|
||||
@@ -755,7 +755,7 @@ public:
|
||||
}
|
||||
|
||||
GrowableArray(int initial_capacity, int initial_len, const E& filler, MEMFLAGS memflags) :
|
||||
GrowableArrayWithAllocator<E, GrowableArray>(
|
||||
GrowableArrayWithAllocator<E, GrowableArray<E> >(
|
||||
allocate(initial_capacity, memflags),
|
||||
initial_capacity, initial_len, filler),
|
||||
_metadata(memflags) {
|
||||
@@ -763,7 +763,7 @@ public:
|
||||
}
|
||||
|
||||
GrowableArray(Arena* arena, int initial_capacity, int initial_len, const E& filler) :
|
||||
GrowableArrayWithAllocator<E, GrowableArray>(
|
||||
GrowableArrayWithAllocator<E, GrowableArray<E> >(
|
||||
allocate(initial_capacity, arena),
|
||||
initial_capacity, initial_len, filler),
|
||||
_metadata(arena) {
|
||||
@@ -847,15 +847,15 @@ class GrowableArrayIterator : public StackObj {
|
||||
|
||||
public:
|
||||
GrowableArrayIterator() : _array(nullptr), _position(0) { }
|
||||
GrowableArrayIterator& operator++() { ++_position; return *this; }
|
||||
E operator*() { return _array->at(_position); }
|
||||
GrowableArrayIterator<E>& operator++() { ++_position; return *this; }
|
||||
E operator*() { return _array->at(_position); }
|
||||
|
||||
bool operator==(const GrowableArrayIterator& rhs) {
|
||||
bool operator==(const GrowableArrayIterator<E>& rhs) {
|
||||
assert(_array == rhs._array, "iterator belongs to different array");
|
||||
return _position == rhs._position;
|
||||
}
|
||||
|
||||
bool operator!=(const GrowableArrayIterator& rhs) {
|
||||
bool operator!=(const GrowableArrayIterator<E>& rhs) {
|
||||
assert(_array == rhs._array, "iterator belongs to different array");
|
||||
return _position != rhs._position;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ template <class E> class LinkedListNode : public AnyObj {
|
||||
template <class E> class LinkedList : public AnyObj {
|
||||
protected:
|
||||
LinkedListNode<E>* _head;
|
||||
NONCOPYABLE(LinkedList);
|
||||
NONCOPYABLE(LinkedList<E>);
|
||||
|
||||
public:
|
||||
LinkedList() : _head(nullptr) { }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
|
||||
* Copyright 2000-2023 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
|
||||
@@ -24,23 +23,21 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#ifndef VKBuffer_h_Included
|
||||
#define VKBuffer_h_Included
|
||||
#include <vulkan/vulkan.h>
|
||||
package com.jetbrains.base;
|
||||
|
||||
typedef struct {
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
VkDeviceSize size;
|
||||
} VKBuffer;
|
||||
import com.jetbrains.internal.JBRApi;
|
||||
|
||||
VkResult VKBuffer_FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter,
|
||||
VkMemoryPropertyFlags properties, uint32_t* pMemoryType);
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
VKBuffer* VKBuffer_Create(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties);
|
||||
|
||||
VKBuffer* VKBuffer_CreateFromData(void* vertices, VkDeviceSize bufferSize);
|
||||
|
||||
void VKBuffer_free(VKBuffer* buffer);
|
||||
|
||||
#endif // VKBuffer_h_Included
|
||||
/**
|
||||
* This class contains mapping between JBR API interfaces and implementation in {@code java.base} module.
|
||||
*/
|
||||
public class JBRApiModule {
|
||||
static {
|
||||
JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports)
|
||||
.service("com.jetbrains.JBR$ServiceApi")
|
||||
.withStatic("getService", "getService", "com.jetbrains.internal.JBRApi")
|
||||
.service("com.jetbrains.Jstack")
|
||||
.withStatic("includeInfoFrom", "$$jb$additionalInfoForJstack", "java.lang.Throwable");
|
||||
}
|
||||
}
|
||||
@@ -26,36 +26,45 @@
|
||||
package com.jetbrains.bootstrap;
|
||||
|
||||
import com.jetbrains.internal.JBRApi;
|
||||
import jdk.internal.loader.ClassLoaders;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Bootstrap class, used to initialize {@linkplain JBRApi JBR API}.
|
||||
* @deprecated replaced by {@link com.jetbrains.exported.JBRApiSupport}
|
||||
*/
|
||||
@Deprecated
|
||||
public class JBRApiBootstrap {
|
||||
private JBRApiBootstrap() {}
|
||||
|
||||
/**
|
||||
* Old version of bootstrap method without metadata parameter.
|
||||
* Classes that register JBR API implementation for their modules.
|
||||
*/
|
||||
private static final String[] MODULES = {
|
||||
"com.jetbrains.base.JBRApiModule",
|
||||
"com.jetbrains.desktop.JBRApiModule"
|
||||
};
|
||||
|
||||
/**
|
||||
* Called from static initializer of {@link com.jetbrains.JBR}.
|
||||
* @param outerLookup lookup context inside {@code jetbrains.api} module
|
||||
* @return implementation for {@link com.jetbrains.JBR.ServiceApi} interface
|
||||
*/
|
||||
public static synchronized Object bootstrap(MethodHandles.Lookup outerLookup) {
|
||||
if (!JBRApi.ENABLED) return null;
|
||||
if (Boolean.getBoolean("jetbrains.runtime.api.verbose")) {
|
||||
System.out.println("JBR API bootstrap in compatibility mode: Object bootstrap(MethodHandles.Lookup)");
|
||||
}
|
||||
Class<?> apiInterface;
|
||||
if (!System.getProperty("jetbrains.api.enabled", "true").equalsIgnoreCase("true")) return null;
|
||||
try {
|
||||
apiInterface = outerLookup.findClass("com.jetbrains.JBR$ServiceApi");
|
||||
} catch (ClassNotFoundException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Failed to retrieve JBR API metadata", e);
|
||||
Class<?> apiInterface = outerLookup.findClass("com.jetbrains.JBR$ServiceApi");
|
||||
if (!outerLookup.hasFullPrivilegeAccess() ||
|
||||
outerLookup.lookupClass().getPackage() != apiInterface.getPackage()) {
|
||||
throw new IllegalArgumentException("Lookup must be full-privileged from the com.jetbrains package: " +
|
||||
outerLookup.lookupClass().getName());
|
||||
}
|
||||
JBRApi.init(outerLookup);
|
||||
ClassLoader classLoader = ClassLoaders.platformClassLoader();
|
||||
for (String m : MODULES) Class.forName(m, true, classLoader);
|
||||
return JBRApi.getService(apiInterface);
|
||||
} catch(ClassNotFoundException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return com.jetbrains.exported.JBRApiSupport.bootstrap(apiInterface, null, null, null, Map.of(), m -> null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.jetbrains.exported;
|
||||
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* JBR API utility class.
|
||||
*/
|
||||
public class JBRApi {
|
||||
private JBRApi() {}
|
||||
|
||||
/**
|
||||
* Marks classes and interfaces whose implementation is provided by JBR API client.
|
||||
* These types must not be inherited by JBR unless explicitly marked with {@link Provides}.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Provided {
|
||||
/**
|
||||
* Binding target.
|
||||
* This is a fully qualified name of corresponding {@code @Provides} class/interface,
|
||||
* e.g. {@code com.jetbrains.My.Nested.Class}.
|
||||
* Package {@code com.jetbrains} may be omitted, e.g. {@code My.Nested.Class}, {@code MyClass#myMethod}.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks classes, interfaces and static methods which provide their functionality to JBR API.
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Provides {
|
||||
/**
|
||||
* Binding target.
|
||||
* For type binding this is a fully qualified name of corresponding {@code @Provided} class/interface,
|
||||
* e.g. {@code com.jetbrains.My.Nested.Class}.
|
||||
* For static method binding this is a fully qualified name of corresponding {@code @Provided} class/interface with optional
|
||||
* method name appended after '#', e.g. {@code com.jetbrains.MyClass#myMethod}.
|
||||
* If method name is omitted, name of annotated method itself is used.
|
||||
* In all cases, package {@code com.jetbrains} may be omitted, e.g. {@code My.Nested.Class}, {@code MyClass#myMethod}.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks JBR API service.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Service {}
|
||||
|
||||
/**
|
||||
* Creates new internal service instance of caller class type.
|
||||
* Intended usage: {@code public static final MyService INSTANCE = JBRApi.internalService();}
|
||||
* @return internal service instance
|
||||
*/
|
||||
@CallerSensitive
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T internalService() {
|
||||
var caller = (Class<T>) Reflection.getCallerClass();
|
||||
if (caller == null) throw new IllegalCallerException("No caller frame");
|
||||
return com.jetbrains.internal.JBRApi.getInternalService(caller);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a JBR API service has a defined implementation class, it is instantiated via static factory method
|
||||
* {@code create()} or a no-arg constructor. {@link ServiceNotAvailableException}
|
||||
* may be thrown from that constructor or factory method to indicate that service is unavailable for some reason.
|
||||
* Exception is not propagated to user, but rather {@code null} is returned for that service.
|
||||
* Optional message and cause can be specified, but only used for logging (when explicitly enabled by VM flag).
|
||||
*/
|
||||
public static class ServiceNotAvailableException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ServiceNotAvailableException() { super(); }
|
||||
|
||||
public ServiceNotAvailableException(String message) { super(message); }
|
||||
|
||||
public ServiceNotAvailableException(String message, Throwable cause) { super(message, cause); }
|
||||
|
||||
public ServiceNotAvailableException(Throwable cause) { super(cause); }
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.jetbrains.exported;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.invoke.CallSite;
|
||||
import java.lang.invoke.ConstantCallSite;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Supporting exported API for JBR API backend. Not intended to be used by JBR or client code directly.
|
||||
*/
|
||||
public class JBRApiSupport {
|
||||
private JBRApiSupport() {}
|
||||
|
||||
/**
|
||||
* Initializes JBR API.
|
||||
* @param apiInterface internal {@code JBR.ServiceApi} interface
|
||||
* @param serviceAnnotation {@code @Service} annotation class
|
||||
* @param providedAnnotation {@code @Provided} annotation class
|
||||
* @param providesAnnotation {@code @Provides} annotation class
|
||||
* @param knownExtensions map of known extension enums and classes defining them
|
||||
* @param extensionExtractor receives method, returns its extension enum, or null
|
||||
* @return implementation for {@code JBR.ServiceApi} interface
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static synchronized Object bootstrap(Class<?> apiInterface,
|
||||
Class<? extends Annotation> serviceAnnotation,
|
||||
Class<? extends Annotation> providedAnnotation,
|
||||
Class<? extends Annotation> providesAnnotation,
|
||||
Map<Enum<?>, Class[]> knownExtensions,
|
||||
Function<Method, Enum<?>> extensionExtractor) {
|
||||
if (!com.jetbrains.internal.JBRApi.ENABLED) return null;
|
||||
com.jetbrains.internal.JBRApi.init(
|
||||
null,
|
||||
serviceAnnotation,
|
||||
providedAnnotation,
|
||||
providesAnnotation,
|
||||
knownExtensions,
|
||||
extensionExtractor);
|
||||
return com.jetbrains.internal.JBRApi.getService(apiInterface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap method for JBR API dynamic invocations.
|
||||
*/
|
||||
public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
|
||||
return new ConstantCallSite(com.jetbrains.internal.JBRApi.bindDynamic(caller, name, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* JBR API proxy class. Every generated proxy class implements this interface.
|
||||
*/
|
||||
public interface Proxy {
|
||||
/**
|
||||
* We really don't want to clash with other possible method names.
|
||||
* @return target object
|
||||
*/
|
||||
Object $getProxyTarget();
|
||||
}
|
||||
}
|
||||
@@ -58,21 +58,23 @@ class ASMUtils {
|
||||
InternalMethodInfo methodInfo = getInternalMethodInfo(interfaceMethod);
|
||||
MethodVisitor p = writer.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(),
|
||||
methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames());
|
||||
p.visitCode();
|
||||
throwException(p, "java/lang/UnsupportedOperationException", "No implementation found for this method");
|
||||
p.visitMaxs(0, 0);
|
||||
p.visitEnd();
|
||||
}
|
||||
|
||||
public static void throwException(MethodVisitor p, String type, String message) {
|
||||
p.visitTypeInsn(NEW, type);
|
||||
p.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException");
|
||||
p.visitInsn(DUP);
|
||||
p.visitLdcInsn(message);
|
||||
p.visitMethodInsn(INVOKESPECIAL, type, "<init>", "(Ljava/lang/String;)V", false);
|
||||
p.visitLdcInsn("No implementation found for this method");
|
||||
p.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V", false);
|
||||
p.visitInsn(ATHROW);
|
||||
p.visitMaxs(-1, -1);
|
||||
}
|
||||
|
||||
public record InternalMethodInfo(String name, String descriptor, String genericSignature,
|
||||
public static void logDeprecated(MethodVisitor writer, String message) {
|
||||
writer.visitTypeInsn(NEW, "java/lang/Exception");
|
||||
writer.visitInsn(DUP);
|
||||
writer.visitLdcInsn(message);
|
||||
writer.visitMethodInsn(INVOKESPECIAL, "java/lang/Exception", "<init>", "(Ljava/lang/String;)V", false);
|
||||
writer.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false);
|
||||
}
|
||||
|
||||
protected record InternalMethodInfo(String name, String descriptor, String genericSignature,
|
||||
String[] exceptionNames) {}
|
||||
|
||||
public static InternalMethodInfo getInternalMethodInfo(Method method) {
|
||||
@@ -119,7 +121,7 @@ class ASMUtils {
|
||||
return IRETURN + getOpcodeOffset(c);
|
||||
}
|
||||
|
||||
private static int getOpcodeOffset(Class<?> c) {
|
||||
public static int getOpcodeOffset(Class<?> c) {
|
||||
if (c.isPrimitive()) {
|
||||
if (c == Long.TYPE) {
|
||||
return 1;
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
package com.jetbrains.internal;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.Handle;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandleInfo;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.lang.invoke.MethodHandles.Lookup;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC;
|
||||
import static jdk.internal.org.objectweb.asm.Type.getInternalName;
|
||||
|
||||
/**
|
||||
* Context used by {@link ProxyGenerator} to determine whether class or method is
|
||||
* accessible from a caller class and generate appropriate direct or dynamic calls.
|
||||
*/
|
||||
class AccessContext {
|
||||
|
||||
private final Map<Class<?>, Boolean> accessibleClasses = new HashMap<>();
|
||||
final Map<Proxy, Boolean> dependencies = new HashMap<>(); // true for required, false for optional
|
||||
final List<DynamicCallTarget> dynamicCallTargets = new ArrayList<>();
|
||||
final Lookup caller;
|
||||
|
||||
AccessContext(Lookup caller) {
|
||||
this.caller = caller;
|
||||
}
|
||||
|
||||
record DynamicCallTarget(String name, String descriptor, Supplier<MethodHandle> futureHandle) {}
|
||||
|
||||
class Method {
|
||||
final MethodVisitor writer;
|
||||
private final boolean methodRequired;
|
||||
|
||||
Method(MethodVisitor writer, boolean methodRequired) {
|
||||
this.writer = writer;
|
||||
this.methodRequired = methodRequired;
|
||||
}
|
||||
|
||||
AccessContext access() {
|
||||
return AccessContext.this;
|
||||
}
|
||||
|
||||
void addDependency(Proxy p) {
|
||||
if (methodRequired) dependencies.put(p, true);
|
||||
else dependencies.putIfAbsent(p, false);
|
||||
}
|
||||
|
||||
void invokeDynamic(MethodHandle handle) {
|
||||
invokeDynamic(handle.type(), () -> handle);
|
||||
}
|
||||
|
||||
void invokeDynamic(MethodType type, Supplier<MethodHandle> futureHandle) {
|
||||
String descriptor = erase(type).descriptorString();
|
||||
DynamicCallTarget t = new DynamicCallTarget("dynamic" + dynamicCallTargets.size(), descriptor, futureHandle);
|
||||
dynamicCallTargets.add(t);
|
||||
writer.visitInvokeDynamicInsn(t.name, descriptor,
|
||||
new Handle(H_INVOKESTATIC, "com/jetbrains/exported/JBRApiSupport", "bootstrapDynamic",
|
||||
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false));
|
||||
}
|
||||
|
||||
void invokeDirect(MethodHandleInfo handleInfo) {
|
||||
int opcode = switch (handleInfo.getReferenceKind()) {
|
||||
default -> throw new RuntimeException("Unknown reference type");
|
||||
case MethodHandleInfo.REF_getField -> GETFIELD;
|
||||
case MethodHandleInfo.REF_getStatic -> GETSTATIC;
|
||||
case MethodHandleInfo.REF_putField -> PUTFIELD;
|
||||
case MethodHandleInfo.REF_putStatic -> PUTSTATIC;
|
||||
case MethodHandleInfo.REF_invokeVirtual -> INVOKEVIRTUAL;
|
||||
case MethodHandleInfo.REF_invokeStatic -> INVOKESTATIC;
|
||||
case MethodHandleInfo.REF_invokeSpecial -> INVOKESPECIAL;
|
||||
case MethodHandleInfo.REF_newInvokeSpecial -> NEW;
|
||||
case MethodHandleInfo.REF_invokeInterface -> INVOKEINTERFACE;
|
||||
};
|
||||
String targetName = getInternalName(handleInfo.getDeclaringClass());
|
||||
if (opcode == NEW) {
|
||||
writer.visitTypeInsn(NEW, targetName);
|
||||
writer.visitInsn(DUP);
|
||||
opcode = INVOKESPECIAL;
|
||||
}
|
||||
switch (opcode) {
|
||||
case GETFIELD, GETSTATIC -> writer.visitFieldInsn(opcode, targetName, handleInfo.getName(),
|
||||
handleInfo.getMethodType().returnType().descriptorString());
|
||||
case PUTFIELD, PUTSTATIC -> writer.visitFieldInsn(opcode, targetName, handleInfo.getName(),
|
||||
handleInfo.getMethodType().parameterType(0).descriptorString());
|
||||
default -> writer.visitMethodInsn(opcode, targetName, handleInfo.getName(),
|
||||
handleInfo.getMethodType().descriptorString(), handleInfo.getDeclaringClass().isInterface());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MethodHandleInfo resolveDirect(MethodHandle handle) {
|
||||
try {
|
||||
MethodHandleInfo handleInfo = caller.revealDirect(handle);
|
||||
if (isClassLoaderAccessible(caller.lookupClass().getClassLoader(),
|
||||
handleInfo.getDeclaringClass().getClassLoader())) {
|
||||
// Accessible directly.
|
||||
return handleInfo;
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
// Not accessible directly.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all inaccessible types with {@link Object}.
|
||||
* @param type original method type
|
||||
* @return erased method type
|
||||
*/
|
||||
private MethodType erase(MethodType type) {
|
||||
if (!canAccess(type.returnType())) type = type.changeReturnType(Object.class);
|
||||
for (int i = 0; i < type.parameterCount(); i++) {
|
||||
if (!canAccess(type.parameterType(i))) type = type.changeParameterType(i, Object.class);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
boolean canAccess(Class<?> clazz) {
|
||||
return accessibleClasses.computeIfAbsent(clazz, c -> canAccess(caller, c));
|
||||
}
|
||||
|
||||
static boolean canAccess(Lookup caller, Class<?> target) {
|
||||
try {
|
||||
if (isClassLoaderAccessible(caller.lookupClass().getClassLoader(),
|
||||
caller.accessClass(target).getClassLoader())) return true;
|
||||
} catch (IllegalAccessException ignore) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isClassLoaderAccessible(ClassLoader caller, ClassLoader target) {
|
||||
if (target == null) return true;
|
||||
for (ClassLoader cl = caller; cl != null; cl = cl.getParent()) {
|
||||
if (cl == target) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -25,196 +25,328 @@
|
||||
|
||||
package com.jetbrains.internal;
|
||||
|
||||
import com.jetbrains.exported.JBRApi.Provides;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.io.Serial;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static java.lang.invoke.MethodHandles.Lookup;
|
||||
|
||||
/**
|
||||
* JBR API is a collection of JBR-specific features that are accessed by client though
|
||||
* {@link com.jetbrains.JBR jetbrains.runtime.api} module. Actual implementation is linked by
|
||||
* {@link com.jetbrains.JBR jetbrains.api} module. Actual implementation is linked by
|
||||
* JBR at runtime by generating {@linkplain Proxy proxy objects}.
|
||||
* Mapping between interfaces and implementation code is defined using
|
||||
* {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations.
|
||||
* Mapping between interfaces and implementation code is defined in
|
||||
* {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}.
|
||||
* <p>
|
||||
* This class is an entry point into JBR API backend.
|
||||
* @see Proxy
|
||||
* This class has most basic methods for working with JBR API and cache of generated proxies.
|
||||
* <p>
|
||||
* <h2>How to add a new service</h2>
|
||||
* <ol>
|
||||
* <li>Create your service interface in module {@link com.jetbrains.JBR jetbrains.api}:
|
||||
* <blockquote><pre>{@code
|
||||
* package com.jetbrains;
|
||||
*
|
||||
* interface StringOptimizer {
|
||||
* void optimize(String string);
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
* </li>
|
||||
* <li>Create an implementation inside JBR:
|
||||
* <blockquote><pre>{@link java.lang.String java.lang.String}:{@code
|
||||
* private static void optimizeInternal(String string) {
|
||||
* string.hash = 0;
|
||||
* string.hashIsZero = true;
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
* </li>
|
||||
* <li>Register your service in corresponding
|
||||
* {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES module registry class}:
|
||||
* <blockquote><pre>{@link com.jetbrains.base.JBRApiModule}:{@code
|
||||
* .service("com.jetbrains.StringOptimizer", null)
|
||||
* .withStatic("optimize", "java.lang.String", "optimizeInternal")
|
||||
* }</pre></blockquote>
|
||||
* </li>
|
||||
* <li>You can also bind the interface to implementation class
|
||||
* (without actually implementing the interface):
|
||||
* <blockquote><pre>{@link java.lang.String java.lang.String}:{@code
|
||||
* private static class StringOptimizerImpl {
|
||||
*
|
||||
* private void optimize(String string) {
|
||||
* string.hash = 0;
|
||||
* string.hashIsZero = true;
|
||||
* }
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
* <blockquote><pre>{@link com.jetbrains.base.JBRApiModule}:{@code
|
||||
* .service("com.jetbrains.StringOptimizer", "java.lang.String$StringOptimizerImpl")
|
||||
* }</pre></blockquote>
|
||||
* </li>
|
||||
* </ol>
|
||||
* <h2>How to add a new proxy</h2>
|
||||
* Registering a proxy is similar to registering a service:
|
||||
* <blockquote><pre>{@code
|
||||
* .proxy("com.jetbrains.SomeProxy", "a.b.c.SomeProxyImpl")
|
||||
* }</pre></blockquote>
|
||||
* Note that unlike service, proxy <b>must</b> have a target type.
|
||||
* Also, proxy expects target object as a single constructor argument
|
||||
* and can only be instantiated by JBR, there's no API that would allow
|
||||
* user to directly create proxy object.
|
||||
*/
|
||||
public class JBRApi {
|
||||
/**
|
||||
* Enable JBR API, it wouldn't init when disabled. Enabled by default.
|
||||
*/
|
||||
public static final boolean ENABLED = Utils.property("jetbrains.runtime.api.enabled", true);
|
||||
/**
|
||||
* Enable API extensions. When disabled, extension methods are treated like any other method,
|
||||
* {@link JBRApi#isExtensionSupported} always returns false, {@link JBRApi#getService(Class, Enum[])}
|
||||
* behaves the same as {@link JBRApi#getService(Class)}. Enabled by default.
|
||||
*/
|
||||
static final boolean EXTENSIONS_ENABLED = Utils.property("jetbrains.runtime.api.extensions.enabled", true);
|
||||
/**
|
||||
* Enable extensive debugging logging. Disabled by default.
|
||||
*/
|
||||
static final boolean VERBOSE = Utils.property("jetbrains.runtime.api.verbose", false);
|
||||
/**
|
||||
* Print warnings about usage of deprecated interfaces and methods to {@link System#err}. Enabled by default.
|
||||
*/
|
||||
static final boolean LOG_DEPRECATED = Utils.property("jetbrains.runtime.api.logDeprecated", true);
|
||||
/**
|
||||
* Enable additional verification of generated bytecode. Disabled by default.
|
||||
*/
|
||||
static final boolean VERIFY_BYTECODE = Utils.property("jetbrains.runtime.api.verifyBytecode", false);
|
||||
/**
|
||||
* Allow extending registry. Disabled by default, used for tests.
|
||||
*/
|
||||
private static final boolean EXTEND_REGISTRY = Utils.property("jetbrains.runtime.api.extendRegistry", false);
|
||||
@SuppressWarnings("removal")
|
||||
static final boolean VERBOSE = AccessController.doPrivileged(new GetBooleanAction("jetbrains.api.verbose"));
|
||||
|
||||
record DynamicCallTargetKey(Class<?> proxy, String name, String descriptor) {}
|
||||
static final ConcurrentMap<DynamicCallTargetKey, Supplier<MethodHandle>> dynamicCallTargets = new ConcurrentHashMap<>();
|
||||
private static final ProxyRepository proxyRepository = new ProxyRepository();
|
||||
private static final Map<String, RegisteredProxyInfo> registeredProxyInfoByInterfaceName = new HashMap<>();
|
||||
private static final Map<String, RegisteredProxyInfo> registeredProxyInfoByTargetName = new HashMap<>();
|
||||
private static final ConcurrentMap<Class<?>, Proxy<?>> proxyByInterface = new ConcurrentHashMap<>();
|
||||
|
||||
private static Boolean[] supportedExtensions;
|
||||
private static long[] emptyExtensionsBitfield;
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static Map<Enum<?>, Class[]> knownExtensions;
|
||||
static Function<Method, Enum<?>> extensionExtractor;
|
||||
/**
|
||||
* lookup context inside {@code jetbrains.api} module
|
||||
*/
|
||||
static Lookup outerLookup;
|
||||
/**
|
||||
* Known service and proxy interfaces extracted from {@link com.jetbrains.JBR.Metadata}
|
||||
*/
|
||||
static Set<String> knownServices, knownProxies;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void init(InputStream extendedRegistryStream,
|
||||
Class<? extends Annotation> serviceAnnotation,
|
||||
Class<? extends Annotation> providedAnnotation,
|
||||
Class<? extends Annotation> providesAnnotation,
|
||||
Map<Enum<?>, Class[]> knownExtensions,
|
||||
Function<Method, Enum<?>> extensionExtractor) {
|
||||
if (extendedRegistryStream != null && !EXTEND_REGISTRY) {
|
||||
throw new Error("Extending JBR API registry is not supported");
|
||||
public static void init(Lookup outerLookup) {
|
||||
JBRApi.outerLookup = outerLookup;
|
||||
try {
|
||||
Class<?> metadataClass = outerLookup.findClass("com.jetbrains.JBR$Metadata");
|
||||
Lookup lookup = MethodHandles.privateLookupIn(metadataClass, outerLookup);
|
||||
knownServices = Set.of((String[]) lookup.findStaticVarHandle(metadataClass,
|
||||
"KNOWN_SERVICES", String[].class).get());
|
||||
knownProxies = Set.of((String[]) lookup.findStaticVarHandle(metadataClass,
|
||||
"KNOWN_PROXIES", String[].class).get());
|
||||
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
knownServices = Set.of();
|
||||
knownProxies = Set.of();
|
||||
}
|
||||
proxyRepository.init(extendedRegistryStream, serviceAnnotation, providedAnnotation, providesAnnotation);
|
||||
|
||||
if (EXTENSIONS_ENABLED) {
|
||||
JBRApi.knownExtensions = knownExtensions;
|
||||
JBRApi.extensionExtractor = extensionExtractor;
|
||||
supportedExtensions = new Boolean[
|
||||
knownExtensions.keySet().stream().mapToInt(Enum::ordinal).max().orElse(-1) + 1];
|
||||
emptyExtensionsBitfield = new long[(supportedExtensions.length + 63) / 64];
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("JBR API init\n knownExtensions = " + (EXTENSIONS_ENABLED ? knownExtensions.keySet() : "DISABLED"));
|
||||
System.out.println("JBR API init\nKNOWN_SERVICES = " + knownServices + "\nKNOWN_PROXIES = " + knownProxies);
|
||||
}
|
||||
}
|
||||
|
||||
public static MethodHandle bindDynamic(Lookup caller, String name, MethodType type) {
|
||||
if (!caller.hasFullPrivilegeAccess()) throw new Error("Caller lookup must have full privilege access"); // Authenticity check.
|
||||
return dynamicCallTargets.remove(new DynamicCallTargetKey(caller.lookupClass(), name, type.descriptorString())).get().asType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JBR API version supported by current implementation.
|
||||
*/
|
||||
@Provides("JBR.ServiceApi")
|
||||
public static String getImplVersion() {
|
||||
return proxyRepository.getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param extension extension name
|
||||
* @return true if all methods belonging to given extension are supported
|
||||
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
|
||||
* service, but is not directly exposed to user.
|
||||
*/
|
||||
@Provides("JBR.ServiceApi")
|
||||
public static boolean isExtensionSupported(Enum<?> extension) {
|
||||
if (!EXTENSIONS_ENABLED) return false;
|
||||
int i = extension.ordinal();
|
||||
if (supportedExtensions[i] == null) {
|
||||
synchronized (JBRApi.class) {
|
||||
if (supportedExtensions[i] == null) {
|
||||
boolean result = true;
|
||||
for (Class<?> c : knownExtensions.get(extension)) {
|
||||
result &= proxyRepository.getProxy(c, null).isExtensionSupported(extension);
|
||||
}
|
||||
supportedExtensions[i] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return supportedExtensions[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return fully supported service implementation for the given interface with specified extensions, or null
|
||||
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
|
||||
* service, but is not directly exposed to user.
|
||||
*/
|
||||
@Provides("JBR.ServiceApi")
|
||||
public static <T> T getService(Class<T> interFace, Enum<?>... extensions) {
|
||||
if (!EXTENSIONS_ENABLED) return getService(interFace);
|
||||
|
||||
long[] bitfield = new long[emptyExtensionsBitfield.length];
|
||||
for (Enum<?> e : extensions) {
|
||||
if (isExtensionSupported(e)) {
|
||||
int i = e.ordinal() / 64;
|
||||
int j = e.ordinal() % 64;
|
||||
bitfield[i] |= 1L << j;
|
||||
} else {
|
||||
if (VERBOSE) {
|
||||
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Extension not supported: " + e.name());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return getService(interFace, bitfield, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return fully supported service implementation for the given interface, or null
|
||||
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
|
||||
* service, but is not directly exposed to user.
|
||||
*/
|
||||
@Provides("JBR.ServiceApi")
|
||||
public static <T> T getService(Class<T> interFace) {
|
||||
return getService(interFace, emptyExtensionsBitfield, true);
|
||||
}
|
||||
|
||||
public static <T> T getInternalService(Class<T> interFace) {
|
||||
return getService(interFace, emptyExtensionsBitfield, false);
|
||||
Proxy<T> p = getProxy(interFace);
|
||||
return p != null && p.isSupported() ? p.getInstance() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return proxy for the given interface, or {@code null}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getService(Class<T> interFace, long[] extensions, boolean publicService) {
|
||||
Proxy p = proxyRepository.getProxy(interFace, null);
|
||||
if ((p.getFlags() & Proxy.SERVICE) == 0 || (publicService && (p.getFlags() & Proxy.INTERNAL) != 0)) {
|
||||
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Not allowed as a service: " + interFace.getCanonicalName());
|
||||
return null;
|
||||
}
|
||||
if (!p.init()) {
|
||||
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Service not supported: " + interFace.getCanonicalName());
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
MethodHandle constructor = p.getConstructor();
|
||||
return (T) (EXTENSIONS_ENABLED ? constructor.invoke(extensions) : constructor.invoke());
|
||||
} catch (com.jetbrains.exported.JBRApi.ServiceNotAvailableException | NullPointerException e) {
|
||||
if (VERBOSE) {
|
||||
synchronized (System.err) {
|
||||
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Service not available: " + interFace.getCanonicalName());
|
||||
System.err.print("Caused by: ");
|
||||
e.printStackTrace(System.err);
|
||||
static <T> Proxy<T> getProxy(Class<T> interFace) {
|
||||
return (Proxy<T>) proxyByInterface.computeIfAbsent(interFace, i -> {
|
||||
RegisteredProxyInfo info = registeredProxyInfoByInterfaceName.get(i.getName());
|
||||
if (info == null) return null;
|
||||
ProxyInfo resolved = ProxyInfo.resolve(info);
|
||||
if (resolved == null) {
|
||||
if (VERBOSE) {
|
||||
System.err.println("Couldn't resolve proxy info: " + i.getName());
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
return null;
|
||||
} else return new Proxy<>(resolved);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if given class represents a proxy interface. Even if {@code jetbrains.api}
|
||||
* introduces new interfaces JBR is not aware of, these interfaces would still be detected
|
||||
* by this method.
|
||||
*/
|
||||
static boolean isKnownProxyInterface(Class<?> clazz) {
|
||||
String name = clazz.getName();
|
||||
return registeredProxyInfoByInterfaceName.containsKey(name) ||
|
||||
knownServices.contains(name) || knownProxies.contains(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse lookup by proxy target type name.
|
||||
* @return user-side interface for given implementation target type name.
|
||||
*/
|
||||
static Class<?> getProxyInterfaceByTargetName(String targetName) {
|
||||
RegisteredProxyInfo info = registeredProxyInfoByTargetName.get(targetName);
|
||||
if (info == null) return null;
|
||||
try {
|
||||
return Class.forName(info.interfaceName(), true,
|
||||
(info.type().isPublicApi() ? outerLookup : info.apiModule()).lookupClass().getClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (VERBOSE) e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static InternalServiceBuilder internalServiceBuilder(Lookup interFace, String... targets) {
|
||||
return new InternalServiceBuilder(new RegisteredProxyInfo(
|
||||
interFace, interFace.lookupClass().getName(), targets, ProxyInfo.Type.INTERNAL_SERVICE, new ArrayList<>()));
|
||||
}
|
||||
|
||||
public static class InternalServiceBuilder {
|
||||
|
||||
private final RegisteredProxyInfo info;
|
||||
|
||||
private InternalServiceBuilder(RegisteredProxyInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public InternalServiceBuilder withStatic(String interfaceMethodName, String methodName, String... classes) {
|
||||
info.staticMethods().add(
|
||||
new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, methodName, classes));
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public Object build() {
|
||||
return AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
|
||||
ProxyInfo info = ProxyInfo.resolve(this.info);
|
||||
if (info == null) return null;
|
||||
ProxyGenerator generator = new ProxyGenerator(info);
|
||||
if (!generator.areAllMethodsImplemented()) return null;
|
||||
generator.defineClasses();
|
||||
MethodHandle constructor = generator.findConstructor();
|
||||
generator.init();
|
||||
try {
|
||||
return constructor.invoke();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}
|
||||
* to register a new mapping for corresponding modules.
|
||||
*/
|
||||
public static ModuleRegistry registerModule(Lookup lookup, BiFunction<String, Module, Module> addExports) {
|
||||
addExports.apply(lookup.lookupClass().getPackageName(), outerLookup.lookupClass().getModule());
|
||||
return new ModuleRegistry(lookup);
|
||||
}
|
||||
|
||||
public static class ModuleRegistry {
|
||||
|
||||
private final Lookup lookup;
|
||||
private RegisteredProxyInfo lastProxy;
|
||||
|
||||
private ModuleRegistry(Lookup lookup) {
|
||||
this.lookup = lookup;
|
||||
}
|
||||
|
||||
private ModuleRegistry addProxy(ProxyInfo.Type type, String interfaceName, String... targets) {
|
||||
lastProxy = new RegisteredProxyInfo(lookup, interfaceName, targets, type, new ArrayList<>());
|
||||
registeredProxyInfoByInterfaceName.put(interfaceName, lastProxy);
|
||||
for (String target : targets) {
|
||||
registeredProxyInfoByTargetName.put(target, lastProxy);
|
||||
}
|
||||
if (targets.length == 1) {
|
||||
validate2WayMapping(lastProxy, registeredProxyInfoByInterfaceName.get(targets[0]));
|
||||
validate2WayMapping(lastProxy, registeredProxyInfoByTargetName.get(interfaceName));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
private static void validate2WayMapping(RegisteredProxyInfo p, RegisteredProxyInfo reverse) {
|
||||
if (reverse != null &&
|
||||
(!p.interfaceName().equals(reverse.targets()[0]) || !p.targets()[0].equals(reverse.interfaceName()))) {
|
||||
throw new IllegalArgumentException("Invalid 2-way proxy mapping: " +
|
||||
p.interfaceName() + " -> " + p.targets()[0] + " & " +
|
||||
reverse.interfaceName() + " -> " + reverse.targets()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new {@linkplain ProxyInfo.Type#PROXY proxy} mapping.
|
||||
* <p>
|
||||
* When {@code target} object is passed from JBR to client through service or any other proxy,
|
||||
* it's converted to corresponding {@code interFace} type by creating a proxy object
|
||||
* that implements {@code interFace} and delegates method calls to {@code target}.
|
||||
* @param interFace interface name in {@link com.jetbrains.JBR jetbrains.api} module.
|
||||
* @param targets corresponding class/interface names in current JBR module, first found will be used. This must not be empty.
|
||||
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
|
||||
*/
|
||||
public ModuleRegistry proxy(String interFace, String... targets) {
|
||||
if (targets.length == 0) throw new IllegalArgumentException("Proxy must have at least one target");
|
||||
return addProxy(ProxyInfo.Type.PROXY, interFace, targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new {@linkplain ProxyInfo.Type#SERVICE service} mapping.
|
||||
* <p>
|
||||
* Service is a singleton, which may be accessed by client using {@link com.jetbrains.JBR} class.
|
||||
* @param interFace interface name in {@link com.jetbrains.JBR jetbrains.api} module.
|
||||
* @param targets corresponding implementation class names in current JBR module, first found will be used.
|
||||
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
|
||||
*/
|
||||
public ModuleRegistry service(String interFace, String... targets) {
|
||||
return addProxy(ProxyInfo.Type.SERVICE, interFace, targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new {@linkplain ProxyInfo.Type#CLIENT_PROXY client proxy} mapping.
|
||||
* This mapping type allows implementation of callbacks.
|
||||
* <p>
|
||||
* When {@code target} object is passed from client to JBR through service or any other proxy,
|
||||
* it's converted to corresponding {@code interFace} type by creating a proxy object
|
||||
* that implements {@code interFace} and delegates method calls to {@code target}.
|
||||
* @param interFace interface name in current JBR module.
|
||||
* @param target corresponding class/interface name in {@link com.jetbrains.JBR jetbrains.api} module.
|
||||
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
|
||||
*/
|
||||
public ModuleRegistry clientProxy(String interFace, String target) {
|
||||
Objects.requireNonNull(target);
|
||||
return addProxy(ProxyInfo.Type.CLIENT_PROXY, interFace, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new 2-way mapping.
|
||||
* It creates both {@linkplain ProxyInfo.Type#PROXY proxy} and
|
||||
* {@linkplain ProxyInfo.Type#CLIENT_PROXY client proxy} between given interfaces.
|
||||
* <p>
|
||||
* It links together two given interfaces and allows passing such objects back and forth
|
||||
* between JBR and {@link com.jetbrains.JBR jetbrains.api} module through services and other proxy methods.
|
||||
* @param apiInterface interface name in {@link com.jetbrains.JBR jetbrains.api} module.
|
||||
* @param jbrInterface interface name in current JBR module.
|
||||
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
|
||||
*/
|
||||
public ModuleRegistry twoWayProxy(String apiInterface, String jbrInterface) {
|
||||
clientProxy(jbrInterface, apiInterface);
|
||||
proxy(apiInterface, jbrInterface);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate "{@code interfaceMethodName}" method calls to first found static "{@code methodName}" in "{@code classes}".
|
||||
*/
|
||||
public ModuleRegistry withStatic(String interfaceMethodName, String methodName, String... classes) {
|
||||
lastProxy.staticMethods().add(
|
||||
new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, methodName, classes));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by service implementations indicating that the service is not available for some reason
|
||||
*/
|
||||
public static class ServiceNotAvailableException extends RuntimeException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
public ServiceNotAvailableException() { super(); }
|
||||
public ServiceNotAvailableException(String message) { super(message); }
|
||||
public ServiceNotAvailableException(String message, Throwable cause) { super(message, cause); }
|
||||
public ServiceNotAvailableException(Throwable cause) { super(cause); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,433 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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.internal;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.Label;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import static jdk.internal.org.objectweb.asm.Type.getInternalName;
|
||||
|
||||
/**
|
||||
* Mapping defines conversion of parameters and return types between source and destination method.
|
||||
*/
|
||||
abstract class Mapping {
|
||||
|
||||
static class Query {
|
||||
boolean valid = true;
|
||||
boolean needsExtensions = false;
|
||||
}
|
||||
|
||||
final Class<?> from, to;
|
||||
|
||||
private Mapping(Class<?> from, Class<?> to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
void convert(AccessContext.Method context) {
|
||||
MethodVisitor m = context.writer;
|
||||
Label skipConvert = new Label();
|
||||
m.visitInsn(DUP);
|
||||
m.visitJumpInsn(IFNULL, skipConvert);
|
||||
convertNonNull(context);
|
||||
m.visitLabel(skipConvert);
|
||||
}
|
||||
|
||||
abstract void convertNonNull(AccessContext.Method context);
|
||||
|
||||
abstract Mapping inverse();
|
||||
|
||||
void query(Query q) {}
|
||||
|
||||
@Override
|
||||
public abstract boolean equals(Object obj);
|
||||
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
|
||||
static class Identity extends Mapping {
|
||||
private Identity(Class<?> c) {
|
||||
super(c, c);
|
||||
}
|
||||
@Override
|
||||
void convert(AccessContext.Method context) {}
|
||||
@Override
|
||||
void convertNonNull(AccessContext.Method context) {}
|
||||
@Override
|
||||
Mapping inverse() { return this; }
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Identity i = (Identity) o;
|
||||
return from.equals(i.from);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() { return from.hashCode(); }
|
||||
@Override
|
||||
public String toString() { return from.getName(); }
|
||||
}
|
||||
|
||||
static class Invalid extends Identity {
|
||||
private Invalid(Class<?> c) {
|
||||
super(c);
|
||||
}
|
||||
@Override
|
||||
void query(Query q) { q.valid = false; }
|
||||
@Override
|
||||
public String toString() { return "INVALID(" + from.getName() + ")"; }
|
||||
}
|
||||
|
||||
static abstract class Nesting extends Mapping {
|
||||
final Mapping component;
|
||||
Nesting(Class<?> from, Class<?> to, Mapping component) {
|
||||
super(from, to);
|
||||
this.component = component;
|
||||
}
|
||||
@Override
|
||||
void query(Query q) { component.query(q); }
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Nesting nesting = (Nesting) o;
|
||||
if (!Objects.equals(from, nesting.from)) return false;
|
||||
if (!Objects.equals(to, nesting.to)) return false;
|
||||
return component.equals(nesting.component);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = from != null ? from.hashCode() : 0;
|
||||
result = 31 * result + (to != null ? to.hashCode() : 0);
|
||||
result = 31 * result + component.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static class Array extends Nesting {
|
||||
private Array(Mapping component) {
|
||||
super(component.from.arrayType(), component.to.arrayType(), component);
|
||||
}
|
||||
static Mapping wrap(Mapping m) {
|
||||
if (m instanceof Identity) return new Identity(m.from.arrayType());
|
||||
else return new Array(m);
|
||||
}
|
||||
@Override
|
||||
void convertNonNull(AccessContext.Method context) {
|
||||
final int TEMP_COUNTER_SLOT = 1; // Warning! We overwrite 1st local slot.
|
||||
MethodVisitor m = context.writer;
|
||||
Label loopStart = new Label(), loopEnd = new Label();
|
||||
// Stack: fromArray -> toArray, fromArray, i=length
|
||||
m.visitInsn(DUP);
|
||||
m.visitInsn(ARRAYLENGTH);
|
||||
if (context.access().canAccess(to)) m.visitTypeInsn(ANEWARRAY, getInternalName(to));
|
||||
else context.invokeDynamic(MethodHandles.arrayConstructor(to));
|
||||
m.visitInsn(SWAP);
|
||||
m.visitInsn(DUP);
|
||||
m.visitInsn(ARRAYLENGTH);
|
||||
// Check loop conditions
|
||||
m.visitLabel(loopStart);
|
||||
m.visitInsn(DUP);
|
||||
m.visitJumpInsn(IFLE, loopEnd);
|
||||
// Stack: toArray, fromArray, i -> toArray, fromArray, i, toArray, i, from
|
||||
m.visitVarInsn(ISTORE, TEMP_COUNTER_SLOT);
|
||||
m.visitInsn(DUP2);
|
||||
m.visitIincInsn(TEMP_COUNTER_SLOT, -1);
|
||||
m.visitVarInsn(ILOAD, TEMP_COUNTER_SLOT);
|
||||
m.visitInsn(DUP_X2);
|
||||
m.visitInsn(DUP_X1);
|
||||
m.visitInsn(AALOAD);
|
||||
// Stack from -> to
|
||||
component.convert(context);
|
||||
// Stack: toArray, fromArray, i, toArray, i, to -> toArray, fromArray, i
|
||||
m.visitInsn(AASTORE);
|
||||
m.visitJumpInsn(GOTO, loopStart);
|
||||
m.visitLabel(loopEnd);
|
||||
// Stack: toArray, fromArray, i -> toArray
|
||||
m.visitInsn(POP2);
|
||||
}
|
||||
@Override
|
||||
Mapping inverse() { return new Array(component.inverse()); }
|
||||
@Override
|
||||
public String toString() { return "[" + component + "]"; }
|
||||
}
|
||||
|
||||
private static abstract class ProxyConversion extends Mapping {
|
||||
final Proxy fromProxy, toProxy;
|
||||
private ProxyConversion(Class<?> from, Class<?> to, Proxy fromProxy, Proxy toProxy) {
|
||||
super(from, to);
|
||||
this.fromProxy = fromProxy;
|
||||
this.toProxy = toProxy;
|
||||
}
|
||||
void wrapNonNull(AccessContext.Method context) {
|
||||
context.addDependency(toProxy);
|
||||
MethodType mt;
|
||||
if (JBRApi.EXTENSIONS_ENABLED) {
|
||||
context.writer.visitVarInsn(ALOAD, 0);
|
||||
mt = MethodType.methodType(to, from, long[].class);
|
||||
} else {
|
||||
mt = MethodType.methodType(to, from);
|
||||
}
|
||||
context.invokeDynamic(mt, toProxy::getConstructor);
|
||||
}
|
||||
void extractNonNull(AccessContext.Method context) {
|
||||
context.addDependency(fromProxy);
|
||||
context.writer.visitMethodInsn(INVOKEINTERFACE,
|
||||
"com/jetbrains/exported/JBRApiSupport$Proxy", "$getProxyTarget", "()Ljava/lang/Object;", true);
|
||||
}
|
||||
void cast(AccessContext.Method context) {
|
||||
if (context.access().canAccess(to)) context.writer.visitTypeInsn(CHECKCAST, getInternalName(to));
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ProxyConversion i = (ProxyConversion) o;
|
||||
return from.equals(i.from) && to.equals(i.to) &&
|
||||
Objects.equals(fromProxy, i.fromProxy) && Objects.equals(toProxy, i.toProxy);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = from.hashCode();
|
||||
result = 31 * result + to.hashCode();
|
||||
result = 31 * result + Objects.hashCode(fromProxy);
|
||||
result = 31 * result + Objects.hashCode(toProxy);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Wrap extends ProxyConversion {
|
||||
private Wrap(Class<?> from, Class<?> to, Proxy proxy) {
|
||||
super(from, to, null, proxy);
|
||||
}
|
||||
@Override
|
||||
void convertNonNull(AccessContext.Method context) { wrapNonNull(context); }
|
||||
@Override
|
||||
Mapping inverse() { return new Extract(to, from, toProxy); }
|
||||
@Override
|
||||
void query(Query q) { q.needsExtensions = JBRApi.EXTENSIONS_ENABLED; }
|
||||
@Override
|
||||
public String toString() { return from.getName() + " --wrap-> " + to.getName(); }
|
||||
}
|
||||
|
||||
private static class Extract extends ProxyConversion {
|
||||
private Extract(Class<?> from, Class<?> to, Proxy proxy) {
|
||||
super(from, to, proxy, null);
|
||||
}
|
||||
@Override
|
||||
void convert(AccessContext.Method context) {
|
||||
super.convert(context);
|
||||
cast(context); // Explicitly cast to result type after non-null branch
|
||||
}
|
||||
@Override
|
||||
void convertNonNull(AccessContext.Method context) { extractNonNull(context); }
|
||||
@Override
|
||||
Mapping inverse() { return new Wrap(to, from, fromProxy); }
|
||||
@Override
|
||||
public String toString() { return from.getName() + " --extract-> " + to.getName(); }
|
||||
}
|
||||
|
||||
private static class Dynamic2Way extends ProxyConversion {
|
||||
private Dynamic2Way(Class<?> from, Class<?> to, Proxy fromProxy, Proxy toProxy) {
|
||||
super(from, to, fromProxy, toProxy);
|
||||
}
|
||||
@Override
|
||||
void convert(AccessContext.Method context) {
|
||||
Label elseBranch = new Label(), afterBranch = new Label();
|
||||
MethodVisitor m = context.writer;
|
||||
m.visitInsn(DUP);
|
||||
m.visitJumpInsn(IFNULL, afterBranch);
|
||||
m.visitInsn(DUP);
|
||||
m.visitTypeInsn(INSTANCEOF, "com/jetbrains/exported/JBRApiSupport$Proxy");
|
||||
m.visitJumpInsn(IFEQ, elseBranch);
|
||||
extractNonNull(context);
|
||||
m.visitJumpInsn(GOTO, afterBranch);
|
||||
m.visitLabel(elseBranch);
|
||||
wrapNonNull(context);
|
||||
m.visitLabel(afterBranch);
|
||||
cast(context); // Explicitly cast to result type after non-null branch
|
||||
}
|
||||
@Override
|
||||
void convertNonNull(AccessContext.Method context) {}
|
||||
@Override
|
||||
Mapping inverse() { return new Dynamic2Way(to, from, toProxy, fromProxy); }
|
||||
@Override
|
||||
void query(Query q) { q.needsExtensions = JBRApi.EXTENSIONS_ENABLED; }
|
||||
@Override
|
||||
public String toString() { return from.getName() + " --2way-> " + to.getName(); }
|
||||
}
|
||||
|
||||
private static class CustomOptional extends Nesting {
|
||||
private CustomOptional(Mapping component) {
|
||||
super(Optional.class, Optional.class, component);
|
||||
}
|
||||
static Mapping wrap(Mapping m) {
|
||||
if (m instanceof Identity) return new Identity(Optional.class);
|
||||
else return new CustomOptional(m);
|
||||
}
|
||||
@Override
|
||||
void convertNonNull(AccessContext.Method context) {
|
||||
MethodVisitor m = context.writer;
|
||||
m.visitInsn(ACONST_NULL);
|
||||
m.visitMethodInsn(INVOKEVIRTUAL, "java/util/Optional", "orElse", "(Ljava/lang/Object;)Ljava/lang/Object;", false);
|
||||
component.convert(context);
|
||||
m.visitMethodInsn(INVOKESTATIC, "java/util/Optional", "ofNullable", "(Ljava/lang/Object;)Ljava/util/Optional;", false);
|
||||
}
|
||||
@Override
|
||||
Mapping inverse() { return new CustomOptional(component.inverse()); }
|
||||
@Override
|
||||
public String toString() { return "Optional<" + component + ">"; }
|
||||
}
|
||||
|
||||
record Method(MethodType type, Mapping returnMapping, Mapping[] parameterMapping, Query query) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return returnMapping + "(" + Arrays.toString(parameterMapping) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
static class Context {
|
||||
|
||||
private final ProxyRepository proxyRepository;
|
||||
private final Map<TypeVariable<?>, Mapping> tvMappings = new HashMap<>();
|
||||
|
||||
Context(ProxyRepository proxyRepository) {
|
||||
this.proxyRepository = proxyRepository;
|
||||
}
|
||||
|
||||
void initTypeParameters(Class<?> type, Stream<Mapping> typeParameters) {
|
||||
if (type == null) return;
|
||||
if (typeParameters != null) {
|
||||
TypeVariable<?>[] tvs = type.getTypeParameters();
|
||||
Iterator<Mapping> tpIterator = typeParameters.iterator();
|
||||
for (int i = 0;; i++) {
|
||||
if ((i < tvs.length) ^ tpIterator.hasNext()) throw new RuntimeException("Number of type parameters doesn't match");
|
||||
if (i >= tvs.length) break;
|
||||
tvMappings.put(tvs[i], tpIterator.next());
|
||||
}
|
||||
}
|
||||
initTypeParameters(type.getGenericSuperclass());
|
||||
for (Type t : type.getGenericInterfaces()) initTypeParameters(t);
|
||||
}
|
||||
|
||||
void initTypeParameters(Type supertype) {
|
||||
if (supertype instanceof ParameterizedType type) {
|
||||
initTypeParameters((Class<?>) type.getRawType(),
|
||||
Stream.of(type.getActualTypeArguments()).map(this::getMapping));
|
||||
} else if (supertype instanceof Class<?> c) {
|
||||
initTypeParameters(c, null);
|
||||
} else if (supertype != null) {
|
||||
throw new RuntimeException("Unknown supertype kind: " + supertype.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
Method getMapping(java.lang.reflect.Method interfaceMethod) {
|
||||
Type[] params = interfaceMethod.getGenericParameterTypes();
|
||||
List<Class<?>> ptypes = new ArrayList<>(params.length);
|
||||
Mapping[] paramMappings = new Mapping[params.length];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
paramMappings[i] = getMapping(params[i]);
|
||||
ptypes.add(paramMappings[i].to);
|
||||
}
|
||||
Mapping returnMapping = getMapping(interfaceMethod.getGenericReturnType()).inverse();
|
||||
return new Method(MethodType.methodType(returnMapping.from, ptypes), returnMapping, paramMappings,
|
||||
query(returnMapping, paramMappings));
|
||||
}
|
||||
|
||||
private Mapping getMapping(Type userType) {
|
||||
if (userType instanceof Class<?> t) {
|
||||
return getMapping(t, null);
|
||||
} else if (userType instanceof GenericArrayType t) {
|
||||
return Array.wrap(getMapping(t.getGenericComponentType()));
|
||||
} else if (userType instanceof ParameterizedType t) {
|
||||
return getMapping(t);
|
||||
} else if (userType instanceof TypeVariable<?> t) {
|
||||
Mapping tvMapping = tvMappings.get(t);
|
||||
if (tvMapping != null) return tvMapping;
|
||||
Type[] bounds = t.getBounds();
|
||||
return getMapping(bounds.length > 0 ? bounds[0] : Object.class);
|
||||
} else if (userType instanceof WildcardType t) {
|
||||
Type[] bounds = t.getUpperBounds();
|
||||
return getMapping(bounds.length > 0 ? bounds[0] : Object.class);
|
||||
} else {
|
||||
throw new RuntimeException("Unknown type kind: " + userType.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private Mapping getMapping(ParameterizedType userType) {
|
||||
Type[] actual = userType.getActualTypeArguments();
|
||||
Mapping[] specialization = null;
|
||||
for (int i = 0; i < actual.length; i++) {
|
||||
Mapping m = getMapping(actual[i]);
|
||||
if (m instanceof Identity) continue;
|
||||
if (specialization == null) specialization = new Mapping[actual.length];
|
||||
specialization[i] = m;
|
||||
}
|
||||
return getMapping((Class<?>) userType.getRawType(), specialization);
|
||||
}
|
||||
|
||||
private Mapping getMapping(Class<?> userType, Mapping[] specialization) {
|
||||
if (userType.isArray()) {
|
||||
return Array.wrap(getMapping(userType.componentType()));
|
||||
}
|
||||
if (specialization != null && specialization.length == 1 && specialization[0] != null &&
|
||||
userType.equals(Optional.class)) {
|
||||
return CustomOptional.wrap(specialization[0]);
|
||||
}
|
||||
Proxy p = proxyRepository.getProxy(userType, specialization);
|
||||
if (p.supported() != Boolean.FALSE && p.inverse().supported() != Boolean.FALSE &&
|
||||
(p.getFlags() & Proxy.SERVICE) == 0 && (p.inverse().getFlags() & Proxy.SERVICE) == 0) {
|
||||
if (p.getInterface() != null && p.inverse().getInterface() != null) {
|
||||
return new Dynamic2Way(userType, p.inverse().getInterface(), p, p.inverse());
|
||||
} else if (p.getInterface() != null) {
|
||||
if (p.getTarget() != null) return new Extract(userType, p.getTarget(), p);
|
||||
} else if (p.inverse().getInterface() != null) {
|
||||
return new Wrap(userType, p.inverse().getInterface(), p.inverse());
|
||||
} else {
|
||||
return new Identity(userType);
|
||||
}
|
||||
}
|
||||
return new Invalid(userType);
|
||||
}
|
||||
|
||||
private static Query query(Mapping returnMapping, Mapping[] parameterMapping) {
|
||||
Query q = new Query();
|
||||
returnMapping.query(q);
|
||||
for (Mapping p : parameterMapping) p.query(q);
|
||||
return q;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,255 +26,203 @@
|
||||
package com.jetbrains.internal;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.*;
|
||||
|
||||
import static java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Proxy is needed to dynamically link JBR API interfaces and implementation at runtime.
|
||||
* Proxy implements or extends a base interface or class and delegates method calls to
|
||||
* target object or static methods. Calls may be delegated even to methods inaccessible by base type.
|
||||
* It implements user-side interfaces and delegates method calls to actual implementation
|
||||
* code through {@linkplain java.lang.invoke.MethodHandle method handles}.
|
||||
* <p>
|
||||
* Mapping between interfaces and implementation code is defined using
|
||||
* {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations.
|
||||
* There are 3 type of proxy objects:
|
||||
* <ol>
|
||||
* <li>{@linkplain ProxyInfo.Type#PROXY Proxy} - implements client-side interface from
|
||||
* {@code jetbrains.api} and delegates calls to JBR-side target object and optionally static methods.</li>
|
||||
* <li>{@linkplain ProxyInfo.Type#SERVICE Service} - singleton {@linkplain ProxyInfo.Type#PROXY proxy},
|
||||
* may delegate calls only to static methods, without target object.</li>
|
||||
* <li>{@linkplain ProxyInfo.Type#CLIENT_PROXY Client proxy} - reverse proxy, implements JBR-side interface
|
||||
* and delegates calls to client-side target object by interface defined in {@code jetbrains.api}.
|
||||
* May be used to implement callbacks which are created by client and called by JBR.</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* When JBR API interface or imeplementation type is used as a method parameter or return type,
|
||||
* JBR API backend performs conversions to ensure each side receives object or correct type,
|
||||
* for example, given following types:
|
||||
* <blockquote><pre>{@code
|
||||
* interface A {
|
||||
* B foo(B b);
|
||||
* }
|
||||
* interface B {
|
||||
* void bar();
|
||||
* }
|
||||
* class AImpl {
|
||||
* BImpl foo(BImpl b) {}
|
||||
* }
|
||||
* class BImpl {
|
||||
* void bar() {}
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
* {@code A#foo(B)} will be mapped to {@code AImpl#foo(BImpl)}.
|
||||
* Conversion between {@code B} and {@code BImpl} will be done by JBR API backend.
|
||||
* Here's what generated proxies would look like:
|
||||
* <blockquote><pre>{@code
|
||||
* final class AProxy implements A {
|
||||
* final AImpl target;
|
||||
* AProxy(AImpl t) {
|
||||
* target = t;
|
||||
* }
|
||||
* @Override
|
||||
* B foo(B b) {
|
||||
* BImpl ret = t.foo(b.target);
|
||||
* return new BProxy(ret);
|
||||
* }
|
||||
* }
|
||||
* final class BProxy implements B {
|
||||
* final BImpl target;
|
||||
* BProxy(BImpl t) {
|
||||
* target = t;
|
||||
* }
|
||||
* @Override
|
||||
* void bar() {
|
||||
* t.bar();
|
||||
* }
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
* <p>
|
||||
* Method signatures of base type and implementation are validated to ensure that proxy can
|
||||
* Method signatures of proxy interfaces and implementation are validated to ensure that proxy can
|
||||
* properly delegate call to the target implementation code. If there's no implementation found for some
|
||||
* interface methods, corresponding proxy is considered unsupported. Proxy is also considered unsupported
|
||||
* if any proxy used by it is unsupported.
|
||||
* if any proxy used by it is unsupported, more about it at {@link ProxyDependencyManager}.
|
||||
* <p>
|
||||
* Extensions are an exception to this rule. Methods of base type may be marked as extension methods,
|
||||
* which makes them optional for support checks. Moreover, extension must be explicitly enabled for a
|
||||
* proxy instance to enable usage of corresponding methods.
|
||||
* Mapping between interfaces and implementation code is defined in
|
||||
* {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}.
|
||||
* @param <INTERFACE> interface type for this proxy.
|
||||
*/
|
||||
class Proxy {
|
||||
/**
|
||||
* @see Proxy.Info#flags
|
||||
*/
|
||||
static final int
|
||||
INTERNAL = 1,
|
||||
SERVICE = 2;
|
||||
class Proxy<INTERFACE> {
|
||||
private final ProxyInfo info;
|
||||
|
||||
private final Proxy inverse;
|
||||
private final Class<?> interFace, target;
|
||||
private final int flags;
|
||||
private volatile ProxyGenerator generator;
|
||||
private volatile Boolean allMethodsImplemented;
|
||||
|
||||
private volatile Boolean supported;
|
||||
|
||||
private volatile Set<Proxy> directDependencies = Set.of();
|
||||
private volatile Set<Enum<?>> supportedExtensions = Set.of();
|
||||
|
||||
private volatile Class<?> proxyClass;
|
||||
private volatile MethodHandle constructor;
|
||||
private volatile MethodHandle targetExtractor;
|
||||
|
||||
/**
|
||||
* Creates empty proxy.
|
||||
*/
|
||||
static Proxy empty(Boolean supported) {
|
||||
return new Proxy(supported);
|
||||
private volatile boolean instanceInitialized;
|
||||
private volatile INTERFACE instance;
|
||||
|
||||
Proxy(ProxyInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new proxy (and possibly an inverse one) from {@link Info}.
|
||||
* @return {@link ProxyInfo} structure of this proxy
|
||||
*/
|
||||
static Proxy create(ProxyRepository repository,
|
||||
Info info, Mapping[] specialization,
|
||||
Info inverseInfo, Mapping[] inverseSpecialization) {
|
||||
return new Proxy(repository, info, specialization, null, inverseInfo, inverseSpecialization);
|
||||
ProxyInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
private Proxy(Boolean supported) {
|
||||
interFace = target = null;
|
||||
flags = 0;
|
||||
inverse = this;
|
||||
this.supported = supported;
|
||||
private synchronized void initGenerator() {
|
||||
if (generator != null) return;
|
||||
generator = new ProxyGenerator(info);
|
||||
allMethodsImplemented = generator.areAllMethodsImplemented();
|
||||
}
|
||||
|
||||
private Proxy(ProxyRepository repository, Info info, Mapping[] specialization,
|
||||
Proxy inverseProxy, Info inverseInfo, Mapping[] inverseSpecialization) {
|
||||
if (info != null) {
|
||||
interFace = info.interfaceLookup.lookupClass();
|
||||
target = info.targetLookup == null ? null : info.targetLookup.lookupClass();
|
||||
flags = info.flags;
|
||||
generator = new ProxyGenerator(repository, info, specialization);
|
||||
} else {
|
||||
interFace = target = null;
|
||||
flags = 0;
|
||||
/**
|
||||
* Checks if implementation is found for all abstract interface methods of this proxy.
|
||||
*/
|
||||
boolean areAllMethodsImplemented() {
|
||||
if (allMethodsImplemented != null) return allMethodsImplemented;
|
||||
synchronized (this) {
|
||||
if (allMethodsImplemented == null) initGenerator();
|
||||
return allMethodsImplemented;
|
||||
}
|
||||
inverse = inverseProxy == null ? new Proxy(repository, inverseInfo, specialization, this, null, null) : inverseProxy;
|
||||
if (inverse.getInterface() != null) directDependencies = Set.of(inverse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return inverse proxy
|
||||
* Checks if all methods are {@linkplain #areAllMethodsImplemented() implemented}
|
||||
* for this proxy and all proxies it {@linkplain ProxyDependencyManager uses}.
|
||||
*/
|
||||
Proxy inverse() {
|
||||
return inverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return interface class
|
||||
*/
|
||||
Class<?> getInterface() {
|
||||
return interFace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return target class
|
||||
*/
|
||||
Class<?> getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return flags
|
||||
*/
|
||||
int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all methods are implemented for this proxy and all proxies it uses.
|
||||
* {@code null} means not known yet.
|
||||
*/
|
||||
Boolean supported() {
|
||||
return supported;
|
||||
}
|
||||
|
||||
private synchronized boolean generate() {
|
||||
boolean isSupported() {
|
||||
if (supported != null) return supported;
|
||||
if (generator == null) return false;
|
||||
Set<Proxy> deps = new HashSet<>(directDependencies);
|
||||
supported = generator.generate();
|
||||
for (Map.Entry<Proxy, Boolean> e : generator.getDependencies().entrySet()) {
|
||||
if (e.getKey().generate()) deps.add(e.getKey());
|
||||
else if (e.getValue()) {
|
||||
supported = false;
|
||||
break;
|
||||
synchronized (this) {
|
||||
if (supported == null) {
|
||||
Set<Class<?>> dependencies = ProxyDependencyManager.getProxyDependencies(info.interFace);
|
||||
for (Class<?> d : dependencies) {
|
||||
Proxy<?> p = JBRApi.getProxy(d);
|
||||
if (p == null || !p.areAllMethodsImplemented()) {
|
||||
supported = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
supported = true;
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
if (supported) {
|
||||
directDependencies = deps;
|
||||
supportedExtensions = generator.getSupportedExtensions();
|
||||
} else generator = null; // Release for gc
|
||||
return supported;
|
||||
}
|
||||
|
||||
private synchronized void defineClasses() {
|
||||
if (constructor != null) return;
|
||||
initGenerator();
|
||||
generator.defineClasses();
|
||||
proxyClass = generator.getProxyClass();
|
||||
constructor = generator.findConstructor();
|
||||
targetExtractor = generator.findTargetExtractor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified extension is implemented by this proxy.
|
||||
* Implicitly runs bytecode generation.
|
||||
* @return generated proxy class
|
||||
*/
|
||||
boolean isExtensionSupported(Enum<?> extension) {
|
||||
if (supported == null) generate();
|
||||
return supportedExtensions.contains(extension);
|
||||
}
|
||||
|
||||
private synchronized boolean define() {
|
||||
if (constructor != null) return true;
|
||||
if (!generate()) return false;
|
||||
constructor = generator.define((flags & SERVICE) != 0);
|
||||
if (constructor == null) {
|
||||
supported = false;
|
||||
return false;
|
||||
Class<?> getProxyClass() {
|
||||
if (proxyClass != null) return proxyClass;
|
||||
synchronized (this) {
|
||||
if (proxyClass == null) defineClasses();
|
||||
return proxyClass;
|
||||
}
|
||||
for (Proxy p : directDependencies) {
|
||||
if (!p.define()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
synchronized boolean init() {
|
||||
if (!define()) return false;
|
||||
if (generator != null) {
|
||||
ProxyGenerator gen = generator;
|
||||
generator = null;
|
||||
gen.init();
|
||||
for (Proxy p : directDependencies) p.init();
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return method handle for the constructor of this proxy.
|
||||
* First parameter is target object to which it would delegate method calls.
|
||||
* Second parameter is extensions bitfield (if extensions are enabled).
|
||||
* <ul>
|
||||
* <li>For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.</li>
|
||||
* <li>For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg,
|
||||
* expecting target object to which it would delegate method calls.</li>
|
||||
* </ul>
|
||||
*/
|
||||
MethodHandle getConstructor() throws NullPointerException {
|
||||
if (JBRApi.LOG_DEPRECATED && interFace.isAnnotationPresent(Deprecated.class)) {
|
||||
Utils.log(Utils.BEFORE_BOOTSTRAP_DYNAMIC, System.err,
|
||||
"Warning: using deprecated JBR API interface " + interFace.getCanonicalName());
|
||||
MethodHandle getConstructor() {
|
||||
if (constructor != null) return constructor;
|
||||
synchronized (this) {
|
||||
if (constructor == null) defineClasses();
|
||||
return constructor;
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy descriptor with all classes and lookup contexts resolved.
|
||||
* Contains all necessary information to create a {@linkplain Proxy proxy}.
|
||||
* @return method handle for that extracts target object of the proxy, or null.
|
||||
*/
|
||||
static class Info {
|
||||
private record StaticMethod(String name, MethodType targetType) {}
|
||||
|
||||
private final Map<StaticMethod, MethodHandle> staticMethods = new HashMap<>();
|
||||
final Lookup interfaceLookup;
|
||||
final Lookup targetLookup;
|
||||
private final int flags;
|
||||
|
||||
Info(Lookup interfaceLookup, Lookup targetLookup, int flags) {
|
||||
this.interfaceLookup = interfaceLookup;
|
||||
this.targetLookup = targetLookup;
|
||||
this.flags = flags;
|
||||
MethodHandle getTargetExtractor() {
|
||||
// targetExtractor may be null, so check constructor instead
|
||||
if (constructor != null) return targetExtractor;
|
||||
synchronized (this) {
|
||||
if (constructor == null) defineClasses();
|
||||
return targetExtractor;
|
||||
}
|
||||
}
|
||||
|
||||
void addStaticMethod(String name, MethodHandle target) {
|
||||
staticMethods.put(new StaticMethod(name, target.type()), target);
|
||||
private synchronized void initClass(Set<Proxy<?>> actualUsages) {
|
||||
defineClasses();
|
||||
if (generator != null) {
|
||||
actualUsages.addAll(generator.getDirectProxyDependencies());
|
||||
generator.init();
|
||||
generator = null;
|
||||
}
|
||||
}
|
||||
private synchronized void initDependencyGraph() {
|
||||
defineClasses();
|
||||
if (generator == null) return;
|
||||
Set<Class<?>> dependencyClasses = ProxyDependencyManager.getProxyDependencies(info.interFace);
|
||||
Set<Proxy<?>> dependencies = new HashSet<>();
|
||||
Set<Proxy<?>> actualUsages = new HashSet<>();
|
||||
for (Class<?> d : dependencyClasses) {
|
||||
Proxy<?> p = JBRApi.getProxy(d);
|
||||
if (p != null) {
|
||||
dependencies.add(p);
|
||||
p.initClass(actualUsages);
|
||||
}
|
||||
}
|
||||
actualUsages.removeAll(dependencies);
|
||||
if (!actualUsages.isEmpty()) {
|
||||
// Should never happen, this is a sign of broken dependency search
|
||||
throw new RuntimeException("Some proxies are not in dependencies of " + info.interFace.getName() +
|
||||
", but are actually used by it: " +
|
||||
actualUsages.stream().map(p -> p.info.interFace.getName()).collect(Collectors.joining(", ")));
|
||||
}
|
||||
}
|
||||
|
||||
MethodHandle getStaticMethod(String name, MethodType targetType) {
|
||||
return staticMethods.get(new StaticMethod(name, targetType));
|
||||
/**
|
||||
* @return instance for this {@linkplain ProxyInfo.Type#SERVICE service},
|
||||
* returns {@code null} for other proxy types.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
INTERFACE getInstance() {
|
||||
if (instanceInitialized) return instance;
|
||||
if (info.type != ProxyInfo.Type.SERVICE) return null;
|
||||
synchronized (this) {
|
||||
if (instance == null) {
|
||||
initDependencyGraph();
|
||||
try {
|
||||
instance = (INTERFACE) getConstructor().invoke();
|
||||
} catch (JBRApi.ServiceNotAvailableException e) {
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.err.println("Service not available: " + info.interFace.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
instanceInitialized = true;
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2000-2023 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.internal;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* This class collects {@linkplain Proxy proxy} dependencies.
|
||||
* <p>
|
||||
* Dependencies of a class {@code C} are other classes that are
|
||||
* used by {@code C} (i.e. supertypes, classes that appear in method
|
||||
* parameters, return types) and all their dependencies. Any class
|
||||
* is also considered a dependency of itself.
|
||||
* <p>
|
||||
* Dependencies allow JBR to validate whole set of interfaces for
|
||||
* a particular feature instead of treating them as separate entities.
|
||||
* <h2>Example</h2>
|
||||
* Suppose we implemented some feature and added some API for it:
|
||||
* <blockquote><pre>{@code
|
||||
* interface SomeFeature {
|
||||
* SomeOtherObject createSomeObject(int magicNumber);
|
||||
* }
|
||||
* interface SomeOtherObject {
|
||||
* int getMagicNumber();
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
* And then used it:
|
||||
* <blockquote><pre>{@code
|
||||
* if (JBR.isSomeFeatureSupported()) {
|
||||
* SomeOtherObject object = JBR.getSomeFeature().createSomeObject(123);
|
||||
* int magic = object.getMagicNumber();
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
* Suppose JBR was able to find implementation for {@code SomeFeature.createSomeObject()},
|
||||
* but not for {@code SomeOtherObject.getMagicNumber()}. So {@code JBR.getSomeFeature()}
|
||||
* would succeed and return service instance, but {@code createSomeObject()} would fail,
|
||||
* because JBR wasn't able to find implementation for {@code SomeOtherObject.getMagicNumber()}
|
||||
* and therefore couldn't create proxy for {@code SomeOtherObject} class.
|
||||
* <p>
|
||||
* To avoid such issues, not only proxy interface itself, but all proxies that are accessible
|
||||
* from current proxy interface must have proper implementation.
|
||||
*/
|
||||
class ProxyDependencyManager {
|
||||
|
||||
private static final ConcurrentMap<Class<?>, Set<Class<?>>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* @return all proxy interfaces that are used (directly or indirectly) by given interface, including itself.
|
||||
*/
|
||||
static Set<Class<?>> getProxyDependencies(Class<?> interFace) {
|
||||
Set<Class<?>> dependencies = cache.get(interFace);
|
||||
if (dependencies != null) return dependencies;
|
||||
step(null, interFace);
|
||||
return cache.get(interFace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect dependencies for given class and store them into cache.
|
||||
*/
|
||||
private static void step(Node parent, Class<?> clazz) {
|
||||
if (!clazz.getPackageName().startsWith("com.jetbrains") && !JBRApi.isKnownProxyInterface(clazz)) return;
|
||||
if (parent != null && parent.findAndMergeCycle(clazz) != null) {
|
||||
return;
|
||||
}
|
||||
Set<Class<?>> cachedDependencies = cache.get(clazz);
|
||||
if (cachedDependencies != null) {
|
||||
if (parent != null) parent.cycle.dependencies.addAll(cachedDependencies);
|
||||
return;
|
||||
}
|
||||
Node node = new Node(parent, clazz);
|
||||
ClassUsagesFinder.visitUsages(clazz, c -> step(node, c));
|
||||
Class<?> correspondingProxyInterface = JBRApi.getProxyInterfaceByTargetName(clazz.getName());
|
||||
if (correspondingProxyInterface != null) {
|
||||
step(node, correspondingProxyInterface);
|
||||
}
|
||||
if (parent != null && parent.cycle != node.cycle) {
|
||||
parent.cycle.dependencies.addAll(node.cycle.dependencies);
|
||||
}
|
||||
if (node.cycle.origin.equals(clazz)) {
|
||||
// Put collected dependencies into cache only when we exit from the cycle
|
||||
// Otherwise cache will contain incomplete data
|
||||
for (Class<?> c : node.cycle.members) {
|
||||
cache.put(c, node.cycle.dependencies);
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.out.println("Found dependencies for " + c.getName() + ": " + node.cycle.dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Graph node, one per visited class
|
||||
*/
|
||||
private static class Node {
|
||||
private final Node parent;
|
||||
private final Class<?> clazz;
|
||||
private Cycle cycle;
|
||||
|
||||
private Node(Node parent, Class<?> clazz) {
|
||||
this.parent = parent;
|
||||
this.clazz = clazz;
|
||||
cycle = new Cycle(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* When classes form dependency cycle, they share all their dependencies.
|
||||
* If cycle was found, merge all found dependencies for nodes that form the cycle.
|
||||
*/
|
||||
private Cycle findAndMergeCycle(Class<?> clazz) {
|
||||
if (this.clazz.equals(clazz)) return cycle;
|
||||
if (parent == null) return null;
|
||||
Cycle c = parent.findAndMergeCycle(clazz);
|
||||
if (c != null && c != cycle) {
|
||||
c.members.addAll(cycle.members);
|
||||
c.dependencies.addAll(cycle.dependencies);
|
||||
cycle = c;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle info. For the sake of elegant code, single node
|
||||
* also forms a cycle with itself as a single member and dependency.
|
||||
*/
|
||||
private static class Cycle {
|
||||
/**
|
||||
* Origin is the first visited class from that cycle.
|
||||
*/
|
||||
private final Class<?> origin;
|
||||
private final Set<Class<?>> members = new HashSet<>();
|
||||
private final Set<Class<?>> dependencies = new HashSet<>();
|
||||
|
||||
private Cycle(Class<?> origin) {
|
||||
this.origin = origin;
|
||||
members.add(origin);
|
||||
if (JBRApi.isKnownProxyInterface(origin)) {
|
||||
dependencies.add(origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class that collects direct class usages using reflection
|
||||
*/
|
||||
private static class ClassUsagesFinder {
|
||||
|
||||
private static void visitUsages(Class<?> c, Consumer<Class<?>> action) {
|
||||
collect(c.getGenericSuperclass(), action);
|
||||
for (java.lang.reflect.Type t : c.getGenericInterfaces()) collect(t, action);
|
||||
for (Field f : c.getDeclaredFields()) collect(f.getGenericType(), action);
|
||||
for (Method m : c.getDeclaredMethods()) {
|
||||
collect(m.getGenericParameterTypes(), action);
|
||||
collect(m.getGenericReturnType(), action);
|
||||
collect(m.getGenericExceptionTypes(), action);
|
||||
}
|
||||
}
|
||||
|
||||
private static void collect(java.lang.reflect.Type type, Consumer<Class<?>> action) {
|
||||
if (type instanceof Class<?> c) {
|
||||
while (c.isArray()) c = Objects.requireNonNull(c.getComponentType());
|
||||
if (!c.isPrimitive()) action.accept(c);
|
||||
} else if (type instanceof TypeVariable<?> v) {
|
||||
collect(v.getBounds(), action);
|
||||
} else if (type instanceof WildcardType w) {
|
||||
collect(w.getUpperBounds(), action);
|
||||
collect(w.getLowerBounds(), action);
|
||||
} else if (type instanceof ParameterizedType p) {
|
||||
collect(p.getActualTypeArguments(), action);
|
||||
collect(p.getRawType(), action);
|
||||
collect(p.getOwnerType(), action);
|
||||
} else if (type instanceof GenericArrayType a) {
|
||||
collect(a.getGenericComponentType(), action);
|
||||
}
|
||||
}
|
||||
|
||||
private static void collect(java.lang.reflect.Type[] types, Consumer<Class<?>> action) {
|
||||
for (java.lang.reflect.Type t : types) collect(t, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,384 +29,457 @@ import jdk.internal.org.objectweb.asm.*;
|
||||
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandleInfo;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.jetbrains.exported.JBRApi.ServiceNotAvailableException;
|
||||
import static com.jetbrains.internal.ASMUtils.*;
|
||||
import static com.jetbrains.internal.JBRApi.EXTENSIONS_ENABLED;
|
||||
import static java.lang.invoke.MethodHandles.Lookup;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import static jdk.internal.org.objectweb.asm.Type.getInternalName;
|
||||
|
||||
/**
|
||||
* Generates {@linkplain Proxy proxy} classes.
|
||||
* This class generates {@linkplain Proxy proxy} classes.
|
||||
* Each proxy is just a generated class implementing some interface and
|
||||
* delegating method calls to method handles.
|
||||
* <p>
|
||||
* Proxy class always implements/extends a base interface/class. It's defined as a
|
||||
* {@linkplain MethodHandles.Lookup#defineHiddenClass(byte[], boolean, Lookup.ClassOption...) hidden class}
|
||||
* sharing the nest with either interface or target implementation class.
|
||||
* Defining proxy as a nestmate of a target class is preferred,
|
||||
* as in this case proxy can call implementation methods directly.
|
||||
* However, this may not be possible if interface is not accessible by target
|
||||
* (thus making it impossible for proxy to implement it), or if there is no target class at all
|
||||
* (e.g. a service may delegate all calls exclusively to static methods).
|
||||
* <p>
|
||||
* Proxy invokes target methods in two ways: either directly, when accessible
|
||||
* ({@code invokestatic}, {@code invokevirtual}, {@code invokeinterface}),
|
||||
* or via {@code invokedynamic} in cases when target method is inaccessible
|
||||
* (see {@link com.jetbrains.exported.JBRApiSupport#bootstrapDynamic(Lookup, String, MethodType)}).
|
||||
* @see Proxy
|
||||
* There are 2 proxy dispatch modes:
|
||||
* <ul>
|
||||
* <li>interface -> proxy -> {@linkplain #generateBridge bridge} -> method handle -> implementation code</li>
|
||||
* <li>interface -> proxy -> method handle -> implementation code</li>
|
||||
* </ul>
|
||||
* Generated proxy is always located in the same package with its interface and optional bridge is located in the
|
||||
* same module with target implementation code. Bridge allows proxy to safely call hidden non-static implementation
|
||||
* methods and is only needed for {@code jetbrains.api} -> JBR calls. For JBR -> {@code jetbrains.api} calls, proxy can
|
||||
* invoke method handle directly.
|
||||
*/
|
||||
class ProxyGenerator {
|
||||
private static final String PROXY_INTERFACE_NAME = getInternalName(com.jetbrains.exported.JBRApiSupport.Proxy.class);
|
||||
private static final String OBJECT_DESCRIPTOR = "Ljava/lang/Object;";
|
||||
private static final String MH_NAME = "java/lang/invoke/MethodHandle";
|
||||
private static final String MH_DESCRIPTOR = "Ljava/lang/invoke/MethodHandle;";
|
||||
private static final String CONVERSION_DESCRIPTOR = "(Ljava/lang/Object;)Ljava/lang/Object;";
|
||||
/**
|
||||
* Print warnings about usage of deprecated interfaces and methods to {@link System#err}.
|
||||
*/
|
||||
private static final boolean LOG_DEPRECATED = System.getProperty("jetbrains.api.logDeprecated", String.valueOf(JBRApi.VERBOSE)).equalsIgnoreCase("true");
|
||||
private static final boolean VERIFY_BYTECODE = Boolean.getBoolean("jetbrains.api.verifyBytecode");
|
||||
|
||||
private final Proxy.Info info;
|
||||
private final Class<?> interFace;
|
||||
private final Lookup proxyGenLookup;
|
||||
private final Mapping[] specialization;
|
||||
private final Mapping.Context mappingContext;
|
||||
private final AccessContext accessContext;
|
||||
private static final AtomicInteger nameCounter = new AtomicInteger();
|
||||
|
||||
private final Class<?> constructorTargetParameterType;
|
||||
private final String targetDescriptor, proxyName, superclassName;
|
||||
private final String[] superinterfaceNames;
|
||||
private final ClassWriter originalProxyWriter;
|
||||
private final ClassVisitor proxyWriter;
|
||||
|
||||
private final Map<Enum<?>, Boolean> supportedExtensions = new HashMap<>();
|
||||
|
||||
private boolean supported = true;
|
||||
private Lookup generatedProxy;
|
||||
private final ProxyInfo info;
|
||||
private final boolean generateBridge;
|
||||
private final String proxyName, bridgeName;
|
||||
private final ClassWriter originalProxyWriter, originalBridgeWriter;
|
||||
private final ClassVisitor proxyWriter, bridgeWriter;
|
||||
private final List<Supplier<MethodHandle>> handles = new ArrayList<>();
|
||||
private final List<Supplier<Class<?>>> classReferences = new ArrayList<>();
|
||||
private final Set<Proxy<?>> directProxyDependencies = new HashSet<>();
|
||||
private final List<Exception> exceptions = new ArrayList<>();
|
||||
private int bridgeMethodCounter;
|
||||
private boolean allMethodsImplemented = true;
|
||||
private Lookup generatedHandlesHolder, generatedProxy;
|
||||
|
||||
/**
|
||||
* Creates new proxy generator from given {@link Proxy.Info},
|
||||
* Creates new proxy generator from given {@link ProxyInfo},
|
||||
* looks for abstract interface methods, corresponding implementation methods
|
||||
* and generates proxy bytecode. However, it doesn't actually load generated
|
||||
* classes until {@link #defineClasses()} is called.
|
||||
*/
|
||||
ProxyGenerator(ProxyRepository proxyRepository, Proxy.Info info, Mapping[] specialization) {
|
||||
ProxyGenerator(ProxyInfo info) {
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.out.println("Generating proxy " + info.interFace.getName());
|
||||
}
|
||||
this.info = info;
|
||||
this.interFace = info.interfaceLookup.lookupClass();
|
||||
this.specialization = specialization;
|
||||
generateBridge = info.type.isPublicApi();
|
||||
int nameId = nameCounter.getAndIncrement();
|
||||
proxyName = Type.getInternalName(info.interFace) + "$$JBRApiProxy$" + nameId;
|
||||
bridgeName = generateBridge ? info.apiModule.lookupClass().getPackageName().replace('.', '/') + "/" +
|
||||
info.interFace.getSimpleName() + "$$JBRApiBridge$" + nameId : null;
|
||||
|
||||
// Placing our proxy implementation into the target's nest is preferred, as it gives us direct method calls.
|
||||
this.proxyGenLookup = info.targetLookup != null && AccessContext.canAccess(info.targetLookup, interFace) ?
|
||||
info.targetLookup : info.interfaceLookup;
|
||||
|
||||
this.mappingContext = new Mapping.Context(proxyRepository);
|
||||
this.accessContext = new AccessContext(proxyGenLookup);
|
||||
|
||||
constructorTargetParameterType = info.targetLookup == null ? null :
|
||||
accessContext.canAccess(info.targetLookup.lookupClass()) ? info.targetLookup.lookupClass() : Object.class;
|
||||
targetDescriptor = constructorTargetParameterType == null ? "" : constructorTargetParameterType.descriptorString();
|
||||
|
||||
proxyName = proxyGenLookup.lookupClass().getPackageName().replace('.', '/') + "/" + interFace.getSimpleName();
|
||||
|
||||
originalProxyWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES) {
|
||||
originalProxyWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||
proxyWriter = VERIFY_BYTECODE ? new CheckClassAdapter(originalProxyWriter, true) : originalProxyWriter;
|
||||
originalBridgeWriter = generateBridge ? new ClassWriter(ClassWriter.COMPUTE_FRAMES) : null;
|
||||
if (generateBridge) {
|
||||
bridgeWriter = VERIFY_BYTECODE ? new CheckClassAdapter(originalBridgeWriter, true) : originalBridgeWriter;
|
||||
} else bridgeWriter = new ClassVisitor(Opcodes.ASM9) { // Empty visitor
|
||||
@Override
|
||||
protected ClassLoader getClassLoader() {
|
||||
return ProxyGenerator.this.proxyGenLookup.lookupClass().getClassLoader();
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
return new MethodVisitor(api) {};
|
||||
}
|
||||
};
|
||||
proxyWriter = JBRApi.VERIFY_BYTECODE ? new CheckClassAdapter(originalProxyWriter, true) : originalProxyWriter;
|
||||
if (interFace.isInterface()) {
|
||||
superclassName = "java/lang/Object";
|
||||
superinterfaceNames = new String[] {getInternalName(interFace), PROXY_INTERFACE_NAME};
|
||||
} else {
|
||||
superclassName = getInternalName(interFace);
|
||||
superinterfaceNames = new String[] {PROXY_INTERFACE_NAME};
|
||||
}
|
||||
proxyWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC, proxyName, null,
|
||||
"java/lang/Object", new String[] {Type.getInternalName(info.interFace)});
|
||||
bridgeWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC | ACC_PUBLIC, bridgeName, null,
|
||||
"java/lang/Object", null);
|
||||
generateConstructor();
|
||||
generateMethods();
|
||||
proxyWriter.visitEnd();
|
||||
bridgeWriter.visitEnd();
|
||||
}
|
||||
|
||||
boolean areAllMethodsImplemented() {
|
||||
return allMethodsImplemented;
|
||||
}
|
||||
|
||||
Set<Proxy<?>> getDirectProxyDependencies() {
|
||||
return directProxyDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct (non-transitive) only. These are the proxies accessed by current one.
|
||||
* True for required and false for optional.
|
||||
*/
|
||||
Map<Proxy, Boolean> getDependencies() {
|
||||
return accessContext.dependencies;
|
||||
}
|
||||
|
||||
Set<Enum<?>> getSupportedExtensions() {
|
||||
return supportedExtensions.entrySet().stream()
|
||||
.filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return service target instance, if any
|
||||
*/
|
||||
private Object createServiceTarget() throws Throwable {
|
||||
Exception exception = null;
|
||||
MethodHandle constructor = null;
|
||||
try {
|
||||
constructor = info.targetLookup.findStatic(
|
||||
info.targetLookup.lookupClass(), "create", MethodType.methodType(info.targetLookup.lookupClass()));
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
exception = e;
|
||||
}
|
||||
try {
|
||||
if (constructor == null) constructor = info.targetLookup.findConstructor(
|
||||
info.targetLookup.lookupClass(), MethodType.methodType(void.class));
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
if (exception == null) exception = e;
|
||||
else exception.addSuppressed(e);
|
||||
}
|
||||
if (constructor == null) throw new ServiceNotAvailableException("No service factory method or constructor found", exception);
|
||||
return constructor.invoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* First parameter is target object to which it would delegate method calls (if target exists).
|
||||
* Second parameter is extensions bitfield (if extensions are enabled).
|
||||
* @return method handle to constructor of generated proxy class, or null.
|
||||
*/
|
||||
private MethodHandle findConstructor() throws NoSuchMethodException, IllegalAccessException {
|
||||
MethodType constructorType = MethodType.methodType(void.class);
|
||||
if (info.targetLookup != null) constructorType = constructorType.appendParameterTypes(constructorTargetParameterType);
|
||||
if (EXTENSIONS_ENABLED) constructorType = constructorType.appendParameterTypes(long[].class);
|
||||
return generatedProxy.findConstructor(generatedProxy.lookupClass(), constructorType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize method handles used by generated class via {@code invokedynamic}.
|
||||
* Insert all method handles and class references into static fields, so that proxy can call implementation methods.
|
||||
*/
|
||||
void init() {
|
||||
for (var t : accessContext.dynamicCallTargets) {
|
||||
JBRApi.dynamicCallTargets.put(new JBRApi.DynamicCallTargetKey(
|
||||
generatedProxy.lookupClass(), t.name(), t.descriptor()
|
||||
), t.futureHandle());
|
||||
try {
|
||||
for (int i = 0; i < handles.size(); i++) {
|
||||
generatedHandlesHolder
|
||||
.findStaticVarHandle(generatedHandlesHolder.lookupClass(), "h" + i, MethodHandle.class)
|
||||
.set(handles.get(i).get());
|
||||
}
|
||||
for (int i = 0; i < classReferences.size(); i++) {
|
||||
generatedHandlesHolder
|
||||
.findStaticVarHandle(generatedHandlesHolder.lookupClass(), "c" + i, Class.class)
|
||||
.set(classReferences.get(i).get());
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> getProxyClass() {
|
||||
return generatedProxy.lookupClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define generated class.
|
||||
* @return method handle to constructor of generated proxy class, or null.
|
||||
* @return method handle to constructor of generated proxy class.
|
||||
* <ul>
|
||||
* <li>For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.</li>
|
||||
* <li>For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg,
|
||||
* expecting target object to which it would delegate method calls.</li>
|
||||
* </ul>
|
||||
*/
|
||||
MethodHandle define(boolean service) {
|
||||
MethodHandle findConstructor() {
|
||||
try {
|
||||
Object sericeTarget = service && info.targetLookup != null ? createServiceTarget() : null;
|
||||
generatedProxy = proxyGenLookup.defineHiddenClass(
|
||||
originalProxyWriter.toByteArray(), false, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
|
||||
MethodHandle constructor = findConstructor();
|
||||
if (sericeTarget != null) constructor = MethodHandles.insertArguments(constructor, 0, sericeTarget);
|
||||
return constructor;
|
||||
} catch (ServiceNotAvailableException e) {
|
||||
if (JBRApi.VERBOSE) e.printStackTrace(System.err);
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
if (info.target == null) {
|
||||
return generatedProxy.findConstructor(generatedProxy.lookupClass(), MethodType.methodType(void.class));
|
||||
} else {
|
||||
MethodHandle c = generatedProxy.findConstructor(generatedProxy.lookupClass(),
|
||||
MethodType.methodType(void.class, Object.class));
|
||||
if (info.type.isService()) {
|
||||
try {
|
||||
return MethodHandles.foldArguments(c, info.target.findConstructor(info.target.lookupClass(),
|
||||
MethodType.methodType(void.class)).asType(MethodType.methodType(Object.class)));
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Service implementation must have no-args constructor: " +
|
||||
info.target.lookupClass(), e);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
} catch (IllegalAccessException | NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate bytecode for class.
|
||||
* @return true if generated proxy is considered supported
|
||||
* @return method handle that receives proxy and returns its target, or null
|
||||
*/
|
||||
boolean generate() {
|
||||
if (!supported) return false;
|
||||
proxyWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC, proxyName, null,
|
||||
superclassName, superinterfaceNames);
|
||||
if (JBRApi.VERBOSE) {
|
||||
synchronized (System.out) {
|
||||
System.out.print("Generating proxy " + interFace.getName());
|
||||
if (specialization != null) System.out.print(" <" +
|
||||
Stream.of(specialization).map(t -> t == null ? "?" : t.toString()).collect(Collectors.joining(", ")) + ">");
|
||||
System.out.println();
|
||||
}
|
||||
MethodHandle findTargetExtractor() {
|
||||
if (info.target == null) return null;
|
||||
try {
|
||||
return generatedProxy.findGetter(generatedProxy.lookupClass(), "target", Object.class);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (specialization != null) {
|
||||
mappingContext.initTypeParameters(interFace, Stream.of(specialization));
|
||||
}
|
||||
mappingContext.initTypeParameters(interFace);
|
||||
generateFields();
|
||||
generateConstructor();
|
||||
generateTargetGetter();
|
||||
generateMethods(interFace);
|
||||
if (interFace.isInterface()) generateMethods(Object.class);
|
||||
proxyWriter.visitEnd();
|
||||
return supported;
|
||||
}
|
||||
|
||||
private void generateFields() {
|
||||
if (info.targetLookup != null) {
|
||||
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", targetDescriptor, null, null);
|
||||
}
|
||||
if (EXTENSIONS_ENABLED) {
|
||||
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "extensions", "[J", null, null);
|
||||
/**
|
||||
* Loads generated classes.
|
||||
*/
|
||||
void defineClasses() {
|
||||
try {
|
||||
Lookup bridge = !generateBridge ? null : MethodHandles.privateLookupIn(
|
||||
info.apiModule.defineClass(originalBridgeWriter.toByteArray()), info.apiModule);
|
||||
generatedProxy = info.interFaceLookup.defineHiddenClass(
|
||||
originalProxyWriter.toByteArray(), true, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
|
||||
generatedHandlesHolder = generateBridge ? bridge : generatedProxy;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateConstructor() {
|
||||
MethodVisitor m = proxyWriter.visitMethod(ACC_PRIVATE, "<init>", "(" + targetDescriptor +
|
||||
(EXTENSIONS_ENABLED ? "[J" : "") + ")V", null, null);
|
||||
m.visitCode();
|
||||
m.visitVarInsn(ALOAD, 0);
|
||||
if (info.targetLookup != null) {
|
||||
m.visitInsn(DUP);
|
||||
m.visitVarInsn(ALOAD, 1);
|
||||
m.visitFieldInsn(PUTFIELD, proxyName, "target", targetDescriptor);
|
||||
if (info.target != null) {
|
||||
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", OBJECT_DESCRIPTOR, null, null);
|
||||
}
|
||||
if (EXTENSIONS_ENABLED) {
|
||||
m.visitInsn(DUP);
|
||||
m.visitVarInsn(ALOAD, info.targetLookup != null ? 2 : 1);
|
||||
m.visitFieldInsn(PUTFIELD, proxyName, "extensions", "[J");
|
||||
MethodVisitor p = proxyWriter.visitMethod(ACC_PRIVATE, "<init>", "(" +
|
||||
(info.target == null ? "" : OBJECT_DESCRIPTOR) + ")V", null, null);
|
||||
if (LOG_DEPRECATED && info.interFace.isAnnotationPresent(Deprecated.class)) {
|
||||
logDeprecated(p, "Warning: using deprecated JBR API interface " + info.interFace.getName());
|
||||
}
|
||||
m.visitMethodInsn(INVOKESPECIAL, superclassName, "<init>", "()V", false);
|
||||
m.visitInsn(RETURN);
|
||||
m.visitMaxs(0, 0);
|
||||
m.visitEnd();
|
||||
p.visitCode();
|
||||
p.visitVarInsn(ALOAD, 0);
|
||||
if (info.target != null) {
|
||||
p.visitInsn(DUP);
|
||||
p.visitVarInsn(ALOAD, 1);
|
||||
p.visitFieldInsn(PUTFIELD, proxyName, "target", OBJECT_DESCRIPTOR);
|
||||
}
|
||||
p.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
|
||||
p.visitInsn(RETURN);
|
||||
p.visitMaxs(0, 0);
|
||||
p.visitEnd();
|
||||
}
|
||||
|
||||
private void generateTargetGetter() {
|
||||
MethodVisitor m = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, "$getProxyTarget", "()" +
|
||||
Object.class.descriptorString(), null, null);
|
||||
m.visitCode();
|
||||
if (info.targetLookup != null) {
|
||||
m.visitVarInsn(ALOAD, 0);
|
||||
m.visitFieldInsn(GETFIELD, proxyName, "target", targetDescriptor);
|
||||
} else m.visitInsn(ACONST_NULL);
|
||||
m.visitInsn(ARETURN);
|
||||
m.visitMaxs(0, 0);
|
||||
m.visitEnd();
|
||||
}
|
||||
|
||||
private void generateMethods(Class<?> interFace) {
|
||||
for (Method method : interFace.getMethods()) {
|
||||
private void generateMethods() {
|
||||
for (Method method : info.interFace.getMethods()) {
|
||||
int mod = method.getModifiers();
|
||||
if ((mod & (Modifier.STATIC | Modifier.FINAL)) != 0) continue;
|
||||
if (!Modifier.isAbstract(mod)) continue;
|
||||
MethodMapping methodMapping = getTargetMethodMapping(method);
|
||||
|
||||
Exception exception = null;
|
||||
Enum<?> extension = EXTENSIONS_ENABLED && JBRApi.extensionExtractor != null ?
|
||||
JBRApi.extensionExtractor.apply(method) : null;
|
||||
Mapping.Method methodMapping = mappingContext.getMapping(method);
|
||||
MethodHandle handle;
|
||||
boolean passInstance;
|
||||
|
||||
if (methodMapping.query().valid) {
|
||||
// Try static method.
|
||||
handle = info.getStaticMethod(method.getName(), methodMapping.type());
|
||||
passInstance = false;
|
||||
|
||||
// Try target class.
|
||||
if (handle == null && info.targetLookup != null) {
|
||||
try {
|
||||
handle = interFace.equals(info.targetLookup.lookupClass()) ?
|
||||
info.targetLookup.unreflect(method) : info.targetLookup.findVirtual(
|
||||
info.targetLookup.lookupClass(), method.getName(), methodMapping.type());
|
||||
passInstance = true;
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (handle != null) {
|
||||
// Method found.
|
||||
generateMethod(method, handle, methodMapping, extension, passInstance);
|
||||
if (extension != null) supportedExtensions.putIfAbsent(extension, true);
|
||||
Exception e1 = null;
|
||||
if (info.target != null) {
|
||||
try {
|
||||
MethodHandle handle = info.target.findVirtual(
|
||||
info.target.lookupClass(), method.getName(), methodMapping.type());
|
||||
generateMethod(method, handle, methodMapping, true);
|
||||
continue;
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
e1 = e;
|
||||
}
|
||||
} else {
|
||||
exception = new Exception("Method mapping is invalid: " + methodMapping);
|
||||
}
|
||||
|
||||
// Skip if possible.
|
||||
if (!Modifier.isAbstract(mod)) continue;
|
||||
Exception e2 = null;
|
||||
ProxyInfo.StaticMethodMapping mapping = info.staticMethods.get(method.getName());
|
||||
if (mapping != null) {
|
||||
try {
|
||||
MethodHandle staticHandle =
|
||||
mapping.lookup().findStatic(mapping.lookup().lookupClass(), mapping.methodName(), methodMapping.type());
|
||||
generateMethod(method, staticHandle, methodMapping, false);
|
||||
continue;
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
e2 = e;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate unsupported stub.
|
||||
if (e1 != null) exceptions.add(e1);
|
||||
if (e2 != null) exceptions.add(e2);
|
||||
generateUnsupportedMethod(proxyWriter, method);
|
||||
if (JBRApi.VERBOSE) {
|
||||
synchronized (System.err) {
|
||||
System.err.println("Couldn't generate method " + method.getName());
|
||||
if (exception != null) exception.printStackTrace(System.err);
|
||||
}
|
||||
System.err.println("Couldn't generate method " + method.getName());
|
||||
if (e1 != null) e1.printStackTrace();
|
||||
if (e2 != null) e2.printStackTrace();
|
||||
}
|
||||
if (extension == null) supported = false;
|
||||
else supportedExtensions.put(extension, false);
|
||||
allMethodsImplemented = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void generateMethod(Method interfaceMethod, MethodHandle handle, Mapping.Method mapping, Enum<?> extension, boolean passInstance) {
|
||||
boolean passExtensions = mapping.query().needsExtensions;
|
||||
private void generateMethod(Method interfaceMethod, MethodHandle handle, MethodMapping mapping, boolean passInstance) {
|
||||
InternalMethodInfo methodInfo = getInternalMethodInfo(interfaceMethod);
|
||||
MethodHandleInfo directCall = accessContext.resolveDirect(handle);
|
||||
Supplier<MethodHandle> futureHandle = () -> handle;
|
||||
String bridgeMethodDescriptor = mapping.getBridgeDescriptor(passInstance);
|
||||
|
||||
// Check usage of deprecated API.
|
||||
if (JBRApi.LOG_DEPRECATED && interfaceMethod.isAnnotationPresent(Deprecated.class)) {
|
||||
directCall = null; // Force invokedynamic.
|
||||
futureHandle = () -> { // Log warning when binding the call site.
|
||||
Utils.log(Utils.BEFORE_BOOTSTRAP_DYNAMIC, System.err, "Warning: using deprecated JBR API method " +
|
||||
interfaceMethod.getDeclaringClass().getCanonicalName() + "." + interfaceMethod.getName());
|
||||
return handle;
|
||||
ClassVisitor handleWriter = generateBridge ? bridgeWriter : proxyWriter;
|
||||
String bridgeOrProxyName = generateBridge ? bridgeName : proxyName;
|
||||
String handleName = addHandle(handleWriter, () -> handle);
|
||||
for (TypeMapping m : mapping) {
|
||||
if (m.conversion() == TypeConversion.EXTRACT_TARGET ||
|
||||
m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
|
||||
Proxy<?> from = m.fromProxy();
|
||||
m.metadata.extractTargetHandle = addHandle(handleWriter, from::getTargetExtractor);
|
||||
directProxyDependencies.add(from);
|
||||
}
|
||||
if (m.conversion() == TypeConversion.WRAP_INTO_PROXY ||
|
||||
m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
|
||||
Proxy<?> to = m.toProxy();
|
||||
m.metadata.proxyConstructorHandle = addHandle(handleWriter, to::getConstructor);
|
||||
directProxyDependencies.add(to);
|
||||
}
|
||||
if (m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
|
||||
String classField = "c" + classReferences.size();
|
||||
m.metadata.extractableClassField = classField;
|
||||
classReferences.add(m.fromProxy()::getProxyClass);
|
||||
handleWriter.visitField(ACC_PRIVATE | ACC_STATIC, classField, "Ljava/lang/Class;", null, null);
|
||||
}
|
||||
}
|
||||
String bridgeMethodName = methodInfo.name() + "$bridge$" + bridgeMethodCounter;
|
||||
bridgeMethodCounter++;
|
||||
|
||||
MethodVisitor p = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(),
|
||||
methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames());
|
||||
MethodVisitor b = bridgeWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, bridgeMethodName,
|
||||
bridgeMethodDescriptor, null, null);
|
||||
if (LOG_DEPRECATED && interfaceMethod.isAnnotationPresent(Deprecated.class)) {
|
||||
logDeprecated(p, "Warning: using deprecated JBR API method " +
|
||||
interfaceMethod.getDeclaringClass().getName() + "#" + interfaceMethod.getName());
|
||||
}
|
||||
p.visitCode();
|
||||
b.visitCode();
|
||||
MethodVisitor bp = generateBridge ? b : p;
|
||||
bp.visitFieldInsn(GETSTATIC, bridgeOrProxyName, handleName, MH_DESCRIPTOR);
|
||||
if (passInstance) {
|
||||
p.visitVarInsn(ALOAD, 0);
|
||||
p.visitFieldInsn(GETFIELD, proxyName, "target", OBJECT_DESCRIPTOR);
|
||||
b.visitVarInsn(ALOAD, 0);
|
||||
}
|
||||
int lvIndex = 1;
|
||||
for (TypeMapping param : mapping.parameterMapping) {
|
||||
int opcode = getLoadOpcode(param.from());
|
||||
p.visitVarInsn(opcode, lvIndex);
|
||||
b.visitVarInsn(opcode, lvIndex - (passInstance ? 0 : 1));
|
||||
lvIndex += getParameterSize(param.from());
|
||||
convertValue(bp, bridgeOrProxyName, param);
|
||||
}
|
||||
if (generateBridge) {
|
||||
p.visitMethodInsn(INVOKESTATIC, bridgeName, bridgeMethodName, bridgeMethodDescriptor, false);
|
||||
}
|
||||
bp.visitMethodInsn(INVOKEVIRTUAL, MH_NAME, "invoke", bridgeMethodDescriptor, false);
|
||||
convertValue(bp, bridgeOrProxyName, mapping.returnMapping());
|
||||
int returnOpcode = getReturnOpcode(mapping.returnMapping().to());
|
||||
p.visitInsn(returnOpcode);
|
||||
b.visitInsn(returnOpcode);
|
||||
p.visitMaxs(0, 0);
|
||||
b.visitMaxs(0, 0);
|
||||
p.visitEnd();
|
||||
b.visitEnd();
|
||||
}
|
||||
|
||||
private String addHandle(ClassVisitor classWriter, Supplier<MethodHandle> handleSupplier) {
|
||||
String handleName = "h" + handles.size();
|
||||
handles.add(handleSupplier);
|
||||
classWriter.visitField(ACC_PRIVATE | ACC_STATIC, handleName, MH_DESCRIPTOR, null, null);
|
||||
return handleName;
|
||||
}
|
||||
|
||||
private static void convertValue(MethodVisitor m, String handlesHolderName, TypeMapping mapping) {
|
||||
if (mapping.conversion() == TypeConversion.IDENTITY) return;
|
||||
Label skipConvert = new Label();
|
||||
m.visitInsn(DUP);
|
||||
m.visitJumpInsn(IFNULL, skipConvert);
|
||||
switch (mapping.conversion()) {
|
||||
case EXTRACT_TARGET ->
|
||||
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractTargetHandle, MH_DESCRIPTOR);
|
||||
case WRAP_INTO_PROXY ->
|
||||
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.proxyConstructorHandle, MH_DESCRIPTOR);
|
||||
case DYNAMIC_2_WAY -> {
|
||||
m.visitInsn(DUP);
|
||||
m.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
|
||||
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractableClassField, "Ljava/lang/Class;");
|
||||
Label elseBranch = new Label(), afterBranch = new Label();
|
||||
m.visitJumpInsn(IF_ACMPNE, elseBranch);
|
||||
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractTargetHandle, MH_DESCRIPTOR);
|
||||
m.visitJumpInsn(GOTO, afterBranch);
|
||||
m.visitLabel(elseBranch);
|
||||
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.proxyConstructorHandle, MH_DESCRIPTOR);
|
||||
m.visitLabel(afterBranch);
|
||||
}
|
||||
}
|
||||
m.visitInsn(SWAP);
|
||||
m.visitMethodInsn(INVOKEVIRTUAL, MH_NAME, "invoke", CONVERSION_DESCRIPTOR, false);
|
||||
m.visitLabel(skipConvert);
|
||||
}
|
||||
|
||||
private static MethodMapping getTargetMethodMapping(Method interfaceMethod) {
|
||||
Class<?>[] params = interfaceMethod.getParameterTypes();
|
||||
TypeMapping[] paramMappings = new TypeMapping[params.length];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
paramMappings[i] = getTargetTypeMapping(params[i]);
|
||||
params[i] = paramMappings[i].to();
|
||||
}
|
||||
TypeMapping returnMapping = getTargetTypeMapping(interfaceMethod.getReturnType()).inverse();
|
||||
return new MethodMapping(MethodType.methodType(returnMapping.from(), params), returnMapping, paramMappings);
|
||||
}
|
||||
|
||||
private static <T> TypeMapping getTargetTypeMapping(Class<T> userType) {
|
||||
TypeMappingMetadata m = new TypeMappingMetadata();
|
||||
Proxy<T> p = JBRApi.getProxy(userType);
|
||||
if (p != null && p.getInfo().target != null) {
|
||||
Proxy<?> r = JBRApi.getProxy(p.getInfo().target.lookupClass());
|
||||
if (r != null && r.getInfo().target != null) {
|
||||
return new TypeMapping(userType, p.getInfo().target.lookupClass(), TypeConversion.DYNAMIC_2_WAY, p, r, m);
|
||||
}
|
||||
return new TypeMapping(userType, p.getInfo().target.lookupClass(), TypeConversion.EXTRACT_TARGET, p, null, m);
|
||||
}
|
||||
Class<?> interFace = JBRApi.getProxyInterfaceByTargetName(userType.getName());
|
||||
if (interFace != null) {
|
||||
Proxy<?> r = JBRApi.getProxy(interFace);
|
||||
if (r != null) {
|
||||
return new TypeMapping(userType, interFace, TypeConversion.WRAP_INTO_PROXY, null, r, m);
|
||||
}
|
||||
}
|
||||
return new TypeMapping(userType, userType, TypeConversion.IDENTITY, null, null, m);
|
||||
}
|
||||
|
||||
private record MethodMapping(MethodType type, TypeMapping returnMapping, TypeMapping[] parameterMapping)
|
||||
implements Iterable<TypeMapping> {
|
||||
@Override
|
||||
public Iterator<TypeMapping> iterator() {
|
||||
return new Iterator<>() {
|
||||
private int index = -1;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < parameterMapping.length;
|
||||
}
|
||||
@Override
|
||||
public TypeMapping next() {
|
||||
TypeMapping m = index == -1 ? returnMapping : parameterMapping[index];
|
||||
index++;
|
||||
return m;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.out.println(" " +
|
||||
mapping.returnMapping() + " " +
|
||||
interfaceMethod.getName() + "(" +
|
||||
Stream.of(mapping.parameterMapping()).map(Mapping::toString).collect(Collectors.joining(", ")) +
|
||||
")" + (directCall != null ? " (direct)" : "")
|
||||
);
|
||||
/**
|
||||
* Every convertable parameter type is replaced with {@link Object} for bridge descriptor.
|
||||
* Optional {@link Object} is added as first parameter for instance methods.
|
||||
*/
|
||||
String getBridgeDescriptor(boolean passInstance) {
|
||||
StringBuilder bd = new StringBuilder("(");
|
||||
if (passInstance) bd.append(OBJECT_DESCRIPTOR);
|
||||
for (TypeMapping m : parameterMapping) {
|
||||
bd.append(m.getBridgeDescriptor());
|
||||
}
|
||||
bd.append(')');
|
||||
bd.append(returnMapping.getBridgeDescriptor());
|
||||
return bd.toString();
|
||||
}
|
||||
}
|
||||
|
||||
MethodVisitor m = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(),
|
||||
methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames());
|
||||
AccessContext.Method methodContext = accessContext.new Method(m, extension == null);
|
||||
m.visitCode();
|
||||
// Load `this`.
|
||||
if (passInstance || passExtensions || extension != null) {
|
||||
m.visitVarInsn(ALOAD, 0);
|
||||
if (passInstance && (passExtensions || extension != null)) m.visitInsn(DUP);
|
||||
private record TypeMapping(Class<?> from, Class<?> to, TypeConversion conversion,
|
||||
Proxy<?> fromProxy, Proxy<?> toProxy, TypeMappingMetadata metadata) {
|
||||
TypeMapping inverse() {
|
||||
return new TypeMapping(to, from, switch (conversion) {
|
||||
case EXTRACT_TARGET -> TypeConversion.WRAP_INTO_PROXY;
|
||||
case WRAP_INTO_PROXY -> TypeConversion.EXTRACT_TARGET;
|
||||
default -> conversion;
|
||||
}, toProxy, fromProxy, metadata);
|
||||
}
|
||||
// Check enabled extension.
|
||||
if (passExtensions || extension != null) {
|
||||
m.visitFieldInsn(GETFIELD, proxyName, "extensions", "[J");
|
||||
if (passExtensions && extension != null) m.visitInsn(DUP);
|
||||
if (passExtensions) {
|
||||
// If this method converts any parameters, we need to store extensions for later use.
|
||||
// We overwrite `this` slot, but we have it on stack, so it's ok.
|
||||
m.visitVarInsn(ASTORE, 0);
|
||||
}
|
||||
if (extension != null) {
|
||||
// Check the specific bit inside long[].
|
||||
Label afterExtensionCheck = new Label();
|
||||
m.visitIntInsn(extension.ordinal() < 8192 ? BIPUSH : SIPUSH, extension.ordinal() / 64);
|
||||
m.visitInsn(LALOAD);
|
||||
m.visitLdcInsn(1L << (extension.ordinal() % 64));
|
||||
m.visitInsn(LAND);
|
||||
m.visitInsn(LCONST_0);
|
||||
m.visitInsn(LCMP);
|
||||
m.visitJumpInsn(IFNE, afterExtensionCheck);
|
||||
throwException(m, "java/lang/UnsupportedOperationException",
|
||||
interFace.getCanonicalName() + '.' + interfaceMethod.getName() +
|
||||
" - extension " + extension.name() + " is disabled");
|
||||
m.visitLabel(afterExtensionCheck);
|
||||
}
|
||||
String getBridgeDescriptor() {
|
||||
if (conversion == TypeConversion.IDENTITY) return Type.getDescriptor(from);
|
||||
else return "Ljava/lang/Object;";
|
||||
}
|
||||
// Extract target from `this`.
|
||||
if (passInstance) {
|
||||
// We already have `this` on stack.
|
||||
m.visitFieldInsn(GETFIELD, proxyName, "target", targetDescriptor);
|
||||
}
|
||||
// Load and convert parameters.
|
||||
for (int lvIndex = 1, i = 0; i < mapping.parameterMapping().length; i++) {
|
||||
Mapping param = mapping.parameterMapping()[i];
|
||||
int opcode = getLoadOpcode(param.from);
|
||||
m.visitVarInsn(opcode, lvIndex);
|
||||
lvIndex += getParameterSize(param.from);
|
||||
param.convert(methodContext);
|
||||
}
|
||||
// Invoke target method.
|
||||
if (directCall != null) methodContext.invokeDirect(directCall);
|
||||
else methodContext.invokeDynamic(handle.type(), futureHandle);
|
||||
// Convert return value.
|
||||
mapping.returnMapping().convert(methodContext);
|
||||
int opcode = getReturnOpcode(mapping.returnMapping().to);
|
||||
m.visitInsn(opcode);
|
||||
m.visitMaxs(0, 0);
|
||||
m.visitEnd();
|
||||
}
|
||||
|
||||
private static class TypeMappingMetadata {
|
||||
private String extractTargetHandle, proxyConstructorHandle, extractableClassField;
|
||||
}
|
||||
|
||||
private enum TypeConversion {
|
||||
/**
|
||||
* No conversion.
|
||||
*/
|
||||
IDENTITY,
|
||||
/**
|
||||
* Take a proxy object and extract its target implementation object.
|
||||
*/
|
||||
EXTRACT_TARGET,
|
||||
/**
|
||||
* Create new proxy targeting given implementation object.
|
||||
*/
|
||||
WRAP_INTO_PROXY,
|
||||
/**
|
||||
* Decide between {@link #EXTRACT_TARGET} and {@link #WRAP_INTO_PROXY} at runtime, depending on actual object.
|
||||
*/
|
||||
DYNAMIC_2_WAY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2000-2023 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.internal;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.invoke.MethodHandles.Lookup;
|
||||
|
||||
/**
|
||||
* Proxy info, like {@link RegisteredProxyInfo}, but with all classes and lookup contexts resolved.
|
||||
* Contains all necessary information to create a {@linkplain Proxy proxy}.
|
||||
*/
|
||||
class ProxyInfo {
|
||||
|
||||
final Lookup apiModule;
|
||||
final Type type;
|
||||
final Lookup interFaceLookup;
|
||||
final Class<?> interFace;
|
||||
final Lookup target;
|
||||
final Map<String, StaticMethodMapping> staticMethods = new HashMap<>();
|
||||
|
||||
private ProxyInfo(RegisteredProxyInfo i) {
|
||||
this.apiModule = i.apiModule();
|
||||
type = i.type();
|
||||
interFaceLookup = lookup(getInterfaceLookup(), i.interfaceName());
|
||||
interFace = interFaceLookup == null ? null : interFaceLookup.lookupClass();
|
||||
target = Stream.of(i.targets())
|
||||
.map(t -> lookup(getTargetLookup(), t))
|
||||
.filter(Objects::nonNull).findFirst().orElse(null);
|
||||
for (RegisteredProxyInfo.StaticMethodMapping m : i.staticMethods()) {
|
||||
Stream.of(m.classes())
|
||||
.map(t -> lookup(getTargetLookup(), t))
|
||||
.filter(Objects::nonNull).findFirst()
|
||||
.ifPresent(l -> staticMethods.put(m.interfaceMethodName(), new StaticMethodMapping(l, m.methodName())));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve all classes and lookups for given {@link RegisteredProxyInfo}.
|
||||
*/
|
||||
static ProxyInfo resolve(RegisteredProxyInfo i) {
|
||||
ProxyInfo info = new ProxyInfo(i);
|
||||
if (info.interFace == null || (info.target == null && info.staticMethods.isEmpty())) return null;
|
||||
if (!info.interFace.isInterface()) {
|
||||
if (info.type == Type.CLIENT_PROXY) {
|
||||
throw new RuntimeException("Tried to create client proxy for non-interface: " + info.interFace);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
Lookup getInterfaceLookup() {
|
||||
return type == Type.CLIENT_PROXY || type == Type.INTERNAL_SERVICE ? apiModule : JBRApi.outerLookup;
|
||||
}
|
||||
|
||||
Lookup getTargetLookup() {
|
||||
return type == Type.CLIENT_PROXY ? JBRApi.outerLookup : apiModule;
|
||||
}
|
||||
|
||||
private Lookup lookup(Lookup lookup, String clazz) {
|
||||
String[] nestedClasses = clazz.split("\\$");
|
||||
clazz = "";
|
||||
for (int i = 0; i < nestedClasses.length; i++) {
|
||||
try {
|
||||
if (i != 0) clazz += "$";
|
||||
clazz += nestedClasses[i];
|
||||
lookup = MethodHandles.privateLookupIn(Class.forName(clazz, false, lookup.lookupClass().getClassLoader()), lookup);
|
||||
} catch (ClassNotFoundException | IllegalAccessException ignore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return lookup;
|
||||
}
|
||||
|
||||
record StaticMethodMapping(Lookup lookup, String methodName) {}
|
||||
|
||||
/**
|
||||
* Proxy type, see {@link Proxy}
|
||||
*/
|
||||
enum Type {
|
||||
PROXY,
|
||||
SERVICE,
|
||||
CLIENT_PROXY,
|
||||
INTERNAL_SERVICE;
|
||||
|
||||
public boolean isPublicApi() {
|
||||
return this == PROXY || this == SERVICE;
|
||||
}
|
||||
|
||||
public boolean isService() {
|
||||
return this == SERVICE || this == INTERNAL_SERVICE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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.internal;
|
||||
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.loader.BootLoader;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.invoke.MethodHandles.Lookup;
|
||||
|
||||
/**
|
||||
* Proxy repository keeps track of all generated proxies.
|
||||
* @see ProxyRepository#getProxy(Class, Mapping[])
|
||||
*/
|
||||
class ProxyRepository {
|
||||
private static final Proxy NONE = Proxy.empty(null), INVALID = Proxy.empty(false);
|
||||
|
||||
private final Registry registry = new Registry();
|
||||
private final Map<Key, Proxy> proxies = new HashMap<>();
|
||||
|
||||
void init(InputStream extendedRegistryStream,
|
||||
Class<? extends Annotation> serviceAnnotation,
|
||||
Class<? extends Annotation> providedAnnotation,
|
||||
Class<? extends Annotation> providesAnnotation) {
|
||||
registry.initAnnotations(serviceAnnotation, providedAnnotation, providesAnnotation);
|
||||
if (extendedRegistryStream != null) registry.readEntries(extendedRegistryStream);
|
||||
}
|
||||
|
||||
String getVersion() {
|
||||
return registry.version;
|
||||
}
|
||||
|
||||
synchronized Proxy getProxy(Class<?> clazz, Mapping[] specialization) {
|
||||
Key key = new Key(clazz, specialization);
|
||||
Proxy p = proxies.get(key);
|
||||
if (p == null) {
|
||||
registry.updateClassLoader(clazz.getClassLoader());
|
||||
Mapping[] inverseSpecialization = specialization == null ? null :
|
||||
Stream.of(specialization).map(m -> m == null ? null : m.inverse()).toArray(Mapping[]::new);
|
||||
Key inverseKey = null;
|
||||
|
||||
Registry.Entry entry = registry.entries.get(key.clazz().getCanonicalName());
|
||||
if (entry != null) { // This is a registered proxy
|
||||
Proxy.Info infoByInterface = entry.resolve(),
|
||||
infoByTarget = entry.inverse != null ? entry.inverse.resolve() : null;
|
||||
inverseKey = infoByTarget != null && infoByTarget.interfaceLookup != null ?
|
||||
new Key(infoByTarget.interfaceLookup.lookupClass(), inverseSpecialization) : null;
|
||||
if ((infoByInterface == null && infoByTarget == null) ||
|
||||
infoByInterface == Registry.Entry.INVALID ||
|
||||
infoByTarget == Registry.Entry.INVALID) {
|
||||
p = INVALID;
|
||||
} else {
|
||||
p = Proxy.create(this, infoByInterface, specialization, infoByTarget, inverseSpecialization);
|
||||
}
|
||||
} else if (!Arrays.equals(specialization, inverseSpecialization)) { // Try implicit proxy
|
||||
inverseKey = new Key(clazz, inverseSpecialization);
|
||||
Lookup lookup = SharedSecrets.getJavaLangInvokeAccess().lookupIn(clazz);
|
||||
Proxy.Info info = new Proxy.Info(lookup, lookup, 0);
|
||||
p = Proxy.create(this, info, specialization, info, inverseSpecialization);
|
||||
} else p = NONE;
|
||||
proxies.put(key, p);
|
||||
if (inverseKey != null) proxies.put(inverseKey, p.inverse());
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private record Key(Class<?> clazz, Mapping[] specialization) {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Key key = (Key) o;
|
||||
if (!clazz.equals(key.clazz)) return false;
|
||||
return Arrays.equals(specialization, key.specialization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clazz.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(specialization);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return clazz.getName() + "<" + Arrays.toString(specialization) + ">";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry contains all information about mapping between JBR API interfaces and implementation.
|
||||
* This mapping information can be {@linkplain Entry#resolve() resolved} into {@link Proxy.Info}.
|
||||
*/
|
||||
private static class Registry {
|
||||
|
||||
private record StaticKey(String methodName, String targetMethodDescriptor) {}
|
||||
private record StaticValue(String targetType, String targetMethodName) {}
|
||||
|
||||
private class Entry {
|
||||
private static final Proxy.Info INVALID = new Proxy.Info(null, null, 0);
|
||||
|
||||
private final Map<StaticKey, StaticValue> staticMethods = new HashMap<>();
|
||||
private String type, target;
|
||||
private Entry inverse;
|
||||
private int flags;
|
||||
|
||||
private Entry(String type) { this.type = type; }
|
||||
|
||||
private Proxy.Info resolve() {
|
||||
if (type == null) return null;
|
||||
Lookup l, t;
|
||||
try {
|
||||
l = resolveType(type, classLoader);
|
||||
t = target != null ? resolveType(target, classLoader) : null;
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.err.println(type + " not eligible");
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
return INVALID;
|
||||
}
|
||||
if (l.lookupClass().isAnnotation() || l.lookupClass().isEnum() || l.lookupClass().isRecord()) {
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.err.println(type + " not eligible: not a class or interface");
|
||||
}
|
||||
return INVALID;
|
||||
}
|
||||
if (Modifier.isFinal(l.lookupClass().getModifiers()) || l.lookupClass().isSealed()) {
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.err.println(type + " not eligible: invalid type (final/sealed)");
|
||||
}
|
||||
return INVALID;
|
||||
}
|
||||
if (target == null) flags |= Proxy.SERVICE;
|
||||
if (needsAnnotation(l.lookupClass())) {
|
||||
if (!hasAnnotation(l.lookupClass(), providedAnnotation)) {
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.err.println(type + " not eligible: no @Provided annotation");
|
||||
}
|
||||
return INVALID;
|
||||
}
|
||||
if (!hasAnnotation(l.lookupClass(), serviceAnnotation)) flags &= ~Proxy.SERVICE;
|
||||
}
|
||||
Proxy.Info info;
|
||||
if (t != null) {
|
||||
if (needsAnnotation(t.lookupClass()) && !hasAnnotation(t.lookupClass(), providesAnnotation)) {
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.err.println(target + " not eligible: no @Provides annotation");
|
||||
}
|
||||
return INVALID;
|
||||
}
|
||||
info = new Proxy.Info(l, t, flags);
|
||||
} else info = new Proxy.Info(l, null, flags);
|
||||
for (var method : staticMethods.entrySet()) {
|
||||
String methodName = method.getKey().methodName;
|
||||
String targetMethodDescriptor = method.getKey().targetMethodDescriptor;
|
||||
String targetType = method.getValue().targetType;
|
||||
String targetMethodName = method.getValue().targetMethodName;
|
||||
try {
|
||||
Lookup lookup = resolveType(targetType, classLoader);
|
||||
MethodType mt = MethodType.fromMethodDescriptorString(targetMethodDescriptor, classLoader);
|
||||
MethodHandle handle = lookup.findStatic(lookup.lookupClass(), targetMethodName, mt);
|
||||
info.addStaticMethod(methodName, handle);
|
||||
} catch (ClassNotFoundException | IllegalArgumentException | TypeNotPresentException |
|
||||
NoSuchMethodException | IllegalAccessException | ClassCastException e) {
|
||||
if (JBRApi.VERBOSE) {
|
||||
System.err.println(targetType + "#" + targetMethodName + " cannot be resolved");
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private static Lookup resolveType(String type, ClassLoader classLoader) throws ClassNotFoundException {
|
||||
Class<?> c = null;
|
||||
try {
|
||||
c = Class.forName(type, false, classLoader);
|
||||
} catch (ClassNotFoundException e) {
|
||||
String dollars = type.replace('.', '$');
|
||||
for (int i = 0;; i++) {
|
||||
i = type.indexOf('.', i);
|
||||
if (i == -1) break;
|
||||
String name = type.substring(0, i) + dollars.substring(i);
|
||||
try {
|
||||
c = Class.forName(name, false, classLoader);
|
||||
break;
|
||||
} catch (ClassNotFoundException ignore) {}
|
||||
}
|
||||
if (c == null) throw e;
|
||||
}
|
||||
return SharedSecrets.getJavaLangInvokeAccess().lookupIn(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return type; }
|
||||
}
|
||||
|
||||
private Class<? extends Annotation> serviceAnnotation, providedAnnotation, providesAnnotation;
|
||||
private Module annotationsModule;
|
||||
private ClassLoader classLoader;
|
||||
private final Map<String, Entry> entries = new HashMap<>();
|
||||
private final String version;
|
||||
|
||||
private Registry() {
|
||||
try (InputStream registryStream = BootLoader.findResourceAsStream("java.base", "META-INF/jbrapi.registry")) {
|
||||
version = readEntries(registryStream);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initAnnotations(Class<? extends Annotation> serviceAnnotation,
|
||||
Class<? extends Annotation> providedAnnotation,
|
||||
Class<? extends Annotation> providesAnnotation) {
|
||||
this.serviceAnnotation = serviceAnnotation;
|
||||
this.providedAnnotation = providedAnnotation;
|
||||
this.providesAnnotation = providesAnnotation;
|
||||
annotationsModule = serviceAnnotation == null ? null : serviceAnnotation.getModule();
|
||||
if (annotationsModule != null) classLoader = annotationsModule.getClassLoader();
|
||||
}
|
||||
|
||||
private String readEntries(InputStream inputStream) {
|
||||
String version = null;
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
String s;
|
||||
while ((s = reader.readLine()) != null) {
|
||||
String[] tokens = s.split(" ");
|
||||
switch (tokens[0]) {
|
||||
case "TYPE" -> {
|
||||
Entry a = entries.computeIfAbsent(tokens[1], Entry::new);
|
||||
Entry b = entries.computeIfAbsent(tokens[2], Entry::new);
|
||||
if ((a.inverse != null || b.inverse != null) && (a.inverse != b || b.inverse != a)) {
|
||||
throw new RuntimeException("Conflicting mapping: " +
|
||||
tokens[1] + " <-> " + tokens[2] +
|
||||
", " + a + " -> " + a.inverse +
|
||||
", " + b + " -> " + b.inverse);
|
||||
}
|
||||
a.inverse = b;
|
||||
b.inverse = a;
|
||||
a.target = tokens[2];
|
||||
b.target = tokens[1];
|
||||
switch (tokens[3]) {
|
||||
case "SERVICE" -> {
|
||||
a.type = null;
|
||||
b.flags |= Proxy.SERVICE;
|
||||
}
|
||||
case "PROVIDES" -> a.type = null;
|
||||
case "PROVIDED" -> b.type = null;
|
||||
}
|
||||
if (tokens.length > 4 && tokens[4].equals("INTERNAL")) {
|
||||
a.flags |= Proxy.INTERNAL;
|
||||
b.flags |= Proxy.INTERNAL;
|
||||
}
|
||||
}
|
||||
case "STATIC" -> {
|
||||
Entry entry = entries.computeIfAbsent(tokens[4], Entry::new);
|
||||
StaticValue target = new StaticValue(tokens[1], tokens[2]);
|
||||
StaticValue prev = entry.staticMethods.put(new StaticKey(tokens[5], tokens[3]), target);
|
||||
if (prev != null && !prev.equals(target)) {
|
||||
throw new RuntimeException("Conflicting mapping: " +
|
||||
target.targetType + "#" + target.targetMethodName + " <- " +
|
||||
tokens[4] + "#" + tokens[5] + " -> " +
|
||||
prev.targetType + "#" + prev.targetMethodName);
|
||||
}
|
||||
if (tokens.length > 6 && tokens[6].equals("INTERNAL")) entry.flags |= Proxy.INTERNAL;
|
||||
}
|
||||
case "VERSION" -> version = tokens[1];
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
entries.clear();
|
||||
throw new RuntimeException(e);
|
||||
} catch (RuntimeException | Error e) {
|
||||
entries.clear();
|
||||
throw e;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
private synchronized void updateClassLoader(ClassLoader newLoader) {
|
||||
// New loader is descendant of current one -> update
|
||||
for (ClassLoader cl = newLoader;; cl = cl.getParent()) {
|
||||
if (cl == classLoader) {
|
||||
classLoader = newLoader;
|
||||
return;
|
||||
}
|
||||
if (cl == null) break;
|
||||
}
|
||||
// Current loader is descendant of the new one -> leave
|
||||
for (ClassLoader cl = classLoader;; cl = cl.getParent()) {
|
||||
if (cl == newLoader) return;
|
||||
if (cl == null) break;
|
||||
}
|
||||
// Independent classloaders -> error? Or maybe reset cache and start fresh?
|
||||
throw new RuntimeException("Incompatible classloader");
|
||||
}
|
||||
|
||||
private boolean needsAnnotation(Class<?> c) {
|
||||
return annotationsModule != null && annotationsModule.equals(c.getModule());
|
||||
}
|
||||
|
||||
private static boolean hasAnnotation(Class<?> c, Class<? extends Annotation> a) {
|
||||
return c.getAnnotation(a) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2000-2023 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.internal;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Raw proxy info, as it was registered through {@link JBRApi.ModuleRegistry}.
|
||||
* Contains all necessary information to create a {@linkplain Proxy proxy}.
|
||||
*/
|
||||
record RegisteredProxyInfo(MethodHandles.Lookup apiModule,
|
||||
String interfaceName,
|
||||
String[] targets,
|
||||
ProxyInfo.Type type,
|
||||
List<StaticMethodMapping> staticMethods) {
|
||||
|
||||
record StaticMethodMapping(String interfaceMethodName, String methodName, String[] classes) {}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.jetbrains.internal;
|
||||
|
||||
import jdk.internal.misc.VM;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
class Utils {
|
||||
|
||||
static final Function<Stream<StackTraceElement>, Stream<StackTraceElement>>
|
||||
BEFORE_BOOTSTRAP_DYNAMIC = st -> st
|
||||
.dropWhile(e -> !e.getClassName().equals("com.jetbrains.exported.JBRApiSupport") ||
|
||||
!e.getMethodName().equals("bootstrapDynamic")).skip(1)
|
||||
.dropWhile(e -> e.getClassName().startsWith("java.lang.invoke."));
|
||||
static final Function<Stream<StackTraceElement>, Stream<StackTraceElement>>
|
||||
BEFORE_JBR = st -> st
|
||||
.dropWhile(e -> !e.getClassName().equals("com.jetbrains.JBR"))
|
||||
.dropWhile(e -> e.getClassName().equals("com.jetbrains.JBR"));
|
||||
|
||||
static void log(Function<Stream<StackTraceElement>, Stream<StackTraceElement>> preprocessor,
|
||||
PrintStream out, String message) {
|
||||
StackTraceElement[] st = Thread.currentThread().getStackTrace();
|
||||
synchronized (out) {
|
||||
out.println(message);
|
||||
preprocessor.apply(Stream.of(st)).forEach(e -> out.println("\tat " + e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* System properties obtained this way are immune to runtime override via {@link System#setProperty(String, String)}.
|
||||
*/
|
||||
static boolean property(String name, boolean defaultValue) {
|
||||
String value = VM.getSavedProperty(name);
|
||||
if (value != null) {
|
||||
if (value.equalsIgnoreCase("true")) return true;
|
||||
if (value.equalsIgnoreCase("false")) return false;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* System properties obtained this way are immune to runtime override via {@link System#setProperty(String, String)}.
|
||||
*/
|
||||
static String property(String name, String defaultValue) {
|
||||
String value = VM.getSavedProperty(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,6 @@ package java.lang;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.misc.InternalLock;
|
||||
|
||||
@@ -1135,7 +1133,7 @@ public class Throwable implements Serializable {
|
||||
|
||||
private static volatile java.util.function.Supplier<String> $$jb$additionalInfoSupplier = null;
|
||||
|
||||
@JBRApi.Provides("Jstack#includeInfoFrom")
|
||||
// JBR API internals
|
||||
private static void $$jb$additionalInfoForJstack(java.util.function.Supplier<String> supplier) {
|
||||
$$jb$additionalInfoSupplier = supplier;
|
||||
}
|
||||
|
||||
@@ -1660,11 +1660,6 @@ abstract class MethodHandleImpl {
|
||||
public Class<?>[] exceptionTypes(MethodHandle handle) {
|
||||
return VarHandles.exceptionTypes(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lookup lookupIn(Class<?> lookupClass) {
|
||||
return IMPL_LOOKUP.in(lookupClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -173,11 +173,4 @@ public interface JavaLangInvokeAccess {
|
||||
* @return an array of exceptions, or {@code null}.
|
||||
*/
|
||||
Class<?>[] exceptionTypes(MethodHandle handle);
|
||||
|
||||
/**
|
||||
* Returns a lookup object corresponding to given class with full access privileges.
|
||||
* @param lookupClass lookup class
|
||||
* @return full-privileged lookup object
|
||||
*/
|
||||
Lookup lookupIn(Class<?> lookupClass);
|
||||
}
|
||||
|
||||
@@ -134,9 +134,10 @@ module java.base {
|
||||
// additional qualified exports may be inserted at build time
|
||||
// see make/gensrc/GenModuleInfo.gmk
|
||||
|
||||
exports com.jetbrains.exported;
|
||||
opens com.jetbrains.bootstrap;
|
||||
|
||||
exports com.jetbrains.internal to
|
||||
java.desktop;
|
||||
exports com.sun.crypto.provider to
|
||||
jdk.crypto.cryptoki;
|
||||
exports sun.invoke.util to
|
||||
|
||||
@@ -192,10 +192,7 @@ JNIEXPORT jint JNICALL Java_sun_awt_UNIXToolkit_isSystemDarkColorScheme() {
|
||||
if (!sendDBusMessageWithReply(msg_freedesktop_appearance, &res, DBUS_TYPE_UINT32)) {
|
||||
return UNKNOWN_RESULT;
|
||||
}
|
||||
/* From org.freedesktop.portal color-scheme specs:
|
||||
* 0: No preference, 1: Prefer dark appearance, 2: Prefer light appearance
|
||||
*/
|
||||
return res == 1;
|
||||
return res;
|
||||
} else {
|
||||
char *res = NULL;
|
||||
if (!sendDBusMessageWithReply(msg_gnome_desktop, &res, DBUS_TYPE_STRING)) {
|
||||
|
||||
@@ -218,7 +218,7 @@ MidiMessage* MIDI_IN_GetMessage(MidiDeviceHandle* handle) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
jdk_message = (MidiMessage*) calloc(1, sizeof(MidiMessage));
|
||||
jdk_message = (MidiMessage*) calloc(sizeof(MidiMessage), 1);
|
||||
if (!jdk_message) {
|
||||
ERROR0("< ERROR: MIDI_IN_GetMessage(): out of memory\n");
|
||||
return NULL;
|
||||
|
||||
@@ -383,7 +383,7 @@ INT32 openMidiDevice(snd_rawmidi_stream_t direction, INT32 deviceIndex,
|
||||
|
||||
TRACE0("> openMidiDevice()\n");
|
||||
|
||||
(*handle) = (MidiDeviceHandle*) calloc(1, sizeof(MidiDeviceHandle));
|
||||
(*handle) = (MidiDeviceHandle*) calloc(sizeof(MidiDeviceHandle), 1);
|
||||
if (!(*handle)) {
|
||||
ERROR0("ERROR: openDevice: out of memory\n");
|
||||
return MIDI_OUT_OF_MEMORY;
|
||||
|
||||
@@ -159,12 +159,6 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment {
|
||||
/* Populate the device table */
|
||||
rebuildDevices();
|
||||
|
||||
if (LogDisplay.ENABLED) {
|
||||
for (CGraphicsDevice gd : devices.values()) {
|
||||
LogDisplay.ADDED.log(gd.getDisplayID(), gd.getBounds(), gd.getScaleFactor());
|
||||
}
|
||||
}
|
||||
|
||||
/* Register our display reconfiguration listener */
|
||||
displayReconfigContext = registerDisplayReconfiguration();
|
||||
if (displayReconfigContext == 0L) {
|
||||
@@ -191,25 +185,13 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment {
|
||||
* @param displayId CoreGraphics displayId
|
||||
* @param removed true if displayId was removed, false otherwise.
|
||||
*/
|
||||
void _displayReconfiguration(int displayId, int flags) {
|
||||
// See CGDisplayChangeSummaryFlags
|
||||
LogDisplay log = !LogDisplay.ENABLED ? null :
|
||||
(flags & (1 << 4)) != 0 ? LogDisplay.ADDED :
|
||||
(flags & (1 << 5)) != 0 ? LogDisplay.REMOVED : LogDisplay.CHANGED;
|
||||
if (log == LogDisplay.REMOVED) {
|
||||
CGraphicsDevice gd = devices.get(displayId);
|
||||
log.log(displayId, gd != null ? gd.getBounds() : "UNKNOWN", gd != null ? gd.getScaleFactor() : Double.NaN);
|
||||
}
|
||||
void _displayReconfiguration(int displayId, boolean removed) {
|
||||
// we ignore the passed parameters and check removed devices ourself
|
||||
// Note that it is possible that this callback is called when the
|
||||
// monitors are not added nor removed, but when the video card is
|
||||
// switched to/from the discrete video card, so we should try to map the
|
||||
// old to the new devices.
|
||||
rebuildDevices();
|
||||
if (log != null && log != LogDisplay.REMOVED) {
|
||||
CGraphicsDevice gd = devices.get(displayId);
|
||||
log.log(displayId, gd != null ? gd.getBounds() : "UNKNOWN", gd != null ? gd.getScaleFactor() : Double.NaN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,23 +25,19 @@
|
||||
|
||||
package sun.java2d.metal;
|
||||
|
||||
import sun.awt.AWTAccessor;
|
||||
import sun.java2d.NullSurfaceData;
|
||||
import sun.java2d.SurfaceData;
|
||||
import sun.lwawt.LWWindowPeer;
|
||||
import sun.lwawt.macosx.CFLayer;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Window;
|
||||
|
||||
|
||||
public class MTLLayer extends CFLayer {
|
||||
private static final PlatformLogger logger = PlatformLogger.getLogger(MTLLayer.class.getName());
|
||||
|
||||
private native long nativeCreateLayer(boolean perfCountersEnabled);
|
||||
private native long nativeCreateLayer();
|
||||
private static native void nativeSetScale(long layerPtr, double scale);
|
||||
|
||||
// Pass the insets to native code to make adjustments in blitTexture
|
||||
@@ -55,10 +51,7 @@ public class MTLLayer extends CFLayer {
|
||||
public MTLLayer(LWWindowPeer peer) {
|
||||
super(0, true);
|
||||
|
||||
Window target = (peer != null) ? peer.getTarget() : null;
|
||||
boolean perfCountersEnabled = (target != null) && AWTAccessor.getWindowAccessor().countersEnabled(target);
|
||||
|
||||
setPtr(nativeCreateLayer(perfCountersEnabled));
|
||||
setPtr(nativeCreateLayer());
|
||||
this.peer = peer;
|
||||
|
||||
MTLGraphicsConfig gc = (MTLGraphicsConfig)getGraphicsConfiguration();
|
||||
@@ -149,22 +142,4 @@ public class MTLLayer extends CFLayer {
|
||||
rq.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void countNewFrame() {
|
||||
// Called from the native code when this layer has been presented on screen
|
||||
Component target = peer.getTarget();
|
||||
if (target instanceof Window window) {
|
||||
AWTAccessor.getWindowAccessor().bumpCounter(window, "java2d.native.frames");
|
||||
}
|
||||
}
|
||||
|
||||
private void countDroppedFrame() {
|
||||
// Called from the native code when an attempt was made to present this layer
|
||||
// on screen, but that attempt was not successful. This can happen, for example,
|
||||
// when those attempts are too frequent.
|
||||
Component target = peer.getTarget();
|
||||
if (target instanceof Window window) {
|
||||
AWTAccessor.getWindowAccessor().bumpCounter(window, "java2d.native.framesDropped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,6 @@ class CFileDialog implements FileDialogPeer {
|
||||
chooseFiles,
|
||||
createDirectories,
|
||||
target.getFilenameFilter() != null,
|
||||
jbrDialog.fileFilterExtensions,
|
||||
target.getDirectory(),
|
||||
target.getFile());
|
||||
|
||||
@@ -205,7 +204,7 @@ class CFileDialog implements FileDialogPeer {
|
||||
private native String[] nativeRunFileDialog(long ownerPtr, String title, int mode,
|
||||
boolean multipleMode, boolean shouldNavigateApps,
|
||||
boolean canChooseDirectories, boolean canChooseFiles,
|
||||
boolean canCreateDirectories, boolean hasFilenameFilter, String[] allowedFileTypes,
|
||||
boolean canCreateDirectories, boolean hasFilenameFilter,
|
||||
String directory, String file);
|
||||
|
||||
@Override
|
||||
|
||||
@@ -532,12 +532,6 @@ public class CInputMethod extends InputMethodAdapter {
|
||||
((TextComponent) fAwtFocussedComponent).select(selectionStart, selectionEnd);
|
||||
return;
|
||||
}
|
||||
|
||||
var desc = (CInputMethodDescriptor)LWCToolkit.getLWCToolkit().getInputMethodAdapterDescriptor();
|
||||
if (desc.textInputEventListener != null) {
|
||||
var event = new JBRTextInputMacOS.SelectTextRangeEvent(fAwtFocussedComponent, selectionStart, length);
|
||||
desc.textInputEventListener.handleSelectTextRangeEvent(event);
|
||||
}
|
||||
}
|
||||
}, fAwtFocussedComponent);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -37,7 +37,6 @@ import java.util.List;
|
||||
*/
|
||||
|
||||
public class CInputMethodDescriptor implements InputMethodDescriptor {
|
||||
JBRTextInputMacOS.EventListener textInputEventListener;
|
||||
|
||||
static {
|
||||
nativeInit();
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.awt.Rectangle;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.PaintEvent;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.peer.ComponentPeer;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
@@ -63,11 +64,11 @@ import javax.swing.SwingUtilities;
|
||||
|
||||
import com.apple.laf.ClientPropertyApplicator;
|
||||
import com.apple.laf.ClientPropertyApplicator.Property;
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import sun.awt.AWTAccessor;
|
||||
import sun.awt.AWTAccessor.ComponentAccessor;
|
||||
import sun.awt.AWTAccessor.WindowAccessor;
|
||||
import sun.awt.AWTThreading;
|
||||
import sun.awt.PaintEventDispatcher;
|
||||
import sun.java2d.SunGraphicsEnvironment;
|
||||
import sun.java2d.SurfaceData;
|
||||
import sun.lwawt.LWLightweightFramePeer;
|
||||
@@ -1486,7 +1487,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
isFullScreenAnimationOn = false;
|
||||
}
|
||||
|
||||
@JBRApi.Provides("java.awt.Window.CustomTitleBarPeer#update")
|
||||
// JBR API internals
|
||||
private static void updateCustomTitleBar(ComponentPeer peer) {
|
||||
if (peer instanceof LWWindowPeer lwwp &&
|
||||
lwwp.getPlatformWindow() instanceof CPlatformWindow cpw) {
|
||||
@@ -1494,7 +1495,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
}
|
||||
}
|
||||
|
||||
@JBRApi.Provides("RoundedCornersManager")
|
||||
// JBR API internals
|
||||
private static void setRoundedCorners(Window window, Object params) {
|
||||
Object peer = AWTAccessor.getComponentAccessor().getPeer(window);
|
||||
if (peer instanceof CPlatformWindow) {
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.lwawt.macosx;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
|
||||
@JBRApi.Service
|
||||
@JBRApi.Provides("TextInput")
|
||||
public class JBRTextInputMacOS {
|
||||
private EventListener listener;
|
||||
|
||||
JBRTextInputMacOS() {
|
||||
var desc = (CInputMethodDescriptor) LWCToolkit.getLWCToolkit().getInputMethodAdapterDescriptor();
|
||||
desc.textInputEventListener = new EventListener() {
|
||||
public void handleSelectTextRangeEvent(SelectTextRangeEvent event) {
|
||||
// This listener is called on the EDT
|
||||
synchronized (JBRTextInputMacOS.this) {
|
||||
if (listener != null) {
|
||||
listener.handleSelectTextRangeEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@JBRApi.Provides("TextInput.SelectTextRangeEvent")
|
||||
public static class SelectTextRangeEvent {
|
||||
private final Object source;
|
||||
private final int begin;
|
||||
private final int length;
|
||||
|
||||
public SelectTextRangeEvent(Object source, int begin, int length) {
|
||||
this.source = source;
|
||||
this.begin = begin;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public Object getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public int getBegin() {
|
||||
return begin;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
@JBRApi.Provided("TextInput.EventListener")
|
||||
public interface EventListener {
|
||||
void handleSelectTextRangeEvent(SelectTextRangeEvent event);
|
||||
}
|
||||
|
||||
public synchronized void setGlobalEventListener(EventListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
@@ -1213,7 +1213,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
isDisabled = !awtWindow.isEnabled;
|
||||
}
|
||||
|
||||
if (menuBar == nil && [ApplicationDelegate sharedDelegate] != nil) {
|
||||
if (menuBar == nil) {
|
||||
menuBar = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
|
||||
isDisabled = NO;
|
||||
}
|
||||
@@ -2267,7 +2267,7 @@ JNI_COCOA_ENTER(env);
|
||||
window.javaMenuBar = menuBar;
|
||||
|
||||
CMenuBar* actualMenuBar = menuBar;
|
||||
if (actualMenuBar == nil && [ApplicationDelegate sharedDelegate] != nil) {
|
||||
if (actualMenuBar == nil) {
|
||||
actualMenuBar = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
|
||||
}
|
||||
|
||||
|
||||
@@ -117,9 +117,8 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
// don't install the EAWT delegate if another kind of NSApplication is installed, like say, Safari
|
||||
BOOL shouldInstall = NO;
|
||||
BOOL overrideDelegate = (getenv("AWT_OVERRIDE_NSDELEGATE") != NULL);
|
||||
if (NSApp != nil) {
|
||||
if ([NSApp isMemberOfClass:[NSApplication class]] && overrideDelegate) shouldInstall = YES;
|
||||
if ([NSApp isMemberOfClass:[NSApplication class]]) shouldInstall = YES;
|
||||
if ([NSApp isKindOfClass:[NSApplicationAWT class]]) shouldInstall = YES;
|
||||
}
|
||||
checked = YES;
|
||||
@@ -416,19 +415,6 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
return NSTerminateLater;
|
||||
}
|
||||
|
||||
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
|
||||
static BOOL checked = NO;
|
||||
static BOOL supportsSecureState = YES;
|
||||
|
||||
if (checked == NO) {
|
||||
checked = YES;
|
||||
if (getenv("AWT_DISABLE_NSDELEGATE_SECURE_SAVE") != NULL) {
|
||||
supportsSecureState = NO;
|
||||
}
|
||||
}
|
||||
return supportsSecureState;
|
||||
}
|
||||
|
||||
+ (void)_systemWillPowerOff {
|
||||
[self _notifyJava:com_apple_eawt__AppEventHandler_NOTIFY_SHUTDOWN];
|
||||
}
|
||||
@@ -530,10 +516,8 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
[dockImageView setImageScaling:NSImageScaleProportionallyUpOrDown];
|
||||
[dockImageView setImage:image];
|
||||
|
||||
if ([ApplicationDelegate sharedDelegate] != nil) {
|
||||
[[ApplicationDelegate sharedDelegate].fProgressIndicator removeFromSuperview];
|
||||
[dockImageView addSubview:[ApplicationDelegate sharedDelegate].fProgressIndicator];
|
||||
}
|
||||
[[ApplicationDelegate sharedDelegate].fProgressIndicator removeFromSuperview];
|
||||
[dockImageView addSubview:[ApplicationDelegate sharedDelegate].fProgressIndicator];
|
||||
|
||||
// add it to the NSDockTile
|
||||
[dockTile setContentView: dockImageView];
|
||||
@@ -546,15 +530,14 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
ApplicationDelegate *delegate = [ApplicationDelegate sharedDelegate];
|
||||
if (delegate != nil) {
|
||||
if ([value doubleValue] >= 0 && [value doubleValue] <=100) {
|
||||
[delegate.fProgressIndicator setDoubleValue:[value doubleValue]];
|
||||
[delegate.fProgressIndicator setHidden:NO];
|
||||
} else {
|
||||
[delegate.fProgressIndicator setHidden:YES];
|
||||
}
|
||||
[[NSApp dockTile] display];
|
||||
if ([value doubleValue] >= 0 && [value doubleValue] <=100) {
|
||||
[delegate.fProgressIndicator setDoubleValue:[value doubleValue]];
|
||||
[delegate.fProgressIndicator setHidden:NO];
|
||||
} else {
|
||||
[delegate.fProgressIndicator setHidden:YES];
|
||||
}
|
||||
|
||||
[[NSApp dockTile] display];
|
||||
}
|
||||
|
||||
// Obtains the image of the Dock icon, either manually set, a drawn copy, or the default NSApplicationIcon
|
||||
@@ -665,9 +648,7 @@ JNI_COCOA_ENTER(env);
|
||||
|
||||
NSMenu *menu = (NSMenu *)jlong_to_ptr(nsMenuPtr);
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
|
||||
if ([ApplicationDelegate sharedDelegate] != nil) {
|
||||
[ApplicationDelegate sharedDelegate].fDockMenu = menu;
|
||||
}
|
||||
[ApplicationDelegate sharedDelegate].fDockMenu = menu;
|
||||
}];
|
||||
|
||||
JNI_COCOA_EXIT(env);
|
||||
@@ -847,15 +828,13 @@ JNI_COCOA_ENTER(env);
|
||||
|
||||
[ThreadUtilities performOnMainThreadWaiting:NO block:^(){
|
||||
ApplicationDelegate *delegate = [ApplicationDelegate sharedDelegate];
|
||||
if (delegate != nil) {
|
||||
switch (menuID) {
|
||||
case com_apple_eawt__AppMenuBarHandler_MENU_ABOUT:
|
||||
[delegate _updateAboutMenu:visible enabled:enabled];
|
||||
break;
|
||||
case com_apple_eawt__AppMenuBarHandler_MENU_PREFS:
|
||||
[delegate _updatePreferencesMenu:visible enabled:enabled];
|
||||
break;
|
||||
}
|
||||
switch (menuID) {
|
||||
case com_apple_eawt__AppMenuBarHandler_MENU_ABOUT:
|
||||
[delegate _updateAboutMenu:visible enabled:enabled];
|
||||
break;
|
||||
case com_apple_eawt__AppMenuBarHandler_MENU_PREFS:
|
||||
[delegate _updatePreferencesMenu:visible enabled:enabled];
|
||||
break;
|
||||
}
|
||||
}];
|
||||
|
||||
@@ -874,9 +853,7 @@ JNI_COCOA_ENTER(env);
|
||||
|
||||
CMenuBar *menu = (CMenuBar *)jlong_to_ptr(cMenuBarPtr);
|
||||
[ThreadUtilities performOnMainThreadWaiting:NO block:^(){
|
||||
if ([ApplicationDelegate sharedDelegate] != nil) {
|
||||
[ApplicationDelegate sharedDelegate].fDefaultMenuBar = menu;
|
||||
}
|
||||
[ApplicationDelegate sharedDelegate].fDefaultMenuBar = menu;
|
||||
}];
|
||||
|
||||
JNI_COCOA_EXIT(env);
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
// Should we query back to Java for a file filter?
|
||||
jboolean fHasFileFilter;
|
||||
|
||||
// Allowed file types
|
||||
NSArray *fFileTypes;
|
||||
|
||||
// sun.awt.CFileDialog
|
||||
jobject fFileDialog;
|
||||
|
||||
@@ -71,8 +68,7 @@
|
||||
|
||||
// Allocator
|
||||
- (id) initWithOwner:(NSWindow*) owner
|
||||
filter:(jboolean)inHasFilter
|
||||
fileTypes:(NSArray *)inFileTypes
|
||||
filter:(jboolean)inHasFilter
|
||||
fileDialog:(jobject)inDialog
|
||||
title:(NSString *)inTitle
|
||||
directory:(NSString *)inPath
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#import <sys/stat.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
|
||||
#import "ThreadUtilities.h"
|
||||
#import "JNIUtilities.h"
|
||||
@@ -41,7 +40,6 @@
|
||||
|
||||
- (id)initWithOwner:(NSWindow*)owner
|
||||
filter:(jboolean)inHasFilter
|
||||
fileTypes:(NSArray *)inFileTypes
|
||||
fileDialog:(jobject)inDialog
|
||||
title:(NSString *)inTitle
|
||||
directory:(NSString *)inPath
|
||||
@@ -58,8 +56,6 @@ canCreateDirectories:(BOOL)inCreateDirectories
|
||||
fOwner = owner;
|
||||
[fOwner retain];
|
||||
fHasFileFilter = inHasFilter;
|
||||
fFileTypes = inFileTypes;
|
||||
[fFileTypes retain];
|
||||
fFileDialog = (*env)->NewGlobalRef(env, inDialog);
|
||||
fDirectory = inPath;
|
||||
[fDirectory retain];
|
||||
@@ -88,9 +84,6 @@ canCreateDirectories:(BOOL)inCreateDirectories
|
||||
}
|
||||
|
||||
-(void) dealloc {
|
||||
[fFileTypes release];
|
||||
fFileTypes = nil;
|
||||
|
||||
[fDirectory release];
|
||||
fDirectory = nil;
|
||||
|
||||
@@ -130,23 +123,6 @@ canCreateDirectories:(BOOL)inCreateDirectories
|
||||
if (thePanel != nil) {
|
||||
[thePanel setTitle:fTitle];
|
||||
|
||||
if (fFileTypes != nil) {
|
||||
if (@available(macOS 11, *)) {
|
||||
int nTypes = (int)[fFileTypes count];
|
||||
NSMutableArray *contentTypes = [NSMutableArray arrayWithCapacity:nTypes];
|
||||
for (int i = 0; i < nTypes; ++i) {
|
||||
NSString *fileType = (NSString *)[fFileTypes objectAtIndex:i];
|
||||
UTType *contentType = [UTType typeWithFilenameExtension:fileType conformingToType:UTTypeData];
|
||||
if (contentType != nil) {
|
||||
[contentTypes addObject:contentType];
|
||||
}
|
||||
}
|
||||
[thePanel setAllowedContentTypes:contentTypes];
|
||||
} else {
|
||||
[thePanel setAllowedFileTypes:fFileTypes];
|
||||
}
|
||||
}
|
||||
|
||||
if (fNavigateApps) {
|
||||
[thePanel setTreatsFilePackagesAsDirectories:YES];
|
||||
}
|
||||
@@ -328,7 +304,7 @@ JNIEXPORT jobjectArray JNICALL
|
||||
Java_sun_lwawt_macosx_CFileDialog_nativeRunFileDialog
|
||||
(JNIEnv *env, jobject peer, jlong ownerPtr, jstring title, jint mode, jboolean multipleMode,
|
||||
jboolean navigateApps, jboolean chooseDirectories, jboolean chooseFiles, jboolean createDirectories,
|
||||
jboolean hasFilter, jobjectArray allowedFileTypes, jstring directory, jstring file)
|
||||
jboolean hasFilter, jstring directory, jstring file)
|
||||
{
|
||||
jobjectArray returnValue = NULL;
|
||||
|
||||
@@ -338,22 +314,8 @@ JNI_COCOA_ENTER(env);
|
||||
dialogTitle = @" ";
|
||||
}
|
||||
|
||||
NSMutableArray *fileTypes = nil;
|
||||
if (allowedFileTypes != nil) {
|
||||
int nTypes = (*env)->GetArrayLength(env, allowedFileTypes);
|
||||
if (nTypes > 0) {
|
||||
fileTypes = [NSMutableArray arrayWithCapacity:nTypes];
|
||||
for (int i = 0; i < nTypes; i++) {
|
||||
jstring fileType = (jstring)(*env)->GetObjectArrayElement(env, allowedFileTypes, i);
|
||||
[fileTypes addObject:JavaStringToNSString(env, fileType)];
|
||||
(*env)->DeleteLocalRef(env, fileType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFileDialog *dialogDelegate = [[CFileDialog alloc] initWithOwner:(NSWindow *)jlong_to_ptr(ownerPtr)
|
||||
filter:hasFilter
|
||||
fileTypes:fileTypes
|
||||
fileDialog:peer
|
||||
title:dialogTitle
|
||||
directory:JavaStringToNSString(env, directory)
|
||||
|
||||
@@ -122,9 +122,9 @@ static void displaycb_handle
|
||||
if (graphicsEnv == NULL) return; // ref already GC'd
|
||||
DECLARE_CLASS(jc_CGraphicsEnvironment, "sun/awt/CGraphicsEnvironment");
|
||||
DECLARE_METHOD(jm_displayReconfiguration,
|
||||
jc_CGraphicsEnvironment, "_displayReconfiguration","(II)V");
|
||||
jc_CGraphicsEnvironment, "_displayReconfiguration","(IZ)V");
|
||||
(*env)->CallVoidMethod(env, graphicsEnv, jm_displayReconfiguration,
|
||||
(jint) display, (jint) flags);
|
||||
(jint) display, (jboolean) flags & kCGDisplayRemoveFlag);
|
||||
(*env)->DeleteLocalRef(env, graphicsEnv);
|
||||
CHECK_EXCEPTION();
|
||||
}];
|
||||
|
||||
@@ -210,11 +210,9 @@ static BOOL sSetupHelpMenu = NO;
|
||||
// In theory, this might cause flickering if the window gaining focus
|
||||
// has its own menu. However, I couldn't reproduce it on practice, so
|
||||
// perhaps this is a non issue.
|
||||
if ([ApplicationDelegate sharedDelegate] != nil) {
|
||||
CMenuBar* defaultMenu = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
|
||||
if (defaultMenu != nil) {
|
||||
[CMenuBar activate:defaultMenu modallyDisabled:NO];
|
||||
}
|
||||
CMenuBar* defaultMenu = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
|
||||
if (defaultMenu != nil) {
|
||||
[CMenuBar activate:defaultMenu modallyDisabled:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,8 @@
|
||||
@property (readwrite, assign) int leftInset;
|
||||
@property (readwrite, atomic) int redrawCount;
|
||||
@property (readwrite, atomic) NSTimeInterval avgBlitFrameTime;
|
||||
@property (readwrite, atomic) BOOL perfCountersEnabled;
|
||||
|
||||
- (id) initWithJavaLayer:(jobject)layer usePerfCounters:(jboolean)perfCountersEnabled;
|
||||
- (id) initWithJavaLayer:(jobject)layer;
|
||||
|
||||
- (void) blitTexture;
|
||||
- (void) fillParallelogramCtxX:(jfloat)x
|
||||
@@ -59,8 +58,6 @@
|
||||
- (void) stopRedraw:(BOOL)force;
|
||||
- (void) flushBuffer;
|
||||
- (void) commitCommandBuffer:(MTLContext*)mtlc wait:(BOOL)waitUntilCompleted display:(BOOL)updateDisplay;
|
||||
- (void) countFramePresentedCallback;
|
||||
- (void) countFrameDroppedCallback;
|
||||
@end
|
||||
|
||||
#endif /* MTLLayer_h_Included */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2023, 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
|
||||
@@ -35,10 +35,6 @@
|
||||
#define MAX_DRAWABLE 3
|
||||
#define LAST_DRAWABLE (MAX_DRAWABLE - 1)
|
||||
|
||||
static jclass jc_JavaLayer = NULL;
|
||||
#define GET_MTL_LAYER_CLASS() \
|
||||
GET_CLASS(jc_JavaLayer, "sun/java2d/metal/MTLLayer");
|
||||
|
||||
const NSTimeInterval DF_BLIT_FRAME_TIME=1.0/120.0;
|
||||
|
||||
BOOL isDisplaySyncEnabled() {
|
||||
@@ -119,7 +115,7 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
|
||||
|
||||
@implementation MTLLayer
|
||||
|
||||
- (id) initWithJavaLayer:(jobject)layer usePerfCounters:(jboolean)perfCountersEnabled
|
||||
- (id) initWithJavaLayer:(jobject)layer
|
||||
{
|
||||
AWT_ASSERT_APPKIT_THREAD;
|
||||
// Initialize ourselves
|
||||
@@ -161,7 +157,6 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
|
||||
}
|
||||
self.presentsWithTransaction = NO;
|
||||
self.avgBlitFrameTime = DF_BLIT_FRAME_TIME;
|
||||
self.perfCountersEnabled = perfCountersEnabled ? YES : NO;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -226,21 +221,6 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
|
||||
destinationOrigin:MTLOriginMake(0, 0, 0)];
|
||||
[blitEncoder endEncoding];
|
||||
|
||||
if (@available(macOS 10.15.4, *)) {
|
||||
if (self.perfCountersEnabled) {
|
||||
[self retain];
|
||||
[mtlDrawable addPresentedHandler:^(id <MTLDrawable> drawable) {
|
||||
// note: called anyway even if drawable.present() not called!
|
||||
const CFTimeInterval presentedTime = drawable.presentedTime;
|
||||
if (presentedTime != 0.0) {
|
||||
[self countFramePresentedCallback];
|
||||
} else {
|
||||
[self countFrameDroppedCallback];
|
||||
}
|
||||
[self release];
|
||||
}];
|
||||
}
|
||||
}
|
||||
if (isDisplaySyncEnabled()) {
|
||||
[commandBuf presentDrawable:mtlDrawable];
|
||||
} else {
|
||||
@@ -283,11 +263,11 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
|
||||
- (void) blitCallback {
|
||||
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
GET_MTL_LAYER_CLASS();
|
||||
DECLARE_CLASS(jc_JavaLayer, "sun/java2d/metal/MTLLayer");
|
||||
DECLARE_METHOD(jm_drawInMTLContext, jc_JavaLayer, "drawInMTLContext", "()V");
|
||||
|
||||
jobject javaLayerLocalRef = (*env)->NewLocalRef(env, self.javaLayer);
|
||||
if (javaLayerLocalRef == NULL) {
|
||||
if ((*env)->IsSameObject(env, javaLayerLocalRef, NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -386,34 +366,6 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
|
||||
[self startRedraw];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) countFramePresentedCallback {
|
||||
// attach the current thread to the JVM if necessary, and get an env
|
||||
JNIEnv* env = [ThreadUtilities getJNIEnvUncached];
|
||||
GET_MTL_LAYER_CLASS();
|
||||
DECLARE_METHOD(jm_countNewFrame, jc_JavaLayer, "countNewFrame", "()V");
|
||||
|
||||
jobject javaLayerLocalRef = (*env)->NewLocalRef(env, self.javaLayer);
|
||||
if (javaLayerLocalRef != NULL) {
|
||||
(*env)->CallVoidMethod(env, javaLayerLocalRef, jm_countNewFrame);
|
||||
CHECK_EXCEPTION();
|
||||
(*env)->DeleteLocalRef(env, javaLayerLocalRef);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) countFrameDroppedCallback {
|
||||
// attach the current thread to the JVM if necessary, and get an env
|
||||
JNIEnv* env = [ThreadUtilities getJNIEnvUncached];
|
||||
GET_MTL_LAYER_CLASS();
|
||||
DECLARE_METHOD(jm_countDroppedFrame, jc_JavaLayer, "countDroppedFrame", "()V");
|
||||
|
||||
jobject javaLayerLocalRef = (*env)->NewLocalRef(env, self.javaLayer);
|
||||
if (javaLayerLocalRef != NULL) {
|
||||
(*env)->CallVoidMethod(env, javaLayerLocalRef, jm_countDroppedFrame);
|
||||
CHECK_EXCEPTION();
|
||||
(*env)->DeleteLocalRef(env, javaLayerLocalRef);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
/*
|
||||
@@ -423,7 +375,7 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_sun_java2d_metal_MTLLayer_nativeCreateLayer
|
||||
(JNIEnv *env, jobject obj, jboolean perfCountersEnabled)
|
||||
(JNIEnv *env, jobject obj)
|
||||
{
|
||||
__block MTLLayer *layer = nil;
|
||||
|
||||
@@ -434,7 +386,7 @@ JNI_COCOA_ENTER(env);
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
|
||||
AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
layer = [[MTLLayer alloc] initWithJavaLayer: javaLayer usePerfCounters: perfCountersEnabled];
|
||||
layer = [[MTLLayer alloc] initWithJavaLayer: javaLayer];
|
||||
}];
|
||||
|
||||
JNI_COCOA_EXIT(env);
|
||||
|
||||
@@ -200,21 +200,6 @@
|
||||
} copy]];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app
|
||||
{
|
||||
static BOOL checked = NO;
|
||||
static BOOL supportsSecureState = YES;
|
||||
|
||||
if (checked == NO) {
|
||||
checked = YES;
|
||||
if (getenv("AWT_DISABLE_NSDELEGATE_SECURE_SAVE") != NULL) {
|
||||
supportsSecureState = NO;
|
||||
}
|
||||
}
|
||||
return supportsSecureState;
|
||||
}
|
||||
|
||||
- (void)processQueuedEventsWithTargetDelegate:(id <NSApplicationDelegate>)delegate
|
||||
{
|
||||
self.realDelegate = delegate;
|
||||
|
||||
@@ -23,12 +23,10 @@
|
||||
|
||||
package com.jetbrains.desktop;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import sun.awt.ConstrainableGraphics;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
@JBRApi.Provided("GraphicsUtils.ConstrainableGraphics2D")
|
||||
public interface ConstrainableGraphics2D extends ConstrainableGraphics {
|
||||
public void constrain(Rectangle2D region);
|
||||
public Object getDestination();
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2000-2023 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.internal.JBRApi;
|
||||
|
||||
import java.awt.*;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FontExtensions {
|
||||
private interface FontExtension {
|
||||
FontExtension INSTANCE = (FontExtension) JBRApi.internalServiceBuilder(MethodHandles.lookup())
|
||||
.withStatic("getFeatures", "getFeatures", "java.awt.Font")
|
||||
.withStatic("isComplexRendering", "isComplexRendering", "java.awt.Font")
|
||||
.withStatic("isKerning", "isKerning", "java.awt.Font")
|
||||
.build();
|
||||
|
||||
TreeMap<String, Integer> getFeatures(Font font);
|
||||
boolean isComplexRendering(Font font);
|
||||
boolean isKerning(Font font);
|
||||
}
|
||||
|
||||
public static String[] featuresToStringArray(Map<String, Integer> features) {
|
||||
return features.entrySet().stream().map(feature -> (feature.getKey() + "=" + feature.getValue())).
|
||||
toArray(String[]::new);
|
||||
}
|
||||
|
||||
public static TreeMap<String, Integer> getFeatures(Font font) {
|
||||
return FontExtension.INSTANCE.getFeatures(font);
|
||||
}
|
||||
|
||||
public static boolean isComplexRendering(Font font) {
|
||||
return FontExtension.INSTANCE.isComplexRendering(font);
|
||||
}
|
||||
|
||||
public static boolean isKerning(Font font) {
|
||||
return FontExtension.INSTANCE.isKerning(font);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2000-2023 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.internal.JBRApi;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
/**
|
||||
* This class contains mapping between JBR API interfaces and implementation in {@code java.desktop} module.
|
||||
*/
|
||||
public class JBRApiModule {
|
||||
static {
|
||||
JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports)
|
||||
.service("com.jetbrains.ExtendedGlyphCache")
|
||||
.withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities")
|
||||
.service("com.jetbrains.JBRFileDialogService")
|
||||
.withStatic("getFileDialog", "get", "com.jetbrains.desktop.JBRFileDialog")
|
||||
.proxy("com.jetbrains.JBRFileDialog", "com.jetbrains.desktop.JBRFileDialog")
|
||||
.service("com.jetbrains.CustomWindowDecoration", "java.awt.Window$CustomWindowDecoration")
|
||||
.service("com.jetbrains.RoundedCornersManager")
|
||||
.withStatic("setRoundedCorners", "setRoundedCorners", "sun.lwawt.macosx.CPlatformWindow",
|
||||
"sun.awt.windows.WWindowPeer")
|
||||
.service("com.jetbrains.DesktopActions")
|
||||
.withStatic("setHandler", "setDesktopActionsHandler", "java.awt.Desktop")
|
||||
.clientProxy("java.awt.Desktop$DesktopActionsHandler", "com.jetbrains.DesktopActions$Handler")
|
||||
.service("com.jetbrains.ProjectorUtils")
|
||||
.withStatic("overrideGraphicsEnvironment", "overrideLocalGraphicsEnvironment", "java.awt.GraphicsEnvironment")
|
||||
.withStatic("setLocalGraphicsEnvironmentProvider", "setLocalGraphicsEnvironmentProvider", "java.awt.GraphicsEnvironment")
|
||||
.service("com.jetbrains.AccessibleAnnouncer")
|
||||
.withStatic("announce", "announce", "sun.swing.AccessibleAnnouncer")
|
||||
.service("com.jetbrains.GraphicsUtils")
|
||||
.withStatic("createConstrainableGraphics", "create", "com.jetbrains.desktop.JBRGraphicsDelegate")
|
||||
.clientProxy("com.jetbrains.desktop.ConstrainableGraphics2D", "com.jetbrains.GraphicsUtils$ConstrainableGraphics2D")
|
||||
.service("com.jetbrains.WindowDecorations", "java.awt.Window$WindowDecorations")
|
||||
.proxy("com.jetbrains.WindowDecorations$CustomTitleBar", "java.awt.Window$CustomTitleBar")
|
||||
.service("com.jetbrains.FontExtensions")
|
||||
.withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities")
|
||||
.withStatic("deriveFontWithFeatures", "deriveFont", "java.awt.Font")
|
||||
.withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font")
|
||||
.service("com.jetbrains.FontOpenTypeFeatures")
|
||||
.withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font")
|
||||
.clientProxy("java.awt.Font$Features", "com.jetbrains.FontExtensions$Features")
|
||||
.service("com.jetbrains.WindowMove", "java.awt.Window$WindowMoveService")
|
||||
.service("com.jetbrains.FontMetricsAccessor", "sun.font.FontDesignMetrics$Accessor")
|
||||
.clientProxy("sun.font.FontDesignMetrics$Overrider", "com.jetbrains.FontMetricsAccessor$Overrider")
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,13 @@
|
||||
package com.jetbrains.desktop;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Native;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
|
||||
@JBRApi.Provides("JBRFileDialog")
|
||||
public final class JBRFileDialog implements Serializable {
|
||||
public class JBRFileDialog implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -9154712118353824660L;
|
||||
@@ -25,25 +21,32 @@ public final class JBRFileDialog implements Serializable {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@JBRApi.Provides("JBRFileDialogService#getFileDialog")
|
||||
public static JBRFileDialog get(FileDialog dialog) {
|
||||
return (JBRFileDialog) getter.get(dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to select files, directories or both (used when common file dialogs are enabled on Windows, or on macOS)
|
||||
*/
|
||||
@Native public static final int SELECT_FILES_HINT = 1, SELECT_DIRECTORIES_HINT = 2;
|
||||
/**
|
||||
* Whether to allow creating directories or not (used on macOS)
|
||||
*/
|
||||
@Native public static final int CREATE_DIRECTORIES_HINT = 4;
|
||||
|
||||
public static final String OPEN_FILE_BUTTON_KEY = "jbrFileDialogOpenFile";
|
||||
public static final String OPEN_DIRECTORY_BUTTON_KEY = "jbrFileDialogSelectDir";
|
||||
public static final String ALL_FILES_COMBO_KEY = "jbrFileDialogAllFiles";
|
||||
|
||||
public int hints = CREATE_DIRECTORIES_HINT;
|
||||
|
||||
/**
|
||||
* Text for "Open" button (used when common file dialogs are enabled on
|
||||
* Windows).
|
||||
*/
|
||||
public String openButtonText;
|
||||
|
||||
/**
|
||||
* Text for "Select Folder" button (used when common file dialogs are
|
||||
* enabled on Windows).
|
||||
*/
|
||||
public String selectFolderButtonText;
|
||||
public String allFilesFilterDescription;
|
||||
public String fileFilterDescription;
|
||||
public String[] fileFilterExtensions;
|
||||
|
||||
public void setHints(int hints) {
|
||||
this.hints = hints;
|
||||
@@ -52,24 +55,9 @@ public final class JBRFileDialog implements Serializable {
|
||||
return hints;
|
||||
}
|
||||
|
||||
public void setLocalizationString(String key, String text) {
|
||||
Objects.requireNonNull(key);
|
||||
switch (key) {
|
||||
case OPEN_FILE_BUTTON_KEY -> openButtonText = text;
|
||||
case OPEN_DIRECTORY_BUTTON_KEY -> selectFolderButtonText = text;
|
||||
case ALL_FILES_COMBO_KEY -> allFilesFilterDescription = text;
|
||||
default -> throw new IllegalArgumentException("unrecognized key: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public void setLocalizationStrings(String openButtonText, String selectFolderButtonText) {
|
||||
setLocalizationString(OPEN_FILE_BUTTON_KEY, openButtonText);
|
||||
setLocalizationString(OPEN_DIRECTORY_BUTTON_KEY, selectFolderButtonText);
|
||||
this.openButtonText = openButtonText;
|
||||
this.selectFolderButtonText = selectFolderButtonText;
|
||||
}
|
||||
|
||||
public void setFileFilterExtensions(String fileFilterDescription, String[] fileFilterExtensions) {
|
||||
this.fileFilterDescription = fileFilterDescription;
|
||||
this.fileFilterExtensions = fileFilterExtensions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
|
||||
package com.jetbrains.desktop;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.GlyphVector;
|
||||
@@ -40,7 +38,6 @@ import java.util.Map;
|
||||
|
||||
|
||||
public class JBRGraphicsDelegate extends Graphics2D implements ConstrainableGraphics2D {
|
||||
@JBRApi.Provides("GraphicsUtils#createConstrainableGraphics")
|
||||
public static Graphics2D create(Graphics2D graphics2D,
|
||||
ConstrainableGraphics2D constrainable) {
|
||||
return new JBRGraphicsDelegate(graphics2D, constrainable);
|
||||
|
||||
@@ -49,7 +49,6 @@ import java.util.Objects;
|
||||
|
||||
import javax.swing.JMenuBar;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import sun.awt.SunToolkit;
|
||||
import sun.security.util.SecurityConstants;
|
||||
|
||||
@@ -1073,7 +1072,6 @@ public class Desktop {
|
||||
return peer.moveToTrash(file);
|
||||
}
|
||||
|
||||
@JBRApi.Provided("DesktopActions.Handler")
|
||||
private interface DesktopActionsHandler {
|
||||
void open(File file) throws IOException;
|
||||
void edit(File file) throws IOException;
|
||||
@@ -1106,8 +1104,7 @@ public class Desktop {
|
||||
}
|
||||
private static volatile DesktopActions actions;
|
||||
|
||||
@JBRApi.Provides("DesktopActions#setHandler")
|
||||
private static void setDesktopActionsHandler(DesktopActionsHandler h) {
|
||||
static void setDesktopActionsHandler(DesktopActionsHandler h) {
|
||||
try {
|
||||
actions = new DesktopActions(h);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -48,12 +48,17 @@ import java.security.AccessController;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.text.AttributedCharacterIterator.Attribute;
|
||||
import java.text.CharacterIterator;
|
||||
import java.util.*;
|
||||
import java.util.EventListener;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import sun.awt.ComponentFactory;
|
||||
import sun.font.AttributeMap;
|
||||
import sun.font.AttributeValues;
|
||||
import sun.font.CompositeFont;
|
||||
import sun.font.CoreMetrics;
|
||||
import sun.font.CreatedFontTracker;
|
||||
import sun.font.Font2D;
|
||||
@@ -279,28 +284,6 @@ public class Font implements java.io.Serializable
|
||||
public FontPeer getFontPeer(final Font font) {
|
||||
return font.getFontPeer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getFeatures(Font font) {
|
||||
int size = font.featureArray == null ? 0 : font.featureArray.length;
|
||||
String[] fs = new String[size + 3];
|
||||
if (size > 0) System.arraycopy(font.featureArray, 0, fs, 0, size);
|
||||
fs[size++] = KERN_FEATURE + "=" + font.getAttributeValues().getKerning();
|
||||
fs[size++] = LIGA_FEATURE + "=" + font.getAttributeValues().getLigatures();
|
||||
fs[size] = CALT_FEATURE + "=" + font.getAttributeValues().getLigatures();
|
||||
return fs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplexRendering(Font font) {
|
||||
return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 ||
|
||||
font.values.getBaselineTransform() != null)) || font.featureArray != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKerning(Font font) {
|
||||
return font.values != null && (font.values.getKerning() != 0);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -472,11 +455,10 @@ public class Font implements java.io.Serializable
|
||||
* Ordered map choose intentionally as field's type. It allows to correctly comparing two Font objects
|
||||
*
|
||||
* @serial
|
||||
* @see #getFeatures
|
||||
* @see #deriveFont(Font, Features)
|
||||
*/
|
||||
private String[] featureArray;
|
||||
@Deprecated
|
||||
private TreeMap<String, Integer> features; // Kept for compatibility
|
||||
private TreeMap<String, Integer> features = new TreeMap<String, Integer>();
|
||||
|
||||
/**
|
||||
* The platform specific font information.
|
||||
@@ -579,6 +561,10 @@ public class Font implements java.io.Serializable
|
||||
return font2DHandle.font2D;
|
||||
}
|
||||
|
||||
private boolean anyEnabledFeatures() {
|
||||
return features.values().stream().anyMatch(x -> x != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code Font} from the specified name, style and
|
||||
* point size.
|
||||
@@ -642,19 +628,18 @@ public class Font implements java.io.Serializable
|
||||
this.pointSize = size;
|
||||
}
|
||||
|
||||
private Font(String name, int style, float sizePts, String[] features) {
|
||||
private Font(String name, int style, float sizePts, TreeMap<String, Integer> features) {
|
||||
this.name = (name != null) ? name : "Default";
|
||||
this.style = (style & ~0x03) == 0 ? style : 0;
|
||||
this.size = (int)(sizePts + 0.5);
|
||||
this.pointSize = sizePts;
|
||||
this.featureArray = features;
|
||||
this.hasLayoutAttributes |= this.featureArray != null;
|
||||
this.features = features;
|
||||
}
|
||||
|
||||
/* This constructor is used by deriveFont when attributes is null */
|
||||
private Font(String name, int style, float sizePts,
|
||||
boolean created, boolean withFallback,
|
||||
Font2DHandle handle, boolean useOldHandle, String[] features) {
|
||||
Font2DHandle handle, boolean useOldHandle, TreeMap<String, Integer> features) {
|
||||
this(name, style, sizePts, features);
|
||||
this.createdFont = created;
|
||||
this.withFallback = withFallback;
|
||||
@@ -719,9 +704,9 @@ public class Font implements java.io.Serializable
|
||||
*/
|
||||
private Font(AttributeValues values, String oldName, int oldStyle,
|
||||
boolean created, boolean withFallback,
|
||||
Font2DHandle handle, boolean useOldHandle, String[] features) {
|
||||
Font2DHandle handle, boolean useOldHandle, TreeMap<String, Integer> features) {
|
||||
|
||||
this.featureArray = features;
|
||||
this.features = features;
|
||||
this.createdFont = created;
|
||||
this.withFallback = withFallback;
|
||||
if (created || withFallback) {
|
||||
@@ -755,7 +740,6 @@ public class Font implements java.io.Serializable
|
||||
this.font2DHandle = handle;
|
||||
}
|
||||
initFromValues(values);
|
||||
this.hasLayoutAttributes |= this.featureArray != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -784,11 +768,6 @@ public class Font implements java.io.Serializable
|
||||
* @since 1.6
|
||||
*/
|
||||
protected Font(Font font) {
|
||||
this(font, font.featureArray);
|
||||
}
|
||||
|
||||
private Font(Font font, String[] features) {
|
||||
this.featureArray = features;
|
||||
if (font.values != null) {
|
||||
initFromValues(font.getAttributeValues().clone());
|
||||
} else {
|
||||
@@ -797,10 +776,15 @@ public class Font implements java.io.Serializable
|
||||
this.size = font.size;
|
||||
this.pointSize = font.pointSize;
|
||||
}
|
||||
this.hasLayoutAttributes |= this.featureArray != null;
|
||||
this.font2DHandle = font.font2DHandle;
|
||||
this.createdFont = font.createdFont;
|
||||
this.withFallback = font.withFallback;
|
||||
this.features = font.features;
|
||||
}
|
||||
|
||||
private Font(Font font, TreeMap<String, Integer> features) {
|
||||
this(font);
|
||||
this.features = features;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -850,7 +834,7 @@ public class Font implements java.io.Serializable
|
||||
if (values.getPosture() >= .2f) this.style |= ITALIC; // not == .2f
|
||||
|
||||
this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK);
|
||||
this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
|
||||
this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -931,7 +915,7 @@ public class Font implements java.io.Serializable
|
||||
values.merge(attributes, SECONDARY_MASK);
|
||||
return new Font(values, font.name, font.style,
|
||||
font.createdFont, font.withFallback,
|
||||
font.font2DHandle, false, null);
|
||||
font.font2DHandle, false, new TreeMap<String, Integer>());
|
||||
}
|
||||
return new Font(attributes);
|
||||
}
|
||||
@@ -943,7 +927,7 @@ public class Font implements java.io.Serializable
|
||||
values.merge(attributes, SECONDARY_MASK);
|
||||
return new Font(values, font.name, font.style,
|
||||
font.createdFont, font.withFallback,
|
||||
font.font2DHandle, false, null);
|
||||
font.font2DHandle, false, new TreeMap<String, Integer>());
|
||||
}
|
||||
|
||||
return font;
|
||||
@@ -1664,7 +1648,17 @@ public class Font implements java.io.Serializable
|
||||
* @since 1.6
|
||||
*/
|
||||
public boolean hasLayoutAttributes() {
|
||||
return hasLayoutAttributes;
|
||||
return anyEnabledFeatures() || hasLayoutAttributes;
|
||||
}
|
||||
|
||||
private static TreeMap<String, Integer> getFeatures(Font font) {
|
||||
TreeMap<String, Integer> res = new TreeMap<>();
|
||||
res.putAll(font.features);
|
||||
res.put(KERN_FEATURE, font.getAttributeValues().getKerning());
|
||||
res.put(LIGA_FEATURE, font.getAttributeValues().getLigatures());
|
||||
res.put(CALT_FEATURE, font.getAttributeValues().getLigatures());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1872,7 +1866,7 @@ public class Font implements java.io.Serializable
|
||||
*/
|
||||
public int hashCode() {
|
||||
if (hash == 0) {
|
||||
hash = name.hashCode() ^ style ^ size ^ Arrays.hashCode(featureArray);
|
||||
hash = name.hashCode() ^ style ^ size ^ features.hashCode();
|
||||
/* It is possible many fonts differ only in transform.
|
||||
* So include the transform in the hash calculation.
|
||||
* nonIdentityTx is set whenever there is a transform in
|
||||
@@ -1911,7 +1905,7 @@ public class Font implements java.io.Serializable
|
||||
pointSize == font.pointSize &&
|
||||
withFallback == font.withFallback &&
|
||||
name.equals(font.name) &&
|
||||
Arrays.equals(featureArray, font.featureArray)) {
|
||||
features.equals(font.features)) {
|
||||
|
||||
/* 'values' is usually initialized lazily, except when
|
||||
* the font is constructed from a Map, or derived using
|
||||
@@ -2017,11 +2011,6 @@ public class Font implements java.io.Serializable
|
||||
pointSize = (float)size;
|
||||
}
|
||||
|
||||
if (features != null) {
|
||||
featureArray = features.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).toArray(String[]::new);
|
||||
features = null;
|
||||
}
|
||||
|
||||
// Handle fRequestedAttributes.
|
||||
// in 1.5, we always streamed out the font values plus
|
||||
// TRANSFORM, SUPERSCRIPT, and WIDTH, regardless of whether the
|
||||
@@ -2040,14 +2029,17 @@ public class Font implements java.io.Serializable
|
||||
}
|
||||
values = getAttributeValues().merge(extras);
|
||||
this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK);
|
||||
this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
|
||||
this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
|
||||
} catch (Throwable t) {
|
||||
throw new IOException(t);
|
||||
} finally {
|
||||
fRequestedAttributes = null; // don't need it any more
|
||||
}
|
||||
}
|
||||
this.hasLayoutAttributes |= this.featureArray != null;
|
||||
|
||||
if (features == null) {
|
||||
features = new TreeMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2151,14 +2143,14 @@ public class Font implements java.io.Serializable
|
||||
public Font deriveFont(int style, float size){
|
||||
if (values == null) {
|
||||
return new Font(name, style, size, createdFont, withFallback,
|
||||
font2DHandle, false, featureArray);
|
||||
font2DHandle, false, features);
|
||||
}
|
||||
AttributeValues newValues = getAttributeValues().clone();
|
||||
int oldStyle = (this.style != style) ? this.style : -1;
|
||||
applyStyle(style, newValues);
|
||||
newValues.setSize(size);
|
||||
return new Font(newValues, null, oldStyle, createdFont, withFallback,
|
||||
font2DHandle, false, featureArray);
|
||||
font2DHandle, false, features);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2178,7 +2170,7 @@ public class Font implements java.io.Serializable
|
||||
applyStyle(style, newValues);
|
||||
applyTransform(trans, newValues);
|
||||
return new Font(newValues, null, oldStyle, createdFont, withFallback,
|
||||
font2DHandle, false, featureArray);
|
||||
font2DHandle, false, features);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2191,12 +2183,12 @@ public class Font implements java.io.Serializable
|
||||
public Font deriveFont(float size){
|
||||
if (values == null) {
|
||||
return new Font(name, style, size, createdFont, withFallback,
|
||||
font2DHandle, true, featureArray);
|
||||
font2DHandle, true, features);
|
||||
}
|
||||
AttributeValues newValues = getAttributeValues().clone();
|
||||
newValues.setSize(size);
|
||||
return new Font(newValues, null, -1, createdFont, withFallback,
|
||||
font2DHandle, true, featureArray);
|
||||
font2DHandle, true, features);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2213,7 +2205,7 @@ public class Font implements java.io.Serializable
|
||||
AttributeValues newValues = getAttributeValues().clone();
|
||||
applyTransform(trans, newValues);
|
||||
return new Font(newValues, null, -1, createdFont, withFallback,
|
||||
font2DHandle, true, featureArray);
|
||||
font2DHandle, true, features);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2226,13 +2218,13 @@ public class Font implements java.io.Serializable
|
||||
public Font deriveFont(int style){
|
||||
if (values == null) {
|
||||
return new Font(name, style, size, createdFont, withFallback,
|
||||
font2DHandle, false, featureArray);
|
||||
font2DHandle, false, features);
|
||||
}
|
||||
AttributeValues newValues = getAttributeValues().clone();
|
||||
int oldStyle = (this.style != style) ? this.style : -1;
|
||||
applyStyle(style, newValues);
|
||||
return new Font(newValues, null, oldStyle, createdFont, withFallback,
|
||||
font2DHandle, false, featureArray);
|
||||
font2DHandle, false, features);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2270,62 +2262,15 @@ public class Font implements java.io.Serializable
|
||||
}
|
||||
}
|
||||
return new Font(newValues, name, style, createdFont, withFallback,
|
||||
font2DHandle, keepFont2DHandle, featureArray);
|
||||
font2DHandle, keepFont2DHandle, features);
|
||||
}
|
||||
|
||||
@JBRApi.Provided("FontExtensions.Features")
|
||||
@Deprecated(forRemoval = true)
|
||||
private interface Features {
|
||||
TreeMap<String, Integer> getAsTreeMap();
|
||||
}
|
||||
|
||||
@JBRApi.Provides("FontExtensions#deriveFontWithFeatures")
|
||||
@Deprecated(forRemoval = true)
|
||||
private static Font deriveFont(Font font, Features features) {
|
||||
TreeMap<String, Integer> map = features.getAsTreeMap();
|
||||
String[] array = new String[map.size()];
|
||||
int i = 0;
|
||||
for (Map.Entry<String, Integer> e : map.entrySet()) {
|
||||
validateFeature(array[i++] = e.getKey() + "=" + e.getValue());
|
||||
}
|
||||
return new Font(font, array);
|
||||
}
|
||||
|
||||
private static void validateFeature(String f) {
|
||||
int len = f.length();
|
||||
invalid:if ((len == 4 || len > 5) &&
|
||||
Character.isLetterOrDigit(f.charAt(0)) &&
|
||||
Character.isLetterOrDigit(f.charAt(1)) &&
|
||||
Character.isLetterOrDigit(f.charAt(2)) &&
|
||||
Character.isLetterOrDigit(f.charAt(3))) {
|
||||
if (len > 5 && f.charAt(4) == '=') {
|
||||
for (int i = 5; i < len; i++) {
|
||||
if (!Character.isDigit(f.charAt(i))) break invalid;
|
||||
}
|
||||
if (f.startsWith("kern")) throw new IllegalArgumentException(
|
||||
"\"kern\" feature is not allowed here, use java.awt.font.TextAttribute.KERNING instead");
|
||||
if (f.startsWith("liga") || f.startsWith("calt")) throw new IllegalArgumentException(
|
||||
"\"liga\" and \"calt\" features are not allowed here, use java.awt.font.TextAttribute.LIGATURES instead");
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid feature: \"" + f + "\", allowed syntax: \"kern\", or \"aalt=2\"");
|
||||
}
|
||||
|
||||
@JBRApi.Provides("FontExtensions#deriveFontWithFeatures")
|
||||
private static Font deriveFont(Font font, String... features) {
|
||||
if (features == null || features.length == 0) return new Font(font, null);
|
||||
for (String f : features) validateFeature(f);
|
||||
return new Font(font, Arrays.copyOf(features, features.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of OpenType's features enabled on the current Font.
|
||||
* @return array of enabled OpenType's features
|
||||
*/
|
||||
@JBRApi.Provides("FontExtensions")
|
||||
private static String[] getEnabledFeatures(Font font) {
|
||||
return font.featureArray == null ? new String[0] : Arrays.copyOf(font.featureArray, font.featureArray.length);
|
||||
return new Font(font, features.getAsTreeMap());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2779,12 +2724,20 @@ public class Font implements java.io.Serializable
|
||||
return metrics.charsBounds(chars, beginIndex, limit - beginIndex);
|
||||
}
|
||||
|
||||
private static boolean isComplexRendering(Font font) {
|
||||
return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 ||
|
||||
font.values.getBaselineTransform() != null)) || font.anyEnabledFeatures();
|
||||
}
|
||||
|
||||
private static boolean isKerning(Font font) {
|
||||
return font.values != null && (font.values.getKerning() != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of OpenType's features supported by current Font.
|
||||
* Implementation of such logic goes to HarfBuzz library.
|
||||
* @return set of available OpenType's features
|
||||
* @return list of OpenType's features concatenated to String
|
||||
*/
|
||||
@JBRApi.Provides("FontExtensions")
|
||||
private static Set<String> getAvailableFeatures(Font font) {
|
||||
return SunLayoutEngine.getAvailableFeatures(FontUtilities.getFont2D(font));
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.security.PrivilegedAction;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import sun.awt.PlatformGraphicsInfo;
|
||||
import sun.font.FontManager;
|
||||
import sun.font.FontManagerFactory;
|
||||
@@ -117,12 +116,12 @@ public abstract class GraphicsEnvironment {
|
||||
return LocalGE.INSTANCE;
|
||||
}
|
||||
|
||||
@JBRApi.Provides("ProjectorUtils")
|
||||
// JBR API internals
|
||||
private static void setLocalGraphicsEnvironmentProvider(Supplier<GraphicsEnvironment> geProvider) {
|
||||
graphicsEnvironmentProvider = geProvider;
|
||||
}
|
||||
|
||||
@JBRApi.Provides("ProjectorUtils#overrideGraphicsEnvironment")
|
||||
// JBR API internals
|
||||
private static void overrideLocalGraphicsEnvironment(GraphicsEnvironment overriddenGE) {
|
||||
setLocalGraphicsEnvironmentProvider(() -> overriddenGE);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import java.io.OptionalDataException;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Native;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.security.AccessController;
|
||||
@@ -54,6 +55,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
@@ -69,7 +71,7 @@ import javax.accessibility.AccessibleRole;
|
||||
import javax.accessibility.AccessibleState;
|
||||
import javax.accessibility.AccessibleStateSet;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import com.jetbrains.internal.JBRApi;
|
||||
import sun.awt.AWTAccessor;
|
||||
import sun.awt.AWTPermissions;
|
||||
import sun.awt.AppContext;
|
||||
@@ -4021,9 +4023,7 @@ public class Window extends Container implements Accessible {
|
||||
|
||||
// ************************** Custom title bar support *******************************
|
||||
|
||||
@JBRApi.Service
|
||||
@JBRApi.Provides("WindowDecorations")
|
||||
private static final class WindowDecorations {
|
||||
private static class WindowDecorations {
|
||||
WindowDecorations() { CustomTitleBar.assertSupported(); }
|
||||
void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar) { ((Window) frame).setCustomTitleBar(customTitleBar); }
|
||||
void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar) { ((Window) dialog).setCustomTitleBar(customTitleBar); }
|
||||
@@ -4031,8 +4031,7 @@ public class Window extends Container implements Accessible {
|
||||
}
|
||||
|
||||
|
||||
@JBRApi.Provides("WindowDecorations.CustomTitleBar")
|
||||
private static final class CustomTitleBar implements Serializable {
|
||||
private static class CustomTitleBar implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -2330620200902241173L;
|
||||
|
||||
@@ -4127,7 +4126,8 @@ public class Window extends Container implements Accessible {
|
||||
}
|
||||
|
||||
private interface CustomTitleBarPeer {
|
||||
CustomTitleBarPeer INSTANCE = JBRApi.internalService();
|
||||
CustomTitleBarPeer INSTANCE = (CustomTitleBarPeer) JBRApi.internalServiceBuilder(MethodHandles.lookup())
|
||||
.withStatic("update", "updateCustomTitleBar", "sun.awt.windows.WFramePeer", "sun.lwawt.macosx.CPlatformWindow").build();
|
||||
void update(ComponentPeer peer);
|
||||
}
|
||||
|
||||
@@ -4163,6 +4163,28 @@ public class Window extends Container implements Accessible {
|
||||
Window updateCustomTitleBarHitTest(boolean allowNativeActions) {
|
||||
if (customTitleBar == null) return null;
|
||||
pendingCustomTitleBarHitTest = allowNativeActions ? CustomTitleBar.HIT_TITLEBAR : CustomTitleBar.HIT_CLIENT;
|
||||
if (customDecorHitTestSpots != null) { // Compatibility bridge, to be removed with old API
|
||||
Point p = getMousePosition(true);
|
||||
if (p == null) return this;
|
||||
// Perform old-style hit test
|
||||
int result = CustomWindowDecoration.NO_HIT_SPOT;
|
||||
for (var spot : customDecorHitTestSpots) {
|
||||
if (spot.getKey().contains(p.x, p.y)) {
|
||||
result = spot.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Convert old hit test value to new one
|
||||
pendingCustomTitleBarHitTest = switch (result) {
|
||||
case CustomWindowDecoration.MINIMIZE_BUTTON -> CustomTitleBar.HIT_MINIMIZE_BUTTON;
|
||||
case CustomWindowDecoration.MAXIMIZE_BUTTON -> CustomTitleBar.HIT_MAXIMIZE_BUTTON;
|
||||
case CustomWindowDecoration.CLOSE_BUTTON -> CustomTitleBar.HIT_CLOSE_BUTTON;
|
||||
case CustomWindowDecoration.NO_HIT_SPOT, CustomWindowDecoration.DRAGGABLE_AREA -> CustomTitleBar.HIT_TITLEBAR;
|
||||
default -> CustomTitleBar.HIT_CLIENT;
|
||||
};
|
||||
// Overwrite hit test value
|
||||
applyCustomTitleBarHitTest();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -4174,6 +4196,130 @@ public class Window extends Container implements Accessible {
|
||||
customTitleBarHitTest = pendingCustomTitleBarHitTest;
|
||||
}
|
||||
|
||||
// *** Following custom decorations code is kept for backward compatibility and will be removed soon. ***
|
||||
|
||||
@Deprecated
|
||||
private transient volatile boolean hasCustomDecoration;
|
||||
@Deprecated
|
||||
private transient volatile List<Map.Entry<Shape, Integer>> customDecorHitTestSpots;
|
||||
@Deprecated
|
||||
private transient volatile int customDecorTitleBarHeight = -1; // 0 can be a legal value when no title bar is expected
|
||||
@Deprecated
|
||||
private static class CustomWindowDecoration {
|
||||
|
||||
CustomWindowDecoration() { CustomTitleBar.assertSupported(); }
|
||||
|
||||
@Native public static final int
|
||||
NO_HIT_SPOT = 0,
|
||||
OTHER_HIT_SPOT = 1,
|
||||
MINIMIZE_BUTTON = 2,
|
||||
MAXIMIZE_BUTTON = 3,
|
||||
CLOSE_BUTTON = 4,
|
||||
MENU_BAR = 5,
|
||||
DRAGGABLE_AREA = 6;
|
||||
|
||||
void setCustomDecorationEnabled(Window window, boolean enabled) {
|
||||
window.hasCustomDecoration = enabled;
|
||||
setTitleBar(window, enabled ? Math.max(window.customDecorTitleBarHeight, 0.01f) : 0);
|
||||
}
|
||||
boolean isCustomDecorationEnabled(Window window) {
|
||||
return window.hasCustomDecoration;
|
||||
}
|
||||
|
||||
void setCustomDecorationHitTestSpots(Window window, List<Map.Entry<Shape, Integer>> spots) {
|
||||
window.customDecorHitTestSpots = List.copyOf(spots);
|
||||
}
|
||||
List<Map.Entry<Shape, Integer>> getCustomDecorationHitTestSpots(Window window) {
|
||||
return window.customDecorHitTestSpots;
|
||||
}
|
||||
|
||||
void setCustomDecorationTitleBarHeight(Window window, int height) {
|
||||
window.customDecorTitleBarHeight = height;
|
||||
setTitleBar(window, window.hasCustomDecoration ? Math.max(height, 0.01f) : 0);
|
||||
}
|
||||
int getCustomDecorationTitleBarHeight(Window window) {
|
||||
return window.customDecorTitleBarHeight;
|
||||
}
|
||||
|
||||
// Bridge from old to new API
|
||||
private static void setTitleBar(Window window, float height) {
|
||||
if (height <= 0.0f) window.setCustomTitleBar(null);
|
||||
else {
|
||||
CustomTitleBar t = new CustomTitleBar();
|
||||
// Old API accepts title bar height with insets, subtract it for new API.
|
||||
// We use bottom insets here because top insets may change when toggling custom title bar, they are usually equal.
|
||||
if (window instanceof Frame f && (f.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0) {
|
||||
height -= window.getInsets().bottom;
|
||||
}
|
||||
t.setHeight(Math.max(height, 0.01f));
|
||||
// In old API versions there were no control buttons on Windows.
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win")) t.putProperty("controls.visible", false);
|
||||
window.setCustomTitleBar(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface WindowMovePeer {
|
||||
void startMovingWindowTogetherWithMouse(Window window, int mouseButton);
|
||||
}
|
||||
|
||||
private interface WindowMovePeerX11 extends WindowMovePeer {
|
||||
WindowMovePeerX11 INSTANCE = (WindowMovePeerX11) JBRApi.internalServiceBuilder(MethodHandles.lookup())
|
||||
.withStatic("startMovingWindowTogetherWithMouse",
|
||||
"startMovingWindowTogetherWithMouse",
|
||||
"sun.awt.X11.XWindowPeer")
|
||||
.build();
|
||||
}
|
||||
|
||||
private interface WindowMovePeerWayland extends WindowMovePeer {
|
||||
WindowMovePeerWayland INSTANCE = (WindowMovePeerWayland) JBRApi.internalServiceBuilder(MethodHandles.lookup())
|
||||
.withStatic("startMovingWindowTogetherWithMouse",
|
||||
"startMovingWindowTogetherWithMouse",
|
||||
"sun.awt.wl.WLComponentPeer")
|
||||
.build();
|
||||
}
|
||||
|
||||
private static class WindowMoveService {
|
||||
WindowMovePeer windowMovePeer;
|
||||
|
||||
WindowMoveService() {
|
||||
var toolkit = Toolkit.getDefaultToolkit();
|
||||
var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
if (toolkit == null || ge == null) {
|
||||
throw new JBRApi.ServiceNotAvailableException("Supported only with a Toolkit present");
|
||||
}
|
||||
|
||||
boolean isWayland = objectIsInstanceOf(toolkit, "sun.awt.wl.WLToolkit");
|
||||
if (isWayland) {
|
||||
if (!objectIsInstanceOf(ge,"sun.awt.wl.WLGraphicsEnvironment")) {
|
||||
throw new JBRApi.ServiceNotAvailableException("On Wayland, supported only with WLGraphicsEnvironment");
|
||||
}
|
||||
} else {
|
||||
if (!objectIsInstanceOf(toolkit, "sun.awt.X11.XToolkit")
|
||||
|| !objectIsInstanceOf(ge, "sun.awt.X11GraphicsEnvironment")) {
|
||||
throw new JBRApi.ServiceNotAvailableException("Supported only with XToolkit and X11GraphicsEnvironment");
|
||||
}
|
||||
}
|
||||
|
||||
if (isWayland) {
|
||||
windowMovePeer = WindowMovePeerWayland.INSTANCE;
|
||||
} else {
|
||||
// This will throw if the service is not supported by the underlying WM
|
||||
windowMovePeer = WindowMovePeerX11.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
boolean objectIsInstanceOf(Object o, String className) {
|
||||
Objects.requireNonNull(o);
|
||||
return o.getClass().getName().equals(className);
|
||||
}
|
||||
|
||||
void startMovingTogetherWithMouse(Window window, int mouseButton) {
|
||||
Objects.requireNonNull(window);
|
||||
windowMovePeer.startMovingWindowTogetherWithMouse(window, mouseButton);
|
||||
}
|
||||
}
|
||||
|
||||
// ************************** JBR stuff *******************************
|
||||
|
||||
private volatile boolean ignoreMouseEvents;
|
||||
|
||||
@@ -27,7 +27,6 @@ package sun.font;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.peer.FontPeer;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public abstract class FontAccess {
|
||||
|
||||
@@ -48,7 +47,4 @@ public abstract class FontAccess {
|
||||
public abstract void setWithFallback(Font f);
|
||||
public abstract boolean isCreatedFont(Font f);
|
||||
public abstract FontPeer getFontPeer(Font f);
|
||||
public abstract String[] getFeatures(Font font);
|
||||
public abstract boolean isComplexRendering(Font font);
|
||||
public abstract boolean isKerning(Font font);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import com.jetbrains.desktop.FontExtensions;
|
||||
import sun.java2d.Disposer;
|
||||
import sun.java2d.DisposerRecord;
|
||||
|
||||
@@ -481,7 +481,7 @@ public final class FontDesignMetrics extends FontMetrics {
|
||||
assert (data instanceof String || data instanceof char[]);
|
||||
float width = 0;
|
||||
|
||||
if (overrider == null && FontAccess.getFontAccess().isComplexRendering(font) && len > 0) {
|
||||
if (overrider == null && FontExtensions.isComplexRendering(font) && len > 0) {
|
||||
return textLayoutBounds(data, off, len);
|
||||
}
|
||||
|
||||
@@ -492,7 +492,7 @@ public final class FontDesignMetrics extends FontMetrics {
|
||||
return new Rectangle2D.Float(0f, -ascent, width, height);
|
||||
}
|
||||
|
||||
boolean isKerning = FontAccess.getFontAccess().isKerning(font);
|
||||
boolean isKerning = FontExtensions.isKerning(font);
|
||||
float consecutiveDoubleCharacterWidth = 0f;
|
||||
char prev = 0;
|
||||
for (int i = off; i < off + len; i++) {
|
||||
@@ -608,9 +608,7 @@ public final class FontDesignMetrics extends FontMetrics {
|
||||
return height;
|
||||
}
|
||||
|
||||
@JBRApi.Service
|
||||
@JBRApi.Provides("FontMetricsAccessor")
|
||||
private static final class Accessor {
|
||||
private static class Accessor { // used by JBR API
|
||||
// Keeping metrics instances here prevents them from being garbage collected
|
||||
// and being re-created by FontDesignMetrics.getMetrics method
|
||||
private final Set<FontDesignMetrics> PINNED_METRICS = new HashSet<>();
|
||||
@@ -649,8 +647,7 @@ public final class FontDesignMetrics extends FontMetrics {
|
||||
}
|
||||
}
|
||||
|
||||
@JBRApi.Provided("FontMetricsAccessor.Overrider")
|
||||
private interface Overrider {
|
||||
private interface Overrider { // used by JBR API
|
||||
float charWidth(int codePoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import javax.swing.plaf.FontUIResource;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import sun.awt.OSInfo;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
@@ -137,8 +136,7 @@ public final class FontUtilities {
|
||||
});
|
||||
}
|
||||
|
||||
@JBRApi.Provides("FontExtensions")
|
||||
private static Dimension getSubpixelResolution() {
|
||||
static Dimension getSubpixelResolution() {
|
||||
return subpixelResolution;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
|
||||
package sun.font;
|
||||
|
||||
import com.jetbrains.desktop.FontExtensions;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.awt.Font;
|
||||
import java.awt.font.FontRenderContext;
|
||||
@@ -174,7 +176,7 @@ public final class GlyphLayout {
|
||||
* leave pt and the gvdata unchanged.
|
||||
*/
|
||||
public void layout(FontStrikeDesc sd, float[] mat, float ptSize, int gmask, int baseIndex, TextRecord text,
|
||||
boolean ltrDirection, String[] features, Point2D.Float pt, GVData data);
|
||||
boolean ltrDirection, Map<String, Integer> features, Point2D.Float pt, GVData data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,7 +452,7 @@ public final class GlyphLayout {
|
||||
EngineRecord er = _erecords.get(ix);
|
||||
for (;;) {
|
||||
try {
|
||||
er.layout(ltrDirection, FontAccess.getFontAccess().getFeatures(font));
|
||||
er.layout(ltrDirection, FontExtensions.getFeatures(font));
|
||||
break;
|
||||
}
|
||||
catch (IndexOutOfBoundsException e) {
|
||||
@@ -656,7 +658,7 @@ public final class GlyphLayout {
|
||||
this.engine = _lef.getEngine(key); // flags?
|
||||
}
|
||||
|
||||
void layout(boolean ltrDirection, String[] features) {
|
||||
void layout(boolean ltrDirection, Map<String, Integer> features) {
|
||||
_textRecord.start = start;
|
||||
_textRecord.limit = limit;
|
||||
engine.layout(_sd, _mat, ptSize, gmask, start - _offset, _textRecord,
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
package sun.font;
|
||||
|
||||
import com.jetbrains.desktop.FontExtensions;
|
||||
import sun.font.GlyphLayout.*;
|
||||
import sun.java2d.Disposer;
|
||||
import sun.java2d.DisposerRecord;
|
||||
@@ -37,6 +38,7 @@ import sun.java2d.DisposerRecord;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.WeakHashMap;
|
||||
@@ -172,7 +174,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
|
||||
}
|
||||
|
||||
public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask,
|
||||
int baseIndex, TextRecord tr, boolean ltrDirection, String[] features,
|
||||
int baseIndex, TextRecord tr, boolean ltrDirection, Map<String, Integer> features,
|
||||
Point2D.Float pt, GVData data) {
|
||||
Font2D font = key.font();
|
||||
FontStrike strike = font.getStrike(desc);
|
||||
@@ -181,7 +183,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
|
||||
shape(font, strike, ptSize, mat, pFace,
|
||||
tr.text, data, key.script(),
|
||||
tr.start, tr.limit, baseIndex, pt,
|
||||
ltrDirection, features, gmask);
|
||||
ltrDirection, FontExtensions.featuresToStringArray(features), gmask);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ import sun.font.FontManagerFactory;
|
||||
import sun.font.FontManagerForSGE;
|
||||
import sun.font.FontUtilities;
|
||||
import sun.java2d.pipe.Region;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
|
||||
/**
|
||||
@@ -89,21 +88,6 @@ public abstract class SunGraphicsEnvironment extends GraphicsEnvironment
|
||||
debugScale = uiScaleEnabled ? getScaleFactor("sun.java2d.uiScale") : -1;
|
||||
}
|
||||
|
||||
protected enum LogDisplay {
|
||||
ADDED,
|
||||
REMOVED,
|
||||
CHANGED;
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public static final boolean ENABLED = AccessController.doPrivileged(new GetBooleanAction("sun.java2d.logDisplays"));
|
||||
|
||||
public void log(int id, Object bounds, double scale) {
|
||||
if (!ENABLED) return;
|
||||
System.out.println("DISPLAY " + this + ": #" + id +
|
||||
", " + bounds + ", scale=" + scale);
|
||||
}
|
||||
}
|
||||
|
||||
protected GraphicsDevice[] screens;
|
||||
|
||||
private static boolean isWindows_8_1_orUpper() {
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
package sun.swing;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import jdk.internal.misc.InnocuousThread;
|
||||
import sun.awt.AWTThreading;
|
||||
|
||||
@@ -73,8 +72,7 @@ public class AccessibleAnnouncer {
|
||||
* @param str string for announcing
|
||||
* @param priority priority for announcing
|
||||
*/
|
||||
@JBRApi.Provides("AccessibleAnnouncer")
|
||||
public static void announce(Accessible a, final String str, final int priority) {
|
||||
public static void announce(Accessible a, final String str, final int priority) throws Exception {
|
||||
if (str == null ||
|
||||
priority != ANNOUNCE_WITHOUT_INTERRUPTING_CURRENT_OUTPUT &&
|
||||
priority != ANNOUNCE_WITH_INTERRUPTING_CURRENT_OUTPUT) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(binding = 0) uniform sampler2D texSampler;
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = texture(texSampler, fragTexCoord);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 inPosition;
|
||||
layout(location = 1) in vec2 texPosition;
|
||||
layout(location = 0) out vec2 fragTexCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(inPosition, 0.0, 1.0);
|
||||
fragTexCoord = texPosition;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in flat vec4 fragColor;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = fragColor;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 inPosition;
|
||||
layout(location = 1) in vec4 inColor;
|
||||
layout(location = 0) out flat vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(inPosition, 0.0, 1.0);
|
||||
fragColor = inColor;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 fragColor;
|
||||
} pushConstants;
|
||||
|
||||
const vec2 positions[4] = vec2[4](
|
||||
vec2(-1.0, -1.0),
|
||||
vec2( 1.0, -1.0),
|
||||
vec2(-1.0, 1.0),
|
||||
vec2( 1.0, 1.0)
|
||||
);
|
||||
|
||||
layout(location = 0) out flat vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
fragColor = pushConstants.fragColor;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in flat vec4 fragColor;
|
||||
layout(location = 0) in vec4 inColor;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = fragColor;
|
||||
outColor = inColor;
|
||||
}
|
||||
21
src/java.desktop/share/glsl/vulkan/test.vert
Normal file
21
src/java.desktop/share/glsl/vulkan/test.vert
Normal file
@@ -0,0 +1,21 @@
|
||||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec2 invViewport2; // 2.0/viewport
|
||||
} push;
|
||||
|
||||
vec4 colors[3] = vec4[](
|
||||
vec4(1,0,0,1),
|
||||
vec4(0,1,0,1),
|
||||
vec4(0,0,1,1)
|
||||
);
|
||||
|
||||
layout(location = 0) in vec2 inPosition;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = colors[gl_VertexIndex % 3];
|
||||
gl_Position = vec4(inPosition * push.invViewport2 - 1.0, 0.0, 1.0);
|
||||
gl_PointSize = 1.0f;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#include <memory.h>
|
||||
#include "CArrayUtil.h"
|
||||
|
||||
#define MIN(a,b) (((a)<(b))?(a):(b))
|
||||
|
||||
void* CARR_array_alloc(size_t elem_size, size_t capacity) {
|
||||
CARR_array_t *pvec = malloc(elem_size * capacity + offsetof(CARR_array_t, data));
|
||||
if (pvec == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
pvec->elem_size = elem_size;
|
||||
pvec->size = 0;
|
||||
pvec->capacity = capacity;
|
||||
return pvec->data;
|
||||
}
|
||||
|
||||
void* CARR_array_realloc(CARR_array_t* vec, size_t new_capacity) {
|
||||
if (vec->capacity == new_capacity) {
|
||||
return vec->data;
|
||||
}
|
||||
CARR_array_t* new_vec =
|
||||
(CARR_array_t*)((char*)CARR_array_alloc(vec->elem_size, new_capacity) - offsetof(CARR_array_t, data));
|
||||
if (new_vec == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
new_vec->capacity = new_capacity;
|
||||
new_vec->size = MIN(vec->size, new_capacity);
|
||||
new_vec->elem_size = vec->elem_size;
|
||||
memcpy(new_vec->data, vec->data, new_vec->size*new_vec->elem_size);
|
||||
free(vec);
|
||||
return new_vec->data;
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
#ifndef C_ARRAY_UTIL_H
|
||||
#define C_ARRAY_UTIL_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define ARRAY_CAPACITY_MULT 2
|
||||
typedef struct {
|
||||
size_t elem_size;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
char data[];
|
||||
} CARR_array_t;
|
||||
|
||||
void* CARR_array_alloc(size_t elem_size, size_t capacity);
|
||||
void* CARR_array_realloc(CARR_array_t* vec, size_t new_capacity);
|
||||
|
||||
/**
|
||||
* Allocate array
|
||||
* @param T type of elements
|
||||
* @param SIZE size of the array
|
||||
*/
|
||||
#define ARRAY_ALLOC(T, SIZE) (T*)CARR_array_alloc(sizeof(T), SIZE)
|
||||
|
||||
|
||||
#define ARRAY_T(P) (CARR_array_t *)((char*)P - offsetof(CARR_array_t, data))
|
||||
|
||||
/**
|
||||
* @param P pointer to the first data element of the array
|
||||
* @return size of the array
|
||||
*/
|
||||
#define ARRAY_SIZE(P) (ARRAY_T(P))->size
|
||||
|
||||
/**
|
||||
* @param P pointer to the first data element of the array
|
||||
* @return capacity of the array
|
||||
*/
|
||||
#define ARRAY_CAPACITY(P) (ARRAY_T(P))->capacity
|
||||
|
||||
/**
|
||||
* @param P pointer to the first data element of the array
|
||||
* @return last element in the array
|
||||
*/
|
||||
#define ARRAY_LAST(P) (P[(ARRAY_T(P))->size - 1])
|
||||
|
||||
/**
|
||||
* Deallocate the vector
|
||||
* @param P pointer to the first data element of the array
|
||||
*/
|
||||
#define ARRAY_FREE(P) free(ARRAY_T(P))
|
||||
|
||||
/**
|
||||
* Apply function to the vector
|
||||
* @param P pointer to the first data element of the array
|
||||
* @param F function to apply
|
||||
*/
|
||||
#define ARRAY_APPLY(P, F) do { \
|
||||
for (uint32_t _i = 0; _i < ARRAY_SIZE(P); _i++) F(&(P[_i])); \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Shrink capacity of the array to its size
|
||||
* @param PP pointer to the pointer to the first data element of the array
|
||||
*/
|
||||
#define ARRAY_SHRINK_TO_FIT(PP) do { \
|
||||
*PP = CARR_array_realloc(ARRAY_T(*PP), ARRAY_SIZE(*PP)); \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Add element to the end of the array
|
||||
* @param PP pointer to the pointer to the first data element of the array
|
||||
*/
|
||||
#define ARRAY_PUSH_BACK(PP, D) do { \
|
||||
if (ARRAY_SIZE(*PP) >= ARRAY_CAPACITY(*PP)) { \
|
||||
*PP = CARR_array_realloc(ARRAY_T(*PP), ARRAY_SIZE(*PP)*ARRAY_CAPACITY_MULT);\
|
||||
} \
|
||||
*(*PP + ARRAY_SIZE(*PP)) = (D); \
|
||||
ARRAY_SIZE(*PP)++; \
|
||||
} while(0)
|
||||
|
||||
#define SARRAY_COUNT_OF(STATIC_ARRAY) (sizeof(STATIC_ARRAY)/sizeof(STATIC_ARRAY[0]))
|
||||
|
||||
#endif // CARRAYUTILS_H
|
||||
@@ -1,812 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. 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 <malloc.h>
|
||||
#include <Trace.h>
|
||||
#include "jvm_md.h"
|
||||
#include "VKBase.h"
|
||||
#include "VKVertex.h"
|
||||
#include "CArrayUtil.h"
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
|
||||
#define VULKAN_DLL JNI_LIB_NAME("vulkan")
|
||||
#define VULKAN_1_DLL VERSIONED_JNI_LIB_NAME("vulkan", "1")
|
||||
static const uint32_t REQUIRED_VULKAN_VERSION = VK_MAKE_API_VERSION(0, 1, 2, 0);
|
||||
|
||||
|
||||
#define MAX_ENABLED_LAYERS 5
|
||||
#define MAX_ENABLED_EXTENSIONS 5
|
||||
#define VALIDATION_LAYER_NAME "VK_LAYER_KHRONOS_validation"
|
||||
#define COUNT_OF(x) (sizeof(x)/sizeof(x[0]))
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
extern struct wl_display *wl_display;
|
||||
#endif
|
||||
|
||||
static jboolean verbose;
|
||||
static jint requestedDeviceNumber = -1;
|
||||
static VKGraphicsEnvironment* geInstance = NULL;
|
||||
static void* pVulkanLib = NULL;
|
||||
#define DEBUG
|
||||
#define INCLUDE_BYTECODE
|
||||
#define SHADER_ENTRY(NAME, TYPE) static uint32_t NAME ## _ ## TYPE ## _data[] = {
|
||||
#define BYTECODE_END };
|
||||
#include "vulkan/shader_list.h"
|
||||
#undef INCLUDE_BYTECODE
|
||||
#undef SHADER_ENTRY
|
||||
#undef BYTECODE_END
|
||||
|
||||
#define DEF_VK_PROC_RET_IF_ERR(INST, NAME, RETVAL) PFN_ ## NAME NAME = (PFN_ ## NAME ) vulkanLibProc(INST, #NAME); \
|
||||
if (NAME == NULL) { \
|
||||
J2dRlsTraceLn1(J2D_TRACE_ERROR, "Required api is not supported. %s is missing.", #NAME)\
|
||||
vulkanLibClose(); \
|
||||
return RETVAL; \
|
||||
}
|
||||
|
||||
|
||||
#define VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(SNAME, NAME) do { \
|
||||
SNAME->NAME = (PFN_ ## NAME ) vulkanLibProc( SNAME->vkInstance, #NAME); \
|
||||
if (SNAME->NAME == NULL) { \
|
||||
J2dRlsTraceLn1(J2D_TRACE_ERROR, "Required api is not supported. %s is missing.", #NAME)\
|
||||
vulkanLibClose(); \
|
||||
return NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void vulkanLibClose() {
|
||||
if (pVulkanLib != NULL) {
|
||||
if (geInstance != NULL) {
|
||||
if (geInstance->layers != NULL) {
|
||||
free(geInstance->layers);
|
||||
}
|
||||
if (geInstance->extensions != NULL) {
|
||||
free(geInstance->extensions);
|
||||
}
|
||||
ARRAY_FREE(geInstance->physicalDevices);
|
||||
if (geInstance->devices != NULL) {
|
||||
PFN_vkDestroyDevice vkDestroyDevice = vulkanLibProc(geInstance->vkInstance, "vkDestroyDevice");
|
||||
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(geInstance->devices); i++) {
|
||||
if (geInstance->devices[i].enabledExtensions != NULL) {
|
||||
free(geInstance->devices[i].enabledExtensions);
|
||||
}
|
||||
if (geInstance->devices[i].enabledLayers != NULL) {
|
||||
free(geInstance->devices[i].enabledLayers);
|
||||
}
|
||||
if (geInstance->devices[i].name != NULL) {
|
||||
free(geInstance->devices[i].name);
|
||||
}
|
||||
if (vkDestroyDevice != NULL && geInstance->devices[i].device != NULL) {
|
||||
vkDestroyDevice(geInstance->devices[i].device, NULL);
|
||||
}
|
||||
}
|
||||
free(geInstance->devices);
|
||||
}
|
||||
|
||||
if (geInstance->vkInstance != NULL) {
|
||||
PFN_vkDestroyInstance vkDestroyInstance = vulkanLibProc(geInstance->vkInstance, "vkDestroyInstance");
|
||||
if (vkDestroyInstance != NULL) {
|
||||
vkDestroyInstance(geInstance->vkInstance, NULL);
|
||||
}
|
||||
}
|
||||
free(geInstance);
|
||||
geInstance = NULL;
|
||||
}
|
||||
dlclose(pVulkanLib);
|
||||
pVulkanLib = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void* vulkanLibProc(VkInstance vkInstance, char* procName) {
|
||||
static PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
|
||||
if (pVulkanLib == NULL) {
|
||||
pVulkanLib = dlopen(VULKAN_DLL, RTLD_NOW);
|
||||
if (pVulkanLib == NULL) {
|
||||
pVulkanLib = dlopen(VULKAN_1_DLL, RTLD_NOW);
|
||||
}
|
||||
if (pVulkanLib == NULL) {
|
||||
J2dRlsTrace1(J2D_TRACE_ERROR, "Failed to load %s\n", VULKAN_DLL)
|
||||
return NULL;
|
||||
}
|
||||
if (!vkGetInstanceProcAddr) {
|
||||
vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) dlsym(pVulkanLib, "vkGetInstanceProcAddr");
|
||||
if (vkGetInstanceProcAddr == NULL) {
|
||||
J2dRlsTrace1(J2D_TRACE_ERROR,
|
||||
"Failed to get proc address of vkGetInstanceProcAddr from %s\n", VULKAN_DLL)
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* vkProc = vkGetInstanceProcAddr(vkInstance, procName);
|
||||
|
||||
if (vkProc == NULL) {
|
||||
J2dRlsTrace1(J2D_TRACE_ERROR, "%s is not supported\n", procName)
|
||||
return NULL;
|
||||
}
|
||||
return vkProc;
|
||||
}
|
||||
|
||||
|
||||
jboolean VK_Init(jboolean verb, jint requestedDevice) {
|
||||
verbose = verb;
|
||||
if (VKGE_graphics_environment() == NULL) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (!VK_FindDevices()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (!VK_CreateLogicalDevice(requestedDevice)) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
return VK_CreateLogicalDeviceRenderers();
|
||||
}
|
||||
|
||||
static const char* physicalDeviceTypeString(VkPhysicalDeviceType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
#define STR(r) case VK_PHYSICAL_DEVICE_TYPE_ ##r: return #r
|
||||
STR(OTHER);
|
||||
STR(INTEGRATED_GPU);
|
||||
STR(DISCRETE_GPU);
|
||||
STR(VIRTUAL_GPU);
|
||||
STR(CPU);
|
||||
#undef STR
|
||||
default: return "UNKNOWN_DEVICE_TYPE";
|
||||
}
|
||||
}
|
||||
|
||||
VKGraphicsEnvironment* VKGE_graphics_environment() {
|
||||
if (geInstance == NULL) {
|
||||
|
||||
DEF_VK_PROC_RET_IF_ERR(VK_NULL_HANDLE, vkEnumerateInstanceVersion, NULL);
|
||||
DEF_VK_PROC_RET_IF_ERR(VK_NULL_HANDLE, vkEnumerateInstanceExtensionProperties, NULL);
|
||||
DEF_VK_PROC_RET_IF_ERR(VK_NULL_HANDLE, vkEnumerateInstanceLayerProperties, NULL);
|
||||
DEF_VK_PROC_RET_IF_ERR(VK_NULL_HANDLE, vkCreateInstance, NULL);
|
||||
|
||||
uint32_t apiVersion = 0;
|
||||
|
||||
if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: unable to enumerate Vulkan instance version\n")
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
J2dRlsTrace3(J2D_TRACE_INFO, "Vulkan: Available (%d.%d.%d)\n",
|
||||
VK_API_VERSION_MAJOR(apiVersion),
|
||||
VK_API_VERSION_MINOR(apiVersion),
|
||||
VK_API_VERSION_PATCH(apiVersion))
|
||||
|
||||
if (apiVersion < REQUIRED_VULKAN_VERSION) {
|
||||
J2dRlsTrace3(J2D_TRACE_ERROR, "Vulkan: Unsupported version. Required at least (%d.%d.%d)\n",
|
||||
VK_API_VERSION_MAJOR(REQUIRED_VULKAN_VERSION),
|
||||
VK_API_VERSION_MINOR(REQUIRED_VULKAN_VERSION),
|
||||
VK_API_VERSION_PATCH(REQUIRED_VULKAN_VERSION))
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
geInstance = (VKGraphicsEnvironment*)malloc(sizeof(VKGraphicsEnvironment));
|
||||
if (geInstance == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VKGraphicsEnvironment\n")
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*geInstance = (VKGraphicsEnvironment) {};
|
||||
uint32_t extensionsCount;
|
||||
// Get the number of extensions and layers
|
||||
if (vkEnumerateInstanceExtensionProperties(NULL,
|
||||
&extensionsCount,
|
||||
NULL) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumerateInstanceExtensionProperties fails\n")
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
geInstance->extensions = ARRAY_ALLOC(VkExtensionProperties, extensionsCount);
|
||||
|
||||
if (geInstance->extensions == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkExtensionProperties\n")
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (vkEnumerateInstanceExtensionProperties(NULL, &extensionsCount,
|
||||
geInstance->extensions) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumerateInstanceExtensionProperties fails\n")
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ARRAY_SIZE(geInstance->extensions) = extensionsCount;
|
||||
|
||||
uint32_t layersCount;
|
||||
if (vkEnumerateInstanceLayerProperties(&layersCount, NULL) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumerateInstanceLayerProperties fails\n")
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
geInstance->layers = ARRAY_ALLOC(VkLayerProperties, layersCount);
|
||||
if (geInstance->layers == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkLayerProperties\n")
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (vkEnumerateInstanceLayerProperties(&layersCount,
|
||||
geInstance->layers) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumerateInstanceLayerProperties fails\n")
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ARRAY_SIZE(geInstance->layers) = layersCount;
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported instance layers:\n")
|
||||
for (uint32_t i = 0; i < layersCount; i++) {
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) geInstance->layers[i].layerName)
|
||||
}
|
||||
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported instance extensions:\n")
|
||||
for (uint32_t i = 0; i < extensionsCount; i++) {
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) geInstance->extensions[i].extensionName)
|
||||
}
|
||||
|
||||
pchar* enabledLayers = ARRAY_ALLOC(pchar, MAX_ENABLED_LAYERS);
|
||||
pchar* enabledExtensions = ARRAY_ALLOC(pchar, MAX_ENABLED_EXTENSIONS);
|
||||
void *pNext = NULL;
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
ARRAY_PUSH_BACK(&enabledExtensions, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
||||
#endif
|
||||
ARRAY_PUSH_BACK(&enabledExtensions, VK_KHR_SURFACE_EXTENSION_NAME);
|
||||
|
||||
// Check required layers & extensions.
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(enabledExtensions); i++) {
|
||||
int notFound = 1;
|
||||
for (uint32_t j = 0; j < extensionsCount; j++) {
|
||||
if (strcmp((char *) geInstance->extensions[j].extensionName, enabledExtensions[i]) == 0) {
|
||||
notFound = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (notFound) {
|
||||
J2dRlsTrace1(J2D_TRACE_ERROR, "Vulkan: Required extension %s not found\n", enabledExtensions[i])
|
||||
vulkanLibClose();
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure validation
|
||||
#ifdef DEBUG
|
||||
VkValidationFeatureEnableEXT enables[] = {
|
||||
// VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,
|
||||
// VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT,
|
||||
VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT,
|
||||
// VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT,
|
||||
VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT
|
||||
};
|
||||
|
||||
VkValidationFeaturesEXT features = {};
|
||||
features.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT;
|
||||
features.enabledValidationFeatureCount = COUNT_OF(enables);
|
||||
features.pEnabledValidationFeatures = enables;
|
||||
|
||||
// Includes the validation features into the instance creation process
|
||||
|
||||
int foundDebugLayer = 0;
|
||||
for (uint32_t i = 0; i < layersCount; i++) {
|
||||
if (strcmp((char *) geInstance->layers[i].layerName, VALIDATION_LAYER_NAME) == 0) {
|
||||
foundDebugLayer = 1;
|
||||
break;
|
||||
}
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) geInstance->layers[i].layerName)
|
||||
}
|
||||
int foundDebugExt = 0;
|
||||
for (uint32_t i = 0; i < extensionsCount; i++) {
|
||||
if (strcmp((char *) geInstance->extensions[i].extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) {
|
||||
foundDebugExt = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDebugLayer && foundDebugExt) {
|
||||
ARRAY_PUSH_BACK(&enabledLayers, VALIDATION_LAYER_NAME);
|
||||
ARRAY_PUSH_BACK(&enabledExtensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
pNext = &features;
|
||||
} else {
|
||||
J2dRlsTrace2(J2D_TRACE_WARNING, "Vulkan: %s and %s are not supported\n",
|
||||
VALIDATION_LAYER_NAME, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)
|
||||
}
|
||||
#endif
|
||||
VkApplicationInfo applicationInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||
.pNext = NULL,
|
||||
.pApplicationName = "OpenJDK",
|
||||
.applicationVersion = 0,
|
||||
.pEngineName = "OpenJDK",
|
||||
.engineVersion = 0,
|
||||
.apiVersion = REQUIRED_VULKAN_VERSION
|
||||
};
|
||||
|
||||
VkInstanceCreateInfo instanceCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||
.pNext =pNext,
|
||||
.flags = 0,
|
||||
.pApplicationInfo = &applicationInfo,
|
||||
.enabledLayerCount = ARRAY_SIZE(enabledLayers),
|
||||
.ppEnabledLayerNames = (const char *const *) enabledLayers,
|
||||
.enabledExtensionCount = ARRAY_SIZE(enabledExtensions),
|
||||
.ppEnabledExtensionNames = (const char *const *) enabledExtensions
|
||||
};
|
||||
|
||||
if (vkCreateInstance(&instanceCreateInfo, NULL, &geInstance->vkInstance) != VK_SUCCESS) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Failed to create Vulkan instance\n")
|
||||
vulkanLibClose();
|
||||
ARRAY_FREE(enabledLayers);
|
||||
ARRAY_FREE(enabledExtensions);
|
||||
return NULL;
|
||||
} else {
|
||||
J2dRlsTrace(J2D_TRACE_INFO, "Vulkan: Instance Created\n")
|
||||
}
|
||||
ARRAY_FREE(enabledLayers);
|
||||
ARRAY_FREE(enabledExtensions);
|
||||
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkEnumeratePhysicalDevices);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceFeatures2);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceProperties2);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceQueueFamilyProperties);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkEnumerateDeviceLayerProperties);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkEnumerateDeviceExtensionProperties);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateShaderModule);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreatePipelineLayout);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateGraphicsPipelines);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyShaderModule);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceSurfaceFormatsKHR);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceSurfacePresentModesKHR);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateSwapchainKHR);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetSwapchainImagesKHR);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateImageView);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateFramebuffer);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateCommandPool);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkAllocateCommandBuffers);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateSemaphore);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateFence);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetDeviceQueue);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkWaitForFences);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkResetFences);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkAcquireNextImageKHR);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkResetCommandBuffer);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkQueueSubmit);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkQueuePresentKHR);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkBeginCommandBuffer);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdBeginRenderPass);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdBindPipeline);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdSetViewport);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdSetScissor);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdDraw);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkEndCommandBuffer);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdEndRenderPass);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateImage);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateSampler);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkAllocateMemory);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceMemoryProperties);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkBindImageMemory);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateDescriptorSetLayout);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkUpdateDescriptorSets);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateDescriptorPool);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkAllocateDescriptorSets);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdBindDescriptorSets);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetImageMemoryRequirements);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateBuffer);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetBufferMemoryRequirements);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkBindBufferMemory);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkMapMemory);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkUnmapMemory);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdBindVertexBuffers);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateRenderPass);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyBuffer);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkFreeMemory);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyImageView);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyImage);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyFramebuffer);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkFlushMappedMemoryRanges);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdPushConstants);
|
||||
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceWaylandPresentationSupportKHR);
|
||||
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateWaylandSurfaceKHR);
|
||||
#endif
|
||||
|
||||
}
|
||||
return geInstance;
|
||||
}
|
||||
|
||||
jboolean VK_FindDevices() {
|
||||
uint32_t physicalDevicesCount;
|
||||
if (geInstance->vkEnumeratePhysicalDevices(geInstance->vkInstance,
|
||||
&physicalDevicesCount,
|
||||
NULL) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumeratePhysicalDevices fails\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (physicalDevicesCount == 0) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Failed to find GPUs with Vulkan support\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
} else {
|
||||
J2dRlsTrace1(J2D_TRACE_INFO, "Vulkan: Found %d physical devices:\n", physicalDevicesCount)
|
||||
}
|
||||
|
||||
geInstance->physicalDevices = ARRAY_ALLOC(VkPhysicalDevice, physicalDevicesCount);
|
||||
if (geInstance->physicalDevices == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkPhysicalDevice\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (geInstance->vkEnumeratePhysicalDevices(
|
||||
geInstance->vkInstance,
|
||||
&physicalDevicesCount,
|
||||
geInstance->physicalDevices) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumeratePhysicalDevices fails\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
geInstance->devices = ARRAY_ALLOC(VKLogicalDevice, physicalDevicesCount);
|
||||
if (geInstance->devices == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VKLogicalDevice\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < physicalDevicesCount; i++) {
|
||||
VkPhysicalDeviceVulkan12Features device12Features = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
|
||||
.pNext = NULL
|
||||
};
|
||||
|
||||
VkPhysicalDeviceFeatures2 deviceFeatures2 = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||
.pNext = &device12Features
|
||||
};
|
||||
|
||||
geInstance->vkGetPhysicalDeviceFeatures2(geInstance->physicalDevices[i], &deviceFeatures2);
|
||||
|
||||
VkPhysicalDeviceProperties2 deviceProperties2 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
|
||||
geInstance->vkGetPhysicalDeviceProperties2(geInstance->physicalDevices[i], &deviceProperties2);
|
||||
J2dRlsTrace5(J2D_TRACE_INFO, "\t- %s (%d.%d.%d, %s) ",
|
||||
(const char *) deviceProperties2.properties.deviceName,
|
||||
VK_API_VERSION_MAJOR(deviceProperties2.properties.apiVersion),
|
||||
VK_API_VERSION_MINOR(deviceProperties2.properties.apiVersion),
|
||||
VK_API_VERSION_PATCH(deviceProperties2.properties.apiVersion),
|
||||
physicalDeviceTypeString(deviceProperties2.properties.deviceType))
|
||||
|
||||
if (!deviceFeatures2.features.logicOp) {
|
||||
J2dRlsTrace(J2D_TRACE_INFO, " - hasLogicOp not supported, skipped \n")
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!device12Features.timelineSemaphore) {
|
||||
J2dRlsTrace(J2D_TRACE_INFO, " - hasTimelineSemaphore not supported, skipped \n")
|
||||
continue;
|
||||
}
|
||||
J2dRlsTrace(J2D_TRACE_INFO, "\n")
|
||||
|
||||
uint32_t queueFamilyCount = 0;
|
||||
geInstance->vkGetPhysicalDeviceQueueFamilyProperties(
|
||||
geInstance->physicalDevices[i], &queueFamilyCount, NULL);
|
||||
|
||||
VkQueueFamilyProperties *queueFamilies = (VkQueueFamilyProperties*)calloc(queueFamilyCount,
|
||||
sizeof(VkQueueFamilyProperties));
|
||||
if (queueFamilies == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkQueueFamilyProperties\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
geInstance->vkGetPhysicalDeviceQueueFamilyProperties(
|
||||
geInstance->physicalDevices[i], &queueFamilyCount, queueFamilies);
|
||||
int64_t queueFamily = -1;
|
||||
for (uint32_t j = 0; j < queueFamilyCount; j++) {
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
VkBool32 presentationSupported =
|
||||
geInstance->vkGetPhysicalDeviceWaylandPresentationSupportKHR(
|
||||
geInstance->physicalDevices[i], j, wl_display);
|
||||
#endif
|
||||
char logFlags[5] = {
|
||||
queueFamilies[j].queueFlags & VK_QUEUE_GRAPHICS_BIT ? 'G' : '-',
|
||||
queueFamilies[j].queueFlags & VK_QUEUE_COMPUTE_BIT ? 'C' : '-',
|
||||
queueFamilies[j].queueFlags & VK_QUEUE_TRANSFER_BIT ? 'T' : '-',
|
||||
queueFamilies[j].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT ? 'S' : '-',
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
presentationSupported ? 'P' : '-'
|
||||
#else
|
||||
'-'
|
||||
#endif
|
||||
};
|
||||
J2dRlsTrace3(J2D_TRACE_INFO, " %d queues in family (%.*s)\n", queueFamilies[j].queueCount, 5,
|
||||
logFlags)
|
||||
|
||||
// TODO use compute workloads? Separate transfer-only DMA queue?
|
||||
if (queueFamily == -1 && (queueFamilies[j].queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
&& presentationSupported
|
||||
#endif
|
||||
) {
|
||||
queueFamily = j;
|
||||
}
|
||||
}
|
||||
free(queueFamilies);
|
||||
if (queueFamily == -1) {
|
||||
J2dRlsTrace(J2D_TRACE_INFO, " --------------------- Suitable queue not found, skipped \n")
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t layerCount;
|
||||
geInstance->vkEnumerateDeviceLayerProperties(geInstance->physicalDevices[i], &layerCount, NULL);
|
||||
VkLayerProperties *layers = (VkLayerProperties *) calloc(layerCount, sizeof(VkLayerProperties));
|
||||
if (layers == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkLayerProperties\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
geInstance->vkEnumerateDeviceLayerProperties(geInstance->physicalDevices[i], &layerCount, layers);
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported device layers:\n")
|
||||
for (uint32_t j = 0; j < layerCount; j++) {
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) layers[j].layerName)
|
||||
}
|
||||
|
||||
uint32_t extensionCount;
|
||||
geInstance->vkEnumerateDeviceExtensionProperties(geInstance->physicalDevices[i], NULL, &extensionCount, NULL);
|
||||
VkExtensionProperties *extensions = (VkExtensionProperties *) calloc(
|
||||
extensionCount, sizeof(VkExtensionProperties));
|
||||
if (extensions == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkExtensionProperties\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
geInstance->vkEnumerateDeviceExtensionProperties(
|
||||
geInstance->physicalDevices[i], NULL, &extensionCount, extensions);
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported device extensions:\n")
|
||||
VkBool32 hasSwapChain = VK_FALSE;
|
||||
for (uint32_t j = 0; j < extensionCount; j++) {
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) extensions[j].extensionName)
|
||||
hasSwapChain = hasSwapChain ||
|
||||
strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME, extensions[j].extensionName) == 0;
|
||||
}
|
||||
free(extensions);
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Found:\n")
|
||||
if (hasSwapChain) {
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " VK_KHR_SWAPCHAIN_EXTENSION_NAME\n")
|
||||
}
|
||||
|
||||
if (!hasSwapChain) {
|
||||
J2dRlsTrace(J2D_TRACE_INFO,
|
||||
" --------------------- Required VK_KHR_SWAPCHAIN_EXTENSION_NAME not found, skipped \n")
|
||||
continue;
|
||||
}
|
||||
|
||||
pchar* deviceEnabledLayers = ARRAY_ALLOC(pchar, MAX_ENABLED_LAYERS);
|
||||
if (deviceEnabledLayers == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate deviceEnabledLayers array\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
pchar* deviceEnabledExtensions = ARRAY_ALLOC(pchar, MAX_ENABLED_EXTENSIONS);
|
||||
if (deviceEnabledExtensions == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate deviceEnabledExtensions array\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
ARRAY_PUSH_BACK(&deviceEnabledExtensions, VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
|
||||
// Validation layer
|
||||
#ifdef DEBUG
|
||||
int validationLayerNotSupported = 1;
|
||||
for (uint32_t j = 0; j < layerCount; j++) {
|
||||
if (strcmp(VALIDATION_LAYER_NAME, layers[j].layerName) == 0) {
|
||||
validationLayerNotSupported = 0;
|
||||
ARRAY_PUSH_BACK(&deviceEnabledLayers, VALIDATION_LAYER_NAME);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validationLayerNotSupported) {
|
||||
J2dRlsTrace1(J2D_TRACE_INFO, " %s device layer is not supported\n", VALIDATION_LAYER_NAME)
|
||||
}
|
||||
#endif
|
||||
free(layers);
|
||||
char* deviceName = strdup(deviceProperties2.properties.deviceName);
|
||||
if (deviceName == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot duplicate deviceName\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
ARRAY_PUSH_BACK(&geInstance->devices,
|
||||
((VKLogicalDevice) {
|
||||
.name = deviceName,
|
||||
.device = VK_NULL_HANDLE,
|
||||
.physicalDevice = geInstance->physicalDevices[i],
|
||||
.queueFamily = queueFamily,
|
||||
.enabledLayers = deviceEnabledLayers,
|
||||
.enabledExtensions = deviceEnabledExtensions,
|
||||
}));
|
||||
}
|
||||
if (ARRAY_SIZE(geInstance->devices) == 0) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "No compatible device found\n")
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
jboolean VK_CreateLogicalDevice(jint requestedDevice) {
|
||||
requestedDeviceNumber = requestedDevice;
|
||||
|
||||
if (geInstance == NULL) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: VKGraphicsEnvironment is not initialized\n")
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
DEF_VK_PROC_RET_IF_ERR(geInstance->vkInstance, vkCreateDevice, JNI_FALSE)
|
||||
DEF_VK_PROC_RET_IF_ERR(geInstance->vkInstance, vkCreatePipelineCache, JNI_FALSE)
|
||||
DEF_VK_PROC_RET_IF_ERR(geInstance->vkInstance, vkCreateRenderPass, JNI_FALSE)
|
||||
|
||||
requestedDeviceNumber = (requestedDeviceNumber == -1) ? 0 : requestedDeviceNumber;
|
||||
|
||||
if (requestedDeviceNumber < 0 || (uint32_t)requestedDeviceNumber >= ARRAY_SIZE(geInstance->devices)) {
|
||||
if (verbose) {
|
||||
fprintf(stderr, " Requested device number (%d) not found, fallback to 0\n", requestedDeviceNumber);
|
||||
}
|
||||
requestedDeviceNumber = 0;
|
||||
}
|
||||
geInstance->enabledDeviceNum = requestedDeviceNumber;
|
||||
if (verbose) {
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(geInstance->devices); i++) {
|
||||
fprintf(stderr, " %c%d: %s\n", i == geInstance->enabledDeviceNum ? '*' : ' ',
|
||||
i, geInstance->devices[i].name);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
VKLogicalDevice* logicalDevice = &geInstance->devices[geInstance->enabledDeviceNum];
|
||||
float queuePriority = 1.0f;
|
||||
VkDeviceQueueCreateInfo queueCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = logicalDevice->queueFamily, // obtained separately
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &queuePriority
|
||||
};
|
||||
|
||||
VkPhysicalDeviceFeatures features10 = { .logicOp = VK_TRUE };
|
||||
|
||||
VkDeviceCreateInfo createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||
.pNext = NULL,
|
||||
.flags = 0,
|
||||
.queueCreateInfoCount = 1,
|
||||
.pQueueCreateInfos = &queueCreateInfo,
|
||||
.enabledLayerCount = ARRAY_SIZE(logicalDevice->enabledLayers),
|
||||
.ppEnabledLayerNames = (const char *const *) logicalDevice->enabledLayers,
|
||||
.enabledExtensionCount = ARRAY_SIZE(logicalDevice->enabledExtensions),
|
||||
.ppEnabledExtensionNames = (const char *const *) logicalDevice->enabledExtensions,
|
||||
.pEnabledFeatures = &features10
|
||||
};
|
||||
|
||||
if (vkCreateDevice(logicalDevice->physicalDevice, &createInfo, NULL, &logicalDevice->device) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTrace1(J2D_TRACE_ERROR, "Cannot create device:\n %s\n",
|
||||
geInstance->devices[geInstance->enabledDeviceNum].name)
|
||||
vulkanLibClose();
|
||||
return JNI_FALSE;
|
||||
}
|
||||
VkDevice device = logicalDevice->device;
|
||||
J2dRlsTrace1(J2D_TRACE_INFO, "Logical device (%s) created\n", logicalDevice->name)
|
||||
|
||||
|
||||
// Create command pool
|
||||
VkCommandPoolCreateInfo poolInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
.queueFamilyIndex = logicalDevice->queueFamily
|
||||
};
|
||||
if (geInstance->vkCreateCommandPool(device, &poolInfo, NULL, &logicalDevice->commandPool) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_INFO, "failed to create command pool!")
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
// Create command buffer
|
||||
VkCommandBufferAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = logicalDevice->commandPool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1
|
||||
};
|
||||
|
||||
if (geInstance->vkAllocateCommandBuffers(device, &allocInfo, &logicalDevice->commandBuffer) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_INFO, "failed to allocate command buffers!");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
// Create semaphores
|
||||
VkSemaphoreCreateInfo semaphoreInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
|
||||
};
|
||||
|
||||
VkFenceCreateInfo fenceInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT
|
||||
};
|
||||
|
||||
if (geInstance->vkCreateSemaphore(device, &semaphoreInfo, NULL, &logicalDevice->imageAvailableSemaphore) != VK_SUCCESS ||
|
||||
geInstance->vkCreateSemaphore(device, &semaphoreInfo, NULL, &logicalDevice->renderFinishedSemaphore) != VK_SUCCESS ||
|
||||
geInstance->vkCreateFence(device, &fenceInfo, NULL, &logicalDevice->inFlightFence) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTraceLn(J2D_TRACE_INFO, "failed to create semaphores!");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
geInstance->vkGetDeviceQueue(device, logicalDevice->queueFamily, 0, &logicalDevice->queue);
|
||||
if (logicalDevice->queue == NULL) {
|
||||
J2dRlsTraceLn(J2D_TRACE_INFO, "failed to get device queue!");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
VKTxVertex* vertices = ARRAY_ALLOC(VKTxVertex, 4);
|
||||
ARRAY_PUSH_BACK(&vertices, ((VKTxVertex){-1.0f, -1.0f, 0.0f, 0.0f}));
|
||||
ARRAY_PUSH_BACK(&vertices, ((VKTxVertex){1.0f, -1.0f, 1.0f, 0.0f}));
|
||||
ARRAY_PUSH_BACK(&vertices, ((VKTxVertex){-1.0f, 1.0f, 0.0f, 1.0f}));
|
||||
ARRAY_PUSH_BACK(&vertices, ((VKTxVertex){1.0f, 1.0f, 1.0f, 1.0f}));
|
||||
logicalDevice->blitVertexBuffer = ARRAY_TO_VERTEX_BUF(vertices);
|
||||
if (!logicalDevice->blitVertexBuffer) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Cannot create vertex buffer\n")
|
||||
return JNI_FALSE;
|
||||
}
|
||||
ARRAY_FREE(vertices);
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL JNI_OnUnload(__attribute__((unused)) JavaVM *vm, __attribute__((unused)) void *reserved) {
|
||||
vulkanLibClose();
|
||||
}
|
||||
453
src/java.desktop/share/native/common/java2d/vulkan/VKBase.cpp
Normal file
453
src/java.desktop/share/native/common/java2d/vulkan/VKBase.cpp
Normal file
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, JetBrains s.r.o.. 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 "VKBase.h"
|
||||
#include <Trace.h>
|
||||
#include <set>
|
||||
#if defined(DEBUG)
|
||||
#include <csignal>
|
||||
#endif
|
||||
|
||||
#define VALIDATION_LAYER_NAME "VK_LAYER_KHRONOS_validation"
|
||||
static const uint32_t REQUIRED_VULKAN_VERSION = VK_MAKE_API_VERSION(0, 1, 2, 0);
|
||||
|
||||
bool VKGraphicsEnvironment::_verbose = false;
|
||||
int VKGraphicsEnvironment::_requested_device_number = -1;
|
||||
std::unique_ptr<VKGraphicsEnvironment> VKGraphicsEnvironment::_ge_instance = nullptr;
|
||||
|
||||
// ========== Graphics environment ==========
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
||||
static VkBool32 debugCallback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData
|
||||
) {
|
||||
auto data = (const vk::DebugUtilsMessengerCallbackDataEXT*) pCallbackData;
|
||||
if (data == nullptr) return 0;
|
||||
// Here we can filter messages like this:
|
||||
// if (std::strcmp(data->pMessageIdName, "UNASSIGNED-BestPractices-DrawState-ClearCmdBeforeDraw") == 0) return 0;
|
||||
|
||||
int level = J2D_TRACE_OFF;
|
||||
if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) level = J2D_TRACE_VERBOSE;
|
||||
else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) level = J2D_TRACE_INFO;
|
||||
else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) level = J2D_TRACE_WARNING;
|
||||
else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) level = J2D_TRACE_ERROR;
|
||||
|
||||
J2dRlsTraceLn(level, data->pMessage);
|
||||
|
||||
if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
raise(SIGABRT);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
VKGraphicsEnvironment *VKGraphicsEnvironment::graphics_environment() {
|
||||
if (!_ge_instance) {
|
||||
try {
|
||||
_ge_instance = std::unique_ptr<VKGraphicsEnvironment>(new VKGraphicsEnvironment());
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
J2dRlsTrace1(J2D_TRACE_ERROR, "Vulkan: %s\n", e.what());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return _ge_instance.get();
|
||||
}
|
||||
|
||||
VKGraphicsEnvironment::VKGraphicsEnvironment() :
|
||||
_vk_context(), _vk_instance(nullptr), _default_device(nullptr) {
|
||||
// Load library.
|
||||
uint32_t version = _vk_context.enumerateInstanceVersion();
|
||||
J2dRlsTrace3(J2D_TRACE_INFO, "Vulkan: Available (%d.%d.%d)\n",
|
||||
VK_API_VERSION_MAJOR(version), VK_API_VERSION_MINOR(version), VK_API_VERSION_PATCH(version));
|
||||
|
||||
if (version < REQUIRED_VULKAN_VERSION) {
|
||||
throw std::runtime_error("Vulkan: Unsupported version");
|
||||
}
|
||||
|
||||
// Populate maps and log supported layers & extensions.
|
||||
std::set<std::string> layers, extensions;
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported instance layers:\n");
|
||||
for (auto &l: _vk_context.enumerateInstanceLayerProperties()) {
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) l.layerName);
|
||||
layers.emplace((char *) l.layerName);
|
||||
}
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported instance extensions:\n");
|
||||
for (auto &e: _vk_context.enumerateInstanceExtensionProperties(nullptr)) {
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) e.extensionName);
|
||||
extensions.emplace((char *) e.extensionName);
|
||||
}
|
||||
|
||||
std::vector<const char *> enabledLayers, enabledExtensions;
|
||||
const void *pNext = nullptr;
|
||||
|
||||
// Check required layers & extensions.
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
enabledExtensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
||||
#endif
|
||||
enabledExtensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
|
||||
for (auto e: enabledExtensions) {
|
||||
if (extensions.find(e) == extensions.end()) {
|
||||
throw std::runtime_error(std::string("Vulkan: Required instance extension not supported:") +
|
||||
(char *) e);
|
||||
}
|
||||
}
|
||||
// Configure validation
|
||||
#ifdef DEBUG
|
||||
std::array<vk::ValidationFeatureEnableEXT, 2> enabledValidationFeatures = {
|
||||
// vk::ValidationFeatureEnableEXT::eGpuAssisted, // TODO GPU assisted validation is available only from Vulkan 1.1
|
||||
// vk::ValidationFeatureEnableEXT::eGpuAssistedReserveBindingSlot,
|
||||
vk::ValidationFeatureEnableEXT::eBestPractices,
|
||||
vk::ValidationFeatureEnableEXT::eSynchronizationValidation
|
||||
};
|
||||
vk::ValidationFeaturesEXT validationFeatures {enabledValidationFeatures};
|
||||
if (layers.find(VALIDATION_LAYER_NAME) != layers.end() &&
|
||||
extensions.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != extensions.end()) {
|
||||
enabledLayers.push_back(VALIDATION_LAYER_NAME);
|
||||
enabledExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
pNext = &validationFeatures;
|
||||
} else {
|
||||
J2dRlsTrace2(J2D_TRACE_WARNING, "Vulkan: %s and %s are not supported\n",
|
||||
VALIDATION_LAYER_NAME, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
vk::ApplicationInfo applicationInfo{
|
||||
/*pApplicationName*/ "OpenJDK",
|
||||
/*applicationVersion*/ 0,
|
||||
/*pEngineName*/ "OpenJDK",
|
||||
/*engineVersion*/ 0,
|
||||
/*apiVersion*/ REQUIRED_VULKAN_VERSION
|
||||
};
|
||||
|
||||
vk::InstanceCreateInfo instanceCreateInfo{
|
||||
/*flags*/ {},
|
||||
/*pApplicationInfo*/ &applicationInfo,
|
||||
/*ppEnabledLayerNames*/ enabledLayers,
|
||||
/*ppEnabledExtensionNames*/ enabledExtensions,
|
||||
/*pNext*/ pNext
|
||||
};
|
||||
|
||||
// Save context object at persistent address before passing it further.
|
||||
_vk_instance = vk::raii::Instance(_vk_context, instanceCreateInfo);
|
||||
J2dRlsTrace(J2D_TRACE_INFO, "Vulkan: Instance created\n");
|
||||
|
||||
// Create debug messenger
|
||||
#if defined(DEBUG)
|
||||
if (pNext) {
|
||||
_debugMessenger = vk::raii::DebugUtilsMessengerEXT(_vk_instance, vk::DebugUtilsMessengerCreateInfoEXT {
|
||||
/*flags*/ {},
|
||||
/*messageSeverity*/ vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose,
|
||||
/*messageType*/ vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
|
||||
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
|
||||
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
|
||||
/*pfnUserCallback*/ &debugCallback
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Find suitable devices.
|
||||
for (auto &handle: _vk_instance.enumeratePhysicalDevices()) {
|
||||
VKDevice device {*_vk_instance, std::move(handle)};
|
||||
if (device.supported()) {
|
||||
_devices.push_back(std::make_unique<VKDevice>(std::move(device)));
|
||||
}
|
||||
}
|
||||
|
||||
if (_devices.empty()) {
|
||||
throw std::runtime_error("Vulkan: No suitable device found");
|
||||
}
|
||||
// Create virtual device for a physical device.
|
||||
// TODO integrated/discrete presets
|
||||
// TODO performance/power saving mode switch on the fly?
|
||||
int _default_device_number = 0; // TODO pick first just to check that virtual device creation works
|
||||
|
||||
if (_verbose) {
|
||||
fprintf(stderr, "Vulkan graphics devices:\n");
|
||||
}
|
||||
|
||||
_requested_device_number = (_requested_device_number == -1) ? 0 : _requested_device_number;
|
||||
if (_requested_device_number < 0 || _requested_device_number >= static_cast<int>(_devices.size())) {
|
||||
if (_verbose) {
|
||||
fprintf(stderr, " Requested device number (%d) not found, fallback to 0\n", _requested_device_number);
|
||||
}
|
||||
_requested_device_number = 0;
|
||||
}
|
||||
_default_device_number = _requested_device_number;
|
||||
if (_verbose) {
|
||||
for (auto devIter = _devices.begin(); devIter != _devices.end(); devIter++) {
|
||||
auto devNum = std::distance(begin(_devices), devIter);
|
||||
fprintf(stderr, " %c%ld: %s\n", devNum == _default_device_number ? '*' : ' ',
|
||||
devNum, (*devIter)->name().c_str());
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
_default_device = &*_devices[_default_device_number]; // TODO pick first just to check hat virtual device creation works
|
||||
_default_device->init();
|
||||
|
||||
}
|
||||
|
||||
vk::raii::Instance& VKGraphicsEnvironment::vk_instance() {
|
||||
return _vk_instance;
|
||||
}
|
||||
|
||||
void VKGraphicsEnvironment::dispose() {
|
||||
_ge_instance.reset();
|
||||
}
|
||||
|
||||
VKDevice& VKGraphicsEnvironment::default_device() {
|
||||
return *_default_device;
|
||||
}
|
||||
|
||||
// ========== Vulkan device ==========
|
||||
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
extern struct wl_display *wl_display;
|
||||
#endif
|
||||
|
||||
VKDevice::VKDevice(vk::Instance instance, vk::raii::PhysicalDevice&& handle) :
|
||||
vk::raii::Device(nullptr), vk::raii::PhysicalDevice(nullptr), _instance(instance) {
|
||||
auto featuresChain = handle.getFeatures2<vk::PhysicalDeviceFeatures2,
|
||||
vk::PhysicalDeviceVulkan11Features,
|
||||
vk::PhysicalDeviceVulkan12Features>();
|
||||
const auto& features10 = featuresChain.get<vk::PhysicalDeviceFeatures2>().features;
|
||||
const auto& features11 = featuresChain.get<vk::PhysicalDeviceVulkan11Features>();
|
||||
const auto& features12 = featuresChain.get<vk::PhysicalDeviceVulkan12Features>();
|
||||
|
||||
auto propertiesChain = handle.getProperties2<vk::PhysicalDeviceProperties2,
|
||||
vk::PhysicalDeviceVulkan11Properties,
|
||||
vk::PhysicalDeviceVulkan12Properties>();
|
||||
const auto& properties10 = propertiesChain.get<vk::PhysicalDeviceProperties2>().properties;
|
||||
const auto& properties11 = propertiesChain.get<vk::PhysicalDeviceVulkan11Properties>();
|
||||
const auto& properties12 = propertiesChain.get<vk::PhysicalDeviceVulkan12Properties>();
|
||||
|
||||
const auto& queueFamilies = handle.getQueueFamilyProperties();
|
||||
|
||||
_name = (const char*) properties10.deviceName;
|
||||
J2dRlsTrace5(J2D_TRACE_INFO, "Vulkan: Found device %s (%d.%d.%d, %s)\n",
|
||||
(const char*) properties10.deviceName,
|
||||
VK_API_VERSION_MAJOR(properties10.apiVersion),
|
||||
VK_API_VERSION_MINOR(properties10.apiVersion),
|
||||
VK_API_VERSION_PATCH(properties10.apiVersion),
|
||||
vk::to_string(properties10.deviceType).c_str());
|
||||
|
||||
// Check API version.
|
||||
if (properties10.apiVersion < REQUIRED_VULKAN_VERSION) {
|
||||
J2dRlsTrace(J2D_TRACE_INFO, " Unsupported Vulkan version\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check supported features.
|
||||
if (!features10.logicOp) {
|
||||
J2dRlsTrace(J2D_TRACE_INFO, " Logic op not supported\n");
|
||||
return;
|
||||
}
|
||||
if (!features12.timelineSemaphore) {
|
||||
J2dRlsTrace(J2D_TRACE_INFO, " Timeline semaphore not supported\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check supported queue families.
|
||||
for (unsigned int i = 0; i < queueFamilies.size(); i++) {
|
||||
const auto& family = queueFamilies[i];
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
bool presentationSupported = handle.getWaylandPresentationSupportKHR(i, *wl_display);
|
||||
#endif
|
||||
char logFlags[5] {
|
||||
family.queueFlags & vk::QueueFlagBits::eGraphics ? 'G' : '-',
|
||||
family.queueFlags & vk::QueueFlagBits::eCompute ? 'C' : '-',
|
||||
family.queueFlags & vk::QueueFlagBits::eTransfer ? 'T' : '-',
|
||||
family.queueFlags & vk::QueueFlagBits::eSparseBinding ? 'S' : '-',
|
||||
presentationSupported ? 'P' : '-'
|
||||
};
|
||||
J2dRlsTrace3(J2D_TRACE_INFO, " %d queues in family (%.*s)\n", family.queueCount, 5, logFlags);
|
||||
|
||||
// TODO use compute workloads? Separate transfer-only DMA queue?
|
||||
if (_queue_family == -1 && (family.queueFlags & vk::QueueFlagBits::eGraphics) && presentationSupported) {
|
||||
_queue_family = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (_queue_family == -1) {
|
||||
J2dRlsTrace(J2D_TRACE_INFO, " No suitable queue\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate maps and log supported layers & extensions.
|
||||
std::set<std::string> layers, extensions;
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported device layers:\n");
|
||||
for (auto& l : handle.enumerateDeviceLayerProperties()) {
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char*) l.layerName);
|
||||
layers.emplace((char*) l.layerName);
|
||||
}
|
||||
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported device extensions:\n");
|
||||
for (auto& e : handle.enumerateDeviceExtensionProperties(nullptr)) {
|
||||
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char*) e.extensionName);
|
||||
extensions.emplace((char*) e.extensionName);
|
||||
}
|
||||
|
||||
// Check required layers & extensions.
|
||||
_enabled_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
bool requiredNotFound = false;
|
||||
for (auto e : _enabled_extensions) {
|
||||
if (extensions.find(e) == extensions.end()) {
|
||||
J2dRlsTrace1(J2D_TRACE_INFO, " Required device extension not supported: %s\n", (char*) e);
|
||||
requiredNotFound = true;
|
||||
}
|
||||
}
|
||||
if (requiredNotFound) return;
|
||||
_ext_memory_budget = extensions.find(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) != extensions.end();
|
||||
if (_ext_memory_budget) _enabled_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
|
||||
_khr_synchronization2 = extensions.find(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME) != extensions.end();
|
||||
if (_khr_synchronization2) _enabled_extensions.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME);
|
||||
_khr_dynamic_rendering = extensions.find(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) != extensions.end();
|
||||
if (_khr_dynamic_rendering) _enabled_extensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
|
||||
|
||||
// Validation layer
|
||||
#ifdef DEBUG
|
||||
if (layers.find(VALIDATION_LAYER_NAME) != layers.end()) {
|
||||
_enabled_layers.push_back(VALIDATION_LAYER_NAME);
|
||||
} else {
|
||||
J2dRlsTrace1(J2D_TRACE_INFO, " %s device layer is not supported\n", VALIDATION_LAYER_NAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
// This device is supported
|
||||
((vk::raii::PhysicalDevice&) *this) = std::move(handle);
|
||||
}
|
||||
|
||||
void VKDevice::init() {
|
||||
float queuePriorities[1] {1.0f}; // We only use one queue for now
|
||||
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
|
||||
queueCreateInfos.push_back(vk::DeviceQueueCreateInfo {
|
||||
{}, queue_family(), 1, &queuePriorities[0]
|
||||
});
|
||||
|
||||
vk::PhysicalDeviceFeatures features10;
|
||||
features10.logicOp = true;
|
||||
vk::PhysicalDeviceVulkan12Features features12;
|
||||
features12.timelineSemaphore = true;
|
||||
|
||||
void *pNext = &features12;
|
||||
vk::PhysicalDeviceSynchronization2FeaturesKHR synchronization2Features;
|
||||
if (_khr_synchronization2) {
|
||||
synchronization2Features.synchronization2 = true;
|
||||
synchronization2Features.pNext = pNext;
|
||||
pNext = &synchronization2Features;
|
||||
}
|
||||
vk::PhysicalDeviceDynamicRenderingFeaturesKHR dynamicRenderingFeatures;
|
||||
if (_khr_dynamic_rendering) {
|
||||
dynamicRenderingFeatures.dynamicRendering = true;
|
||||
dynamicRenderingFeatures.pNext = pNext;
|
||||
pNext = &dynamicRenderingFeatures;
|
||||
}
|
||||
|
||||
vk::DeviceCreateInfo deviceCreateInfo {
|
||||
/*flags*/ {},
|
||||
/*pQueueCreateInfos*/ queueCreateInfos,
|
||||
/*ppEnabledLayerNames*/ _enabled_layers,
|
||||
/*ppEnabledExtensionNames*/ _enabled_extensions,
|
||||
/*pEnabledFeatures*/ &features10,
|
||||
/*pNext*/ pNext
|
||||
};
|
||||
((vk::raii::Device&) *this) = {*this, deviceCreateInfo};
|
||||
_memory.init(_instance, *this, *this, REQUIRED_VULKAN_VERSION, _ext_memory_budget);
|
||||
_pipelines.init((vk::raii::Device&) *this, _khr_dynamic_rendering);
|
||||
_queue = getQueue(queue_family(), 0);
|
||||
_commandPool = createCommandPool(vk::CommandPoolCreateInfo {
|
||||
vk::CommandPoolCreateFlagBits::eTransient | vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
||||
queue_family()
|
||||
});
|
||||
vk::SemaphoreTypeCreateInfo semaphoreTypeCreateInfo { vk::SemaphoreType::eTimeline, 0 };
|
||||
_timelineSemaphore = createSemaphore(vk::SemaphoreCreateInfo {{}, &semaphoreTypeCreateInfo});
|
||||
_timelineCounter = 0;
|
||||
J2dRlsTrace1(J2D_TRACE_INFO, "Vulkan: Device created %s\n", _name.c_str());
|
||||
}
|
||||
|
||||
VKBuffer VKDevice::getVertexBuffer() {
|
||||
auto b = popPending<VKBuffer>(_pendingVertexBuffers);
|
||||
if (*b) {
|
||||
b.position() = 0;
|
||||
return b;
|
||||
} else {
|
||||
return _memory.allocateBuffer(64 * 1024, vk::BufferUsageFlagBits::eVertexBuffer,
|
||||
vma::AllocationCreateFlagBits::eMapped | vma::AllocationCreateFlagBits::eHostAccessSequentialWrite,
|
||||
vma::MemoryUsage::eAutoPreferHost);
|
||||
}
|
||||
}
|
||||
|
||||
vk::raii::CommandBuffer VKDevice::getCommandBuffer(vk::CommandBufferLevel level) {
|
||||
auto b = popPending<vk::raii::CommandBuffer>(level == vk::CommandBufferLevel::ePrimary ?
|
||||
_pendingPrimaryBuffers : _pendingSecondaryBuffers);
|
||||
if (*b) {
|
||||
b.reset({});
|
||||
return b;
|
||||
} else {
|
||||
return std::move(allocateCommandBuffers({*_commandPool, level, 1})[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void VKDevice::submitCommandBuffer(vk::raii::CommandBuffer&& primary,
|
||||
std::vector<vk::raii::CommandBuffer>& secondary,
|
||||
std::vector<VKBuffer>& vertexBuffers,
|
||||
std::vector<vk::Semaphore>& waitSemaphores,
|
||||
std::vector<vk::PipelineStageFlags>& waitStages,
|
||||
std::vector<vk::Semaphore>& signalSemaphores) {
|
||||
_timelineCounter++;
|
||||
signalSemaphores.insert(signalSemaphores.begin(), *_timelineSemaphore);
|
||||
vk::TimelineSemaphoreSubmitInfo timelineInfo { 0, nullptr, (uint32_t) signalSemaphores.size(), &_timelineCounter };
|
||||
queue().submit(vk::SubmitInfo {
|
||||
waitSemaphores, waitStages, *primary, signalSemaphores, &timelineInfo
|
||||
}, nullptr);
|
||||
pushPending(_pendingPrimaryBuffers, std::move(primary));
|
||||
pushPending(_pendingSecondaryBuffers, secondary);
|
||||
pushPending(_pendingVertexBuffers, vertexBuffers);
|
||||
signalSemaphores.clear();
|
||||
waitSemaphores.clear();
|
||||
waitStages.clear();
|
||||
}
|
||||
|
||||
extern "C" jboolean VK_Init(jboolean verbose, jint requestedDevice) {
|
||||
VKGraphicsEnvironment::set_verbose(verbose);
|
||||
VKGraphicsEnvironment::set_requested_device(requestedDevice);
|
||||
if (VKGraphicsEnvironment::graphics_environment() != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
VKGraphicsEnvironment::dispose();
|
||||
}
|
||||
@@ -26,124 +26,139 @@
|
||||
|
||||
#ifndef VKBase_h_Included
|
||||
#define VKBase_h_Included
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "CArrayUtil.h"
|
||||
#ifdef __cplusplus
|
||||
|
||||
|
||||
#define VK_NO_PROTOTYPES
|
||||
#define VULKAN_HPP_NO_DEFAULT_DISPATCHER
|
||||
#include <queue>
|
||||
#include <vulkan/vulkan_raii.hpp>
|
||||
#include "jni.h"
|
||||
#include "VKBuffer.h"
|
||||
#include "VKMemory.h"
|
||||
#include "VKPipeline.h"
|
||||
|
||||
typedef char* pchar;
|
||||
class VKDevice : public vk::raii::Device, public vk::raii::PhysicalDevice {
|
||||
friend class VKGraphicsEnvironment;
|
||||
|
||||
typedef struct {
|
||||
VkRenderPass renderPass;
|
||||
VkDescriptorSetLayout descriptorSetLayout;
|
||||
VkDescriptorPool descriptorPool;
|
||||
VkDescriptorSet descriptorSets;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VkPipeline graphicsPipeline;
|
||||
} VKRenderer;
|
||||
vk::Instance _instance;
|
||||
std::string _name;
|
||||
std::vector<const char*> _enabled_layers, _enabled_extensions;
|
||||
bool _ext_memory_budget, _khr_synchronization2, _khr_dynamic_rendering;
|
||||
int _queue_family = -1;
|
||||
|
||||
typedef struct {
|
||||
VkDevice device;
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VKRenderer* fillTexturePoly;
|
||||
VKRenderer* fillColorPoly;
|
||||
VKRenderer* fillMaxColorPoly;
|
||||
char* name;
|
||||
uint32_t queueFamily;
|
||||
pchar* enabledLayers;
|
||||
pchar* enabledExtensions;
|
||||
VkCommandPool commandPool;
|
||||
VkCommandBuffer commandBuffer;
|
||||
VkSemaphore imageAvailableSemaphore;
|
||||
VkSemaphore renderFinishedSemaphore;
|
||||
VkFence inFlightFence;
|
||||
VkQueue queue;
|
||||
VkSampler textureSampler;
|
||||
VKBuffer* blitVertexBuffer;
|
||||
} VKLogicalDevice;
|
||||
// Logical device state
|
||||
VKMemory _memory;
|
||||
VKPipelines _pipelines;
|
||||
vk::raii::Queue _queue = nullptr;
|
||||
vk::raii::CommandPool _commandPool = nullptr;
|
||||
vk::raii::Semaphore _timelineSemaphore = nullptr;
|
||||
uint64_t _timelineCounter = 0;
|
||||
uint64_t _lastReadTimelineCounter = 0;
|
||||
|
||||
template <typename T> struct Pending {
|
||||
T resource;
|
||||
uint64_t counter;
|
||||
using Queue = std::queue<Pending<T>>;
|
||||
};
|
||||
Pending<vk::raii::CommandBuffer>::Queue _pendingPrimaryBuffers, _pendingSecondaryBuffers;
|
||||
Pending<VKBuffer>::Queue _pendingVertexBuffers;
|
||||
|
||||
typedef struct {
|
||||
VkInstance vkInstance;
|
||||
VkPhysicalDevice* physicalDevices;
|
||||
VKLogicalDevice* devices;
|
||||
uint32_t enabledDeviceNum;
|
||||
VkExtensionProperties* extensions;
|
||||
VkLayerProperties* layers;
|
||||
template <typename T> T popPending(typename Pending<T>::Queue& queue) {
|
||||
if (!queue.empty()) {
|
||||
auto& f = queue.front();
|
||||
if (_lastReadTimelineCounter >= f.counter ||
|
||||
(_lastReadTimelineCounter = _timelineSemaphore.getCounterValue()) >= f.counter) {
|
||||
T resource = std::move(f.resource);
|
||||
queue.pop();
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
return T(nullptr);
|
||||
}
|
||||
template <typename T> void pushPending(typename Pending<T>::Queue& queue, T&& resource) {
|
||||
queue.push({std::move(resource), _timelineCounter});
|
||||
}
|
||||
template <typename T> void pushPending(typename Pending<T>::Queue& queue, std::vector<T>& resources) {
|
||||
for (T& r : resources) {
|
||||
pushPending(queue, std::move(r));
|
||||
}
|
||||
resources.clear();
|
||||
}
|
||||
|
||||
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
|
||||
PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2;
|
||||
PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2;
|
||||
PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
|
||||
PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
|
||||
PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
|
||||
PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
|
||||
explicit VKDevice(vk::Instance instance, vk::raii::PhysicalDevice&& handle);
|
||||
public:
|
||||
|
||||
bool synchronization2() {
|
||||
return _khr_synchronization2;
|
||||
}
|
||||
|
||||
bool dynamicRendering() {
|
||||
return _khr_dynamic_rendering;
|
||||
}
|
||||
|
||||
VKPipelines& pipelines() {
|
||||
return _pipelines;
|
||||
}
|
||||
|
||||
uint32_t queue_family() const {
|
||||
return (uint32_t) _queue_family;
|
||||
}
|
||||
|
||||
const vk::raii::Queue& queue() const {
|
||||
return _queue;
|
||||
}
|
||||
|
||||
void init(); // Creates actual logical device
|
||||
|
||||
VKBuffer getVertexBuffer();
|
||||
vk::raii::CommandBuffer getCommandBuffer(vk::CommandBufferLevel level);
|
||||
void submitCommandBuffer(vk::raii::CommandBuffer&& primary,
|
||||
std::vector<vk::raii::CommandBuffer>& secondary,
|
||||
std::vector<VKBuffer>& vertexBuffers,
|
||||
std::vector<vk::Semaphore>& waitSemaphores,
|
||||
std::vector<vk::PipelineStageFlags>& waitStages,
|
||||
std::vector<vk::Semaphore>& signalSemaphores);
|
||||
|
||||
bool supported() const { // Supported or not
|
||||
return *((const vk::raii::PhysicalDevice&) *this);
|
||||
}
|
||||
|
||||
explicit operator bool() const { // Initialized or not
|
||||
return *((const vk::raii::Device&) *this);
|
||||
}
|
||||
|
||||
const std::string& name() {
|
||||
return _name;
|
||||
}
|
||||
};
|
||||
|
||||
class VKGraphicsEnvironment {
|
||||
vk::raii::Context _vk_context;
|
||||
vk::raii::Instance _vk_instance;
|
||||
#if defined(DEBUG)
|
||||
vk::raii::DebugUtilsMessengerEXT _debugMessenger = nullptr;
|
||||
#endif
|
||||
PFN_vkCreateShaderModule vkCreateShaderModule;
|
||||
PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
|
||||
PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
|
||||
PFN_vkDestroyShaderModule vkDestroyShaderModule;
|
||||
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
|
||||
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
|
||||
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
|
||||
PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
|
||||
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
|
||||
PFN_vkCreateImageView vkCreateImageView;
|
||||
PFN_vkCreateFramebuffer vkCreateFramebuffer;
|
||||
PFN_vkCreateCommandPool vkCreateCommandPool;
|
||||
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
|
||||
PFN_vkCreateSemaphore vkCreateSemaphore;
|
||||
PFN_vkCreateFence vkCreateFence;
|
||||
PFN_vkGetDeviceQueue vkGetDeviceQueue;
|
||||
PFN_vkWaitForFences vkWaitForFences;
|
||||
PFN_vkResetFences vkResetFences;
|
||||
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
|
||||
PFN_vkResetCommandBuffer vkResetCommandBuffer;
|
||||
PFN_vkQueueSubmit vkQueueSubmit;
|
||||
PFN_vkQueuePresentKHR vkQueuePresentKHR;
|
||||
PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
|
||||
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
|
||||
PFN_vkCmdBindPipeline vkCmdBindPipeline;
|
||||
PFN_vkCmdSetViewport vkCmdSetViewport;
|
||||
PFN_vkCmdSetScissor vkCmdSetScissor;
|
||||
PFN_vkCmdDraw vkCmdDraw;
|
||||
PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
|
||||
PFN_vkEndCommandBuffer vkEndCommandBuffer;
|
||||
PFN_vkCreateImage vkCreateImage;
|
||||
PFN_vkCreateSampler vkCreateSampler;
|
||||
PFN_vkAllocateMemory vkAllocateMemory;
|
||||
PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
|
||||
PFN_vkBindImageMemory vkBindImageMemory;
|
||||
PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
|
||||
PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
|
||||
PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
|
||||
PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
|
||||
PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
|
||||
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
|
||||
PFN_vkCreateBuffer vkCreateBuffer;
|
||||
PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
|
||||
PFN_vkBindBufferMemory vkBindBufferMemory;
|
||||
PFN_vkMapMemory vkMapMemory;
|
||||
PFN_vkUnmapMemory vkUnmapMemory;
|
||||
PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
|
||||
PFN_vkCreateRenderPass vkCreateRenderPass;
|
||||
PFN_vkDestroyBuffer vkDestroyBuffer;
|
||||
PFN_vkFreeMemory vkFreeMemory;
|
||||
PFN_vkDestroyImageView vkDestroyImageView;
|
||||
PFN_vkDestroyImage vkDestroyImage;
|
||||
PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
|
||||
PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
|
||||
PFN_vkCmdPushConstants vkCmdPushConstants;
|
||||
} VKGraphicsEnvironment;
|
||||
std::vector<std::unique_ptr<VKDevice>> _devices;
|
||||
VKDevice* _default_device;
|
||||
static bool _verbose;
|
||||
static int _requested_device_number;
|
||||
static std::unique_ptr<VKGraphicsEnvironment> _ge_instance;
|
||||
VKGraphicsEnvironment();
|
||||
public:
|
||||
static void set_verbose(bool verbose) { _verbose = verbose; }
|
||||
static void set_requested_device(int requested_device) { _requested_device_number = requested_device; }
|
||||
static VKGraphicsEnvironment* graphics_environment();
|
||||
static void dispose();
|
||||
VKDevice& default_device();
|
||||
vk::raii::Instance& vk_instance();
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
#endif //__cplusplus
|
||||
|
||||
jboolean VK_FindDevices();
|
||||
jboolean VK_CreateLogicalDevice(jint requestedDeviceNumber);
|
||||
jboolean VK_CreateLogicalDeviceRenderers();
|
||||
|
||||
VKGraphicsEnvironment* VKGE_graphics_environment();
|
||||
void* vulkanLibProc(VkInstance vkInstance, char* procName);
|
||||
jboolean VK_Init(jboolean verbose, jint requestedDevice);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif //__cplusplus
|
||||
#endif //VKBase_h_Included
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. 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 <string.h>
|
||||
#include <Trace.h>
|
||||
#include "CArrayUtil.h"
|
||||
#include "VKBase.h"
|
||||
#include "VKBuffer.h"
|
||||
|
||||
VkResult VKBuffer_FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter,
|
||||
VkMemoryPropertyFlags properties, uint32_t* pMemoryType) {
|
||||
VkPhysicalDeviceMemoryProperties memProperties;
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
ge->vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
|
||||
|
||||
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
||||
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
*pMemoryType = i;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return VK_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
VKBuffer* VKBuffer_Create(VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
VkMemoryPropertyFlags properties)
|
||||
{
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
|
||||
VKBuffer* buffer = malloc(sizeof (VKBuffer));
|
||||
|
||||
VkBufferCreateInfo bufferInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = size,
|
||||
.usage = usage,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
|
||||
};
|
||||
|
||||
if (ge->vkCreateBuffer(logicalDevice->device, &bufferInfo, NULL, &buffer->buffer) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to allocate descriptor sets!")
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buffer->size = size;
|
||||
|
||||
VkMemoryRequirements memRequirements;
|
||||
ge->vkGetBufferMemoryRequirements(logicalDevice->device, buffer->buffer, &memRequirements);
|
||||
|
||||
uint32_t memoryType;
|
||||
|
||||
if (VKBuffer_FindMemoryType(logicalDevice->physicalDevice,
|
||||
memRequirements.memoryTypeBits,
|
||||
properties, &memoryType) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to find memory!")
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memRequirements.size,
|
||||
.memoryTypeIndex = memoryType
|
||||
};
|
||||
|
||||
if (ge->vkAllocateMemory(logicalDevice->device, &allocInfo, NULL, &buffer->memory) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to allocate buffer memory!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ge->vkBindBufferMemory(logicalDevice->device, buffer->buffer, buffer->memory, 0) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to bind buffer memory!");
|
||||
return NULL;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
VKBuffer* VKBuffer_CreateFromData(void* vertices, VkDeviceSize bufferSize)
|
||||
{
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
|
||||
|
||||
VKBuffer* buffer = VKBuffer_Create(bufferSize,
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
|
||||
void* data;
|
||||
if (ge->vkMapMemory(logicalDevice->device, buffer->memory, 0, bufferSize, 0, &data) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to map memory!");
|
||||
return NULL;
|
||||
}
|
||||
memcpy(data, vertices, bufferSize);
|
||||
|
||||
VkMappedMemoryRange memoryRange = {
|
||||
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
||||
.pNext = NULL,
|
||||
.memory = buffer->memory,
|
||||
.offset = 0,
|
||||
.size = VK_WHOLE_SIZE
|
||||
};
|
||||
|
||||
|
||||
if (ge->vkFlushMappedMemoryRanges(logicalDevice->device, 1, &memoryRange) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to flush memory!");
|
||||
return NULL;
|
||||
}
|
||||
ge->vkUnmapMemory(logicalDevice->device, buffer->memory);
|
||||
buffer->size = bufferSize;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void VKBuffer_free(VKBuffer* buffer) {
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
|
||||
|
||||
if (buffer != NULL) {
|
||||
if (buffer->buffer != VK_NULL_HANDLE) {
|
||||
ge->vkDestroyBuffer(logicalDevice->device, buffer->buffer, NULL);
|
||||
}
|
||||
if (buffer->memory != VK_NULL_HANDLE) {
|
||||
ge->vkFreeMemory(logicalDevice->device, buffer->memory, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. 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 <Trace.h>
|
||||
#include "VKBase.h"
|
||||
#include "VKBuffer.h"
|
||||
#include "VKImage.h"
|
||||
|
||||
|
||||
VkBool32 VKImage_CreateView(VKImage* image) {
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
|
||||
|
||||
VkImageViewCreateInfo viewInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image->image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = image->format,
|
||||
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.subresourceRange.baseMipLevel = 0,
|
||||
.subresourceRange.levelCount = 1,
|
||||
.subresourceRange.baseArrayLayer = 0,
|
||||
.subresourceRange.layerCount = 1,
|
||||
};
|
||||
|
||||
if (ge->vkCreateImageView(logicalDevice->device, &viewInfo, NULL, &image->view) != VK_SUCCESS) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Cannot surface image view\n");
|
||||
return VK_FALSE;
|
||||
}
|
||||
return VK_TRUE;
|
||||
}
|
||||
|
||||
VkBool32 VKImage_CreateFramebuffer(VKImage *image, VkRenderPass renderPass) {
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
|
||||
|
||||
VkImageView attachments[] = {
|
||||
image->view
|
||||
};
|
||||
|
||||
VkFramebufferCreateInfo framebufferInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.renderPass = renderPass,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = attachments,
|
||||
.width = image->extent.width,
|
||||
.height = image->extent.height,
|
||||
.layers = 1
|
||||
};
|
||||
|
||||
if (ge->vkCreateFramebuffer(logicalDevice->device, &framebufferInfo, NULL,
|
||||
&image->framebuffer) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to create framebuffer!")
|
||||
return VK_FALSE;
|
||||
}
|
||||
return VK_TRUE;
|
||||
}
|
||||
|
||||
VKImage* VKImage_Create(uint32_t width, uint32_t height,
|
||||
VkFormat format, VkImageTiling tiling,
|
||||
VkImageUsageFlags usage,
|
||||
VkMemoryPropertyFlags properties)
|
||||
{
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
|
||||
VKImage* image = malloc(sizeof (VKImage));
|
||||
|
||||
if (!image) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "Cannot allocate data for image")
|
||||
return NULL;
|
||||
}
|
||||
|
||||
image->format = format;
|
||||
image->extent = (VkExtent2D) {width, height};
|
||||
|
||||
VkImageCreateInfo imageInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.extent.width = width,
|
||||
.extent.height = height,
|
||||
.extent.depth = 1,
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.format = format,
|
||||
.tiling = tiling,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.usage = usage,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
|
||||
};
|
||||
|
||||
if (ge->vkCreateImage(logicalDevice->device, &imageInfo, NULL, &image->image) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "Cannot create surface image")
|
||||
VKImage_free(image);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VkMemoryRequirements memRequirements;
|
||||
ge->vkGetImageMemoryRequirements(logicalDevice->device, image->image, &memRequirements);
|
||||
|
||||
uint32_t memoryType;
|
||||
if (VKBuffer_FindMemoryType(logicalDevice->physicalDevice,
|
||||
memRequirements.memoryTypeBits,
|
||||
properties, &memoryType) != VK_SUCCESS)
|
||||
{
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "Failed to find memory")
|
||||
VKImage_free(image);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memRequirements.size,
|
||||
.memoryTypeIndex = memoryType
|
||||
};
|
||||
|
||||
if (ge->vkAllocateMemory(logicalDevice->device, &allocInfo, NULL, &image->memory) != VK_SUCCESS) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "Failed to allocate image memory");
|
||||
VKImage_free(image);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ge->vkBindImageMemory(logicalDevice->device, image->image, image->memory, 0);
|
||||
|
||||
if (!VKImage_CreateView(image)) {
|
||||
VKImage_free(image);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
VKImage* VKImage_CreateImageArrayFromSwapChain(VkSwapchainKHR swapchainKhr, VkRenderPass renderPass,
|
||||
VkFormat format, VkExtent2D extent)
|
||||
{
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
|
||||
uint32_t swapChainImagesCount;
|
||||
if (ge->vkGetSwapchainImagesKHR(logicalDevice->device, swapchainKhr, &swapChainImagesCount,
|
||||
NULL) != VK_SUCCESS) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Cannot get swapchain images\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (swapChainImagesCount == 0) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "No swapchain images found\n");
|
||||
return NULL;
|
||||
}
|
||||
VkImage swapChainImages[swapChainImagesCount];
|
||||
|
||||
if (ge->vkGetSwapchainImagesKHR(logicalDevice->device, swapchainKhr, &swapChainImagesCount,
|
||||
swapChainImages) != VK_SUCCESS) {
|
||||
J2dRlsTrace(J2D_TRACE_ERROR, "Cannot get swapchain images\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VKImage* images = ARRAY_ALLOC(VKImage, swapChainImagesCount);
|
||||
for (uint32_t i = 0; i < swapChainImagesCount; i++) {
|
||||
ARRAY_PUSH_BACK(&images, ((VKImage){
|
||||
.image = swapChainImages[i],
|
||||
.memory = VK_NULL_HANDLE,
|
||||
.format = format,
|
||||
.extent = extent,
|
||||
.noImageDealloc = VK_TRUE
|
||||
}));
|
||||
|
||||
if (!VKImage_CreateView(&ARRAY_LAST(images))) {
|
||||
ARRAY_APPLY(images, VKImage_dealloc);
|
||||
ARRAY_FREE(images);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!VKImage_CreateFramebuffer(&ARRAY_LAST(images), renderPass)) {
|
||||
ARRAY_APPLY(images, VKImage_dealloc);
|
||||
ARRAY_FREE(images);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
void VKImage_dealloc(VKImage* image) {
|
||||
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
|
||||
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
|
||||
|
||||
if (!image) return;
|
||||
|
||||
if (image->framebuffer != VK_NULL_HANDLE) {
|
||||
ge->vkDestroyFramebuffer(logicalDevice->device, image->framebuffer, NULL);
|
||||
}
|
||||
|
||||
if (image->view != VK_NULL_HANDLE) {
|
||||
ge->vkDestroyImageView(logicalDevice->device, image->view, NULL);
|
||||
}
|
||||
|
||||
if (image->memory != VK_NULL_HANDLE) {
|
||||
ge->vkFreeMemory(logicalDevice->device, image->memory, NULL);
|
||||
}
|
||||
|
||||
if (image->image != VK_NULL_HANDLE && !image->noImageDealloc) {
|
||||
ge->vkDestroyImage(logicalDevice->device, image->image, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void VKImage_free(VKImage* image) {
|
||||
VKImage_dealloc(image);
|
||||
free(image);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. 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.
|
||||
*/
|
||||
|
||||
#ifndef VKImage_h_Included
|
||||
#define VKImage_h_Included
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
typedef struct {
|
||||
VkImage image;
|
||||
VkDeviceMemory memory;
|
||||
VkFramebuffer framebuffer;
|
||||
VkImageView view;
|
||||
VkFormat format;
|
||||
VkExtent2D extent;
|
||||
VkBool32 noImageDealloc;
|
||||
} VKImage;
|
||||
|
||||
VKImage* VKImage_Create(uint32_t width, uint32_t height,
|
||||
VkFormat format, VkImageTiling tiling,
|
||||
VkImageUsageFlags usage,
|
||||
VkMemoryPropertyFlags properties);
|
||||
|
||||
VKImage* VKImage_CreateImageArrayFromSwapChain(VkSwapchainKHR swapchainKhr,
|
||||
VkRenderPass renderPass,
|
||||
VkFormat format,
|
||||
VkExtent2D extent);
|
||||
|
||||
VkBool32 VKImage_CreateFramebuffer(VKImage *image, VkRenderPass renderPass);
|
||||
|
||||
void VKImage_free(VKImage* image);
|
||||
void VKImage_dealloc(VKImage* image);
|
||||
#endif // VKImage_h_Included
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, JetBrains s.r.o.. 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.
|
||||
*/
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include "VKMemory.h"
|
||||
|
||||
void VKMemory::init(vk::Instance instance, const vk::raii::PhysicalDevice& physicalDevice,
|
||||
const vk::raii::Device& device, uint32_t apiVersion, bool extMemoryBudget) {
|
||||
vma::VulkanFunctions functions = vma::functionsFromDispatcher(physicalDevice.getDispatcher(), device.getDispatcher());
|
||||
vma::AllocatorCreateInfo createInfo {};
|
||||
createInfo.instance = instance;
|
||||
createInfo.physicalDevice = *physicalDevice;
|
||||
createInfo.device = *device;
|
||||
createInfo.pVulkanFunctions = &functions;
|
||||
createInfo.vulkanApiVersion = apiVersion;
|
||||
if (extMemoryBudget) {
|
||||
createInfo.flags |= vma::AllocatorCreateFlagBits::eExtMemoryBudget;
|
||||
}
|
||||
_allocator = vma::createAllocatorUnique(createInfo);
|
||||
*((vma::Allocator*) this) = *_allocator;
|
||||
}
|
||||
|
||||
VKBuffer VKMemory::allocateBuffer(uint32_t size, vk::BufferUsageFlags usage,
|
||||
vma::AllocationCreateFlags flags, vma::MemoryUsage memoryUsage) {
|
||||
VKBuffer b = nullptr;
|
||||
auto pair = createBufferUnique(vk::BufferCreateInfo {
|
||||
{}, size, usage, vk::SharingMode::eExclusive, {}
|
||||
}, vma::AllocationCreateInfo {
|
||||
flags,
|
||||
memoryUsage, {}, {}, (uint32_t) -1
|
||||
}, b._allocationInfo);
|
||||
b._buffer = std::move(pair.first);
|
||||
b._allocation = std::move(pair.second);
|
||||
b._size = size;
|
||||
return b;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, JetBrains s.r.o.. 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.
|
||||
*/
|
||||
|
||||
#ifndef VKMemory_h_Included
|
||||
#define VKMemory_h_Included
|
||||
|
||||
#define VK_NO_PROTOTYPES
|
||||
#define VULKAN_HPP_NO_DEFAULT_DISPATCHER
|
||||
#include <vulkan/vulkan_raii.hpp>
|
||||
#include <vk_mem_alloc.hpp>
|
||||
|
||||
class VKBuffer {
|
||||
friend class VKMemory;
|
||||
vma::UniqueBuffer _buffer;
|
||||
vma::UniqueAllocation _allocation;
|
||||
vma::AllocationInfo _allocationInfo;
|
||||
uint32_t _size = 0;
|
||||
uint32_t _position = 0;
|
||||
public:
|
||||
VKBuffer(nullptr_t) {}
|
||||
vk::Buffer operator*() const {
|
||||
return *_buffer;
|
||||
}
|
||||
uint32_t size() const {
|
||||
return _size;
|
||||
}
|
||||
uint32_t& position() {
|
||||
return _position;
|
||||
}
|
||||
void* data() {
|
||||
return _allocationInfo.pMappedData;
|
||||
}
|
||||
};
|
||||
|
||||
class VKMemory : vma::Allocator {
|
||||
vma::UniqueAllocator _allocator;
|
||||
|
||||
public:
|
||||
void init(vk::Instance instance, const vk::raii::PhysicalDevice& physicalDevice,
|
||||
const vk::raii::Device& device, uint32_t apiVersion, bool extMemoryBudget);
|
||||
|
||||
VKBuffer allocateBuffer(uint32_t size, vk::BufferUsageFlags usage,
|
||||
vma::AllocationCreateFlags flags, vma::MemoryUsage memoryUsage);
|
||||
};
|
||||
|
||||
#endif //VKMemory_h_Included
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, JetBrains s.r.o.. 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 "VKPipeline.h"
|
||||
|
||||
void VKPipelines::init(const vk::raii::Device& device, bool dynamicRendering) {
|
||||
shaders.init(device);
|
||||
|
||||
vk::Format format = vk::Format::eB8G8R8A8Unorm; // TODO
|
||||
|
||||
if (!dynamicRendering) {
|
||||
vk::AttachmentDescription attachmentDescription {
|
||||
/*flags*/ {},
|
||||
/*format*/ format,
|
||||
/*samples*/ vk::SampleCountFlagBits::e1,
|
||||
/*loadOp*/ vk::AttachmentLoadOp::eLoad,
|
||||
/*storeOp*/ vk::AttachmentStoreOp::eStore,
|
||||
/*stencilLoadOp*/ vk::AttachmentLoadOp::eDontCare,
|
||||
/*stencilStoreOp*/ vk::AttachmentStoreOp::eDontCare,
|
||||
/*initialLayout*/ vk::ImageLayout::eColorAttachmentOptimal,
|
||||
/*finalLayout*/ vk::ImageLayout::eColorAttachmentOptimal
|
||||
};
|
||||
vk::AttachmentReference attachmentReference { 0, vk::ImageLayout::eColorAttachmentOptimal };
|
||||
vk::SubpassDescription subpassDescription {
|
||||
/*flags*/ {},
|
||||
/*pipelineBindPoint*/ vk::PipelineBindPoint::eGraphics,
|
||||
/*inputAttachmentCount*/ 0,
|
||||
/*pInputAttachments*/ nullptr,
|
||||
/*colorAttachmentCount*/ 1,
|
||||
/*pColorAttachments*/ &attachmentReference,
|
||||
/*pResolveAttachments*/ nullptr,
|
||||
/*pDepthStencilAttachment*/ nullptr,
|
||||
/*preserveAttachmentCount*/ 0,
|
||||
/*pPreserveAttachments*/ nullptr,
|
||||
};
|
||||
// We don't know in advance, which operations to synchronize
|
||||
// with before and after the render pass, so do a full sync.
|
||||
std::array<vk::SubpassDependency, 2> subpassDependencies {vk::SubpassDependency{
|
||||
/*srcSubpass*/ VK_SUBPASS_EXTERNAL,
|
||||
/*dstSubpass*/ 0,
|
||||
/*srcStageMask*/ vk::PipelineStageFlagBits::eBottomOfPipe,
|
||||
/*dstStageMask*/ vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
/*srcAccessMask*/ {},
|
||||
/*dstAccessMask*/ vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
/*dependencyFlags*/ {},
|
||||
}, vk::SubpassDependency{
|
||||
/*srcSubpass*/ 0,
|
||||
/*dstSubpass*/ VK_SUBPASS_EXTERNAL,
|
||||
/*srcStageMask*/ vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
/*dstStageMask*/ vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
/*srcAccessMask*/ vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
/*dstAccessMask*/ {},
|
||||
/*dependencyFlags*/ {},
|
||||
}};
|
||||
renderPass = device.createRenderPass(vk::RenderPassCreateInfo{
|
||||
/*flags*/ {},
|
||||
/*pAttachments*/ attachmentDescription,
|
||||
/*pSubpasses*/ subpassDescription,
|
||||
/*pDependencies*/ subpassDependencies
|
||||
});
|
||||
}
|
||||
|
||||
vk::PushConstantRange pushConstantRange {vk::ShaderStageFlagBits::eVertex, 0, sizeof(float) * 2};
|
||||
testLayout = device.createPipelineLayout(vk::PipelineLayoutCreateInfo {{}, {}, pushConstantRange});
|
||||
|
||||
std::array<vk::PipelineShaderStageCreateInfo, 2> testStages {shaders.test_vert.stage(), shaders.test_frag.stage()};
|
||||
vk::VertexInputBindingDescription vertexInputBindingDescription {0, 8, vk::VertexInputRate::eVertex};
|
||||
vk::VertexInputAttributeDescription vertexInputAttributeDescription {0, 0, vk::Format::eR32G32Sfloat, 0};
|
||||
vk::PipelineVertexInputStateCreateInfo vertexInputStateCreateInfo {{}, vertexInputBindingDescription, vertexInputAttributeDescription};
|
||||
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo {{}, vk::PrimitiveTopology::eTriangleFan, false};
|
||||
vk::Viewport viewport;
|
||||
vk::Rect2D scissor;
|
||||
vk::PipelineViewportStateCreateInfo viewportStateCreateInfo {{}, viewport, scissor};
|
||||
vk::PipelineRasterizationStateCreateInfo rasterizationStateCreateInfo {
|
||||
{}, false, false, vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eClockwise, false, 0, 0, 0, 1
|
||||
};
|
||||
vk::PipelineMultisampleStateCreateInfo multisampleStateCreateInfo {};
|
||||
vk::PipelineColorBlendAttachmentState colorBlendAttachmentState {false}; // TODO No blending yet
|
||||
colorBlendAttachmentState.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
||||
vk::PipelineColorBlendStateCreateInfo colorBlendStateCreateInfo {{}, false, vk::LogicOp::eXor, colorBlendAttachmentState};
|
||||
std::array<vk::DynamicState, 2> dynamicStates {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
|
||||
vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo {{}, dynamicStates};
|
||||
vk::PipelineRenderingCreateInfoKHR renderingCreateInfo {0, format};
|
||||
auto pipelines = device.createGraphicsPipelines(nullptr, {
|
||||
vk::GraphicsPipelineCreateInfo {
|
||||
{}, testStages,
|
||||
&vertexInputStateCreateInfo,
|
||||
&inputAssemblyStateCreateInfo,
|
||||
nullptr,
|
||||
&viewportStateCreateInfo,
|
||||
&rasterizationStateCreateInfo,
|
||||
&multisampleStateCreateInfo,
|
||||
nullptr,
|
||||
&colorBlendStateCreateInfo,
|
||||
&dynamicStateCreateInfo,
|
||||
*testLayout,
|
||||
*renderPass, 0, nullptr, 0,
|
||||
dynamicRendering ? &renderingCreateInfo : nullptr
|
||||
}
|
||||
});
|
||||
// TODO pipeline cache
|
||||
test = std::move(pipelines[0]);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, JetBrains s.r.o.. 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
|
||||
@@ -24,31 +24,19 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.awt.wl;
|
||||
#ifndef VKPipeline_h_Included
|
||||
#define VKPipeline_h_Included
|
||||
|
||||
import java.awt.AWTError;
|
||||
#include "VKShader.h"
|
||||
|
||||
public final class WLDisplay {
|
||||
private static final WLDisplay INSTANCE = new WLDisplay();
|
||||
struct VKPipelines {
|
||||
VKShaders shaders;
|
||||
// TODO we need a pool of pipelines and (optionally) render passes for different formats.
|
||||
vk::raii::RenderPass renderPass = nullptr; // Render pass is only needed if dynamic rendering is off.
|
||||
vk::raii::PipelineLayout testLayout = nullptr;
|
||||
vk::raii::Pipeline test = nullptr;
|
||||
|
||||
private final long displayPtr;
|
||||
void init(const vk::raii::Device& device, bool dynamicRendering);
|
||||
};
|
||||
|
||||
private WLDisplay() {
|
||||
long p = connect(); // NB: we never disconnect
|
||||
if (p == 0) {
|
||||
throw new AWTError("Can't connect to the Wayland server");
|
||||
}
|
||||
|
||||
displayPtr = p;
|
||||
}
|
||||
|
||||
public long getDisplayPtr() {
|
||||
return displayPtr;
|
||||
}
|
||||
|
||||
public static WLDisplay getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private native long connect();
|
||||
}
|
||||
#endif //VKPipeline_h_Included
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user