mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2026-01-24 09:20:50 +01:00
Compare commits
13 Commits
jb21.0.6-b
...
bookmark2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
860020a1fe | ||
|
|
ae2e0a1773 | ||
|
|
ff8e67c141 | ||
|
|
c053250dbd | ||
|
|
5a1f42627b | ||
|
|
a8500e1357 | ||
|
|
d7931c14b8 | ||
|
|
07a86f7646 | ||
|
|
878e4e61ed | ||
|
|
289a58f1f2 | ||
|
|
1372f86a0c | ||
|
|
ee9eab7621 | ||
|
|
d5db58c8e3 |
@@ -7,7 +7,7 @@
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -43,7 +43,9 @@ find "$APPLICATION_PATH" -name '*.cstemp' -exec rm '{}' \;
|
||||
log "Signing libraries and executables..."
|
||||
# -perm +111 searches for executables
|
||||
for f in \
|
||||
"Contents/Home/lib" "Contents/MacOS"; do
|
||||
"Contents/Home/lib" "Contents/MacOS" \
|
||||
"Contents/Home/Frameworks" \
|
||||
"Contents/Frameworks"; do
|
||||
if [ -d "$APPLICATION_PATH/$f" ]; then
|
||||
find "$APPLICATION_PATH/$f" \
|
||||
-type f \( -name "*.jnilib" -o -name "*.dylib" -o -name "*.so" -o -name "*.tbd" -o -name "*.node" -o -perm +111 \) \
|
||||
@@ -51,13 +53,6 @@ for f in \
|
||||
fi
|
||||
done
|
||||
|
||||
log "Signing JCEF libraries and executables..."
|
||||
if [ -d "$APPLICATION_PATH/Contents/Frameworks" ]; then
|
||||
find "$APPLICATION_PATH/Contents/Frameworks" \
|
||||
-type f \( -name "*.dylib" -o -perm +111 \) \
|
||||
-exec sh -c '"$1" --timestamp -v -s "$2" --options=runtime --force --entitlements "$3" "$4" || exit 1' sh "$SIGN_UTILITY" "$JB_DEVELOPER_CERT" "$SCRIPT_DIR/entitlements_jcef.xml" {} \;
|
||||
fi
|
||||
|
||||
log "Signing jmod files"
|
||||
JMODS_DIR="$APPLICATION_PATH/Contents/Home/jmods"
|
||||
JMOD_EXE="$BOOT_JDK/bin/jmod"
|
||||
@@ -181,7 +176,7 @@ done
|
||||
log "Signing whole frameworks..."
|
||||
# shellcheck disable=SC2043
|
||||
if [ "$JB_SIGN" = true ]; then for f in \
|
||||
"Contents/Frameworks/cef_server.app/Contents/Frameworks" "Contents/Frameworks"; do
|
||||
"Contents/Frameworks/cef_server.app/Contents/Frameworks" "Contents/Home/Frameworks" "Contents/Frameworks"; do
|
||||
if [ -d "$APPLICATION_PATH/$f" ]; then
|
||||
find "$APPLICATION_PATH/$f" \( -name '*.framework' -o -name '*.app' \) -maxdepth 1 | while read -r line
|
||||
do
|
||||
@@ -190,7 +185,7 @@ if [ "$JB_SIGN" = true ]; then for f in \
|
||||
"$SIGN_UTILITY" --timestamp \
|
||||
-v -s "$JB_DEVELOPER_CERT" --options=runtime \
|
||||
--force \
|
||||
--entitlements "$SCRIPT_DIR/entitlements_jcef.xml" tmp-to-sign.tar.gz || exit 1
|
||||
--entitlements "$SCRIPT_DIR/entitlements.xml" tmp-to-sign.tar.gz || exit 1
|
||||
rm -rf "$line"
|
||||
tar -xzf tmp-to-sign.tar.gz --directory "$(dirname "$line")"
|
||||
rm -f tmp-to-sign.tar.gz
|
||||
@@ -199,14 +194,16 @@ if [ "$JB_SIGN" = true ]; then for f in \
|
||||
done; fi
|
||||
|
||||
log "Checking framework signatures..."
|
||||
|
||||
if [ -d "$APPLICATION_PATH/Contents/Frameworks" ]; then
|
||||
find "$APPLICATION_PATH/Contents/Frameworks" -name '*.framework' -maxdepth 1 | while read -r line
|
||||
do
|
||||
log "Checking '$line':"
|
||||
codesign --verify --deep --strict --verbose=4 "$line"
|
||||
done
|
||||
fi
|
||||
for f in \
|
||||
"Contents/Home/Frameworks" "Contents/Frameworks"; do
|
||||
if [ -d "$APPLICATION_PATH/$f" ]; then
|
||||
find "$APPLICATION_PATH/$f" -name '*.framework' -maxdepth 1 | while read -r line
|
||||
do
|
||||
log "Checking '$line':"
|
||||
codesign --verify --deep --strict --verbose=4 "$line"
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
log "Signing whole app..."
|
||||
if [ "$JB_SIGN" = true ]; then
|
||||
|
||||
@@ -127,7 +127,7 @@ AC_DEFUN_ONCE([LIB_SETUP_WAYLAND],
|
||||
VULKAN_FOUND=no
|
||||
|
||||
if test "x${with_vulkan_include}" != x; then
|
||||
AC_MSG_CHECKING([for ${with_vulkan_include}/vulkan/vulkan.h])
|
||||
AC_MSG_CHECKING([for vulkan.h])
|
||||
if test -s "${with_vulkan_include}/vulkan/vulkan.h"; then
|
||||
VULKAN_FOUND=yes
|
||||
VULKAN_FLAGS="-DVK_USE_PLATFORM_WAYLAND_KHR -I${with_vulkan_include} -DVULKAN_ENABLED"
|
||||
@@ -138,15 +138,14 @@ AC_DEFUN_ONCE([LIB_SETUP_WAYLAND],
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "x$VULKAN_FOUND" = xno && test "x${VULKAN_SDK}" != x; then
|
||||
AC_MSG_CHECKING([for ${VULKAN_SDK}/include/vulkan/vulkan.h])
|
||||
if test -s "${VULKAN_SDK}/include/vulkan/vulkan.h"; then
|
||||
VULKAN_FOUND=yes
|
||||
VULKAN_FLAGS="-DVK_USE_PLATFORM_WAYLAND_KHR -I${VULKAN_SDK}/include -DVULKAN_ENABLED"
|
||||
AC_MSG_RESULT([yes])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
fi
|
||||
if test "x$VULKAN_FOUND" = xno; then
|
||||
# Check vulkan sdk location
|
||||
AC_CHECK_HEADERS([$VULKAN_SDK/include/vulkan/vulkan.h],
|
||||
[ VULKAN_FOUND=yes
|
||||
VULKAN_FLAGS="-DVK_USE_PLATFORM_WAYLAND_KHR -I${VULKAN_SDK}/include -DVULKAN_ENABLED"
|
||||
],
|
||||
[ VULKAN_FOUND=no; break ]
|
||||
)
|
||||
fi
|
||||
|
||||
if test "x$VULKAN_FOUND" = xno; then
|
||||
|
||||
@@ -124,14 +124,11 @@ ifeq ($(call isTargetOs, macosx), true)
|
||||
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeJniInvocationTest := -ljli
|
||||
BUILD_JDK_JTREG_LIBRARIES_LIBS_libTestDynamicStore := \
|
||||
-framework Cocoa -framework SystemConfiguration
|
||||
BUILD_JDK_JTREG_LIBRARIES_LIBS_libSharedTexturesTest := \
|
||||
-framework Cocoa -framework Metal
|
||||
else
|
||||
BUILD_JDK_JTREG_EXCLUDE += libTestMainKeyWindow.m
|
||||
BUILD_JDK_JTREG_EXCLUDE += libTestDynamicStore.m
|
||||
BUILD_JDK_JTREG_EXCLUDE += exeJniInvocationTest.c
|
||||
BUILD_JDK_JTREG_EXCLUDE += exeLibraryCache.c
|
||||
BUILD_JDK_JTREG_EXCLUDE += libSharedTexturesTest.m
|
||||
endif
|
||||
|
||||
ifeq ($(OPENJDK_TARGET_OS), windows)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2021, Red Hat Inc. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
@@ -165,13 +165,7 @@ class FloatRegister {
|
||||
max_slots_per_register = 4,
|
||||
save_slots_per_register = 2,
|
||||
slots_per_neon_register = 4,
|
||||
extra_save_slots_per_neon_register = slots_per_neon_register - save_slots_per_register,
|
||||
neon_vl = 16,
|
||||
// VLmax: The maximum sve vector length is determined by the hardware
|
||||
// sve_vl_min <= VLmax <= sve_vl_max.
|
||||
sve_vl_min = 16,
|
||||
// Maximum supported vector length across all CPUs
|
||||
sve_vl_max = 256
|
||||
extra_save_slots_per_neon_register = slots_per_neon_register - save_slots_per_register
|
||||
};
|
||||
|
||||
class FloatRegisterImpl: public AbstractRegisterImpl {
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "pauth_aarch64.hpp"
|
||||
#include "register_aarch64.hpp"
|
||||
#include "runtime/arguments.hpp"
|
||||
#include "runtime/globals_extension.hpp"
|
||||
#include "runtime/java.hpp"
|
||||
@@ -441,19 +440,7 @@ void VM_Version::initialize() {
|
||||
}
|
||||
|
||||
if (UseSVE > 0) {
|
||||
int vl = get_current_sve_vector_length();
|
||||
if (vl < 0) {
|
||||
warning("Unable to get SVE vector length on this system. "
|
||||
"Disabling SVE. Specify -XX:UseSVE=0 to shun this warning.");
|
||||
FLAG_SET_DEFAULT(UseSVE, 0);
|
||||
} else if ((vl == 0) || ((vl % FloatRegister::sve_vl_min) != 0) || !is_power_of_2(vl)) {
|
||||
warning("Detected SVE vector length (%d) should be a power of two and a multiple of %d. "
|
||||
"Disabling SVE. Specify -XX:UseSVE=0 to shun this warning.",
|
||||
vl, FloatRegister::sve_vl_min);
|
||||
FLAG_SET_DEFAULT(UseSVE, 0);
|
||||
} else {
|
||||
_initial_sve_vector_length = vl;
|
||||
}
|
||||
_initial_sve_vector_length = get_current_sve_vector_length();
|
||||
}
|
||||
|
||||
// This machine allows unaligned memory accesses
|
||||
|
||||
@@ -375,8 +375,6 @@ inline int Backtrace::get_line_number(Method* method, int bci) {
|
||||
// "no LineNumberTable". JDK tests for -2.
|
||||
line_number = -2;
|
||||
} else {
|
||||
// (DCEVM): Line numbers from the newest version must be used
|
||||
method = method->newest_version();
|
||||
// Returns -1 if no LineNumberTable, and otherwise actual line number
|
||||
line_number = method->line_number_from_bci(bci);
|
||||
}
|
||||
|
||||
@@ -1052,6 +1052,7 @@ void InstanceKlass::clean_initialization_error_table() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
assert_locked_or_safepoint(ClassInitError_lock);
|
||||
InitErrorTableCleaner cleaner;
|
||||
if (_initialization_error_table != nullptr) {
|
||||
|
||||
@@ -3266,28 +3266,23 @@ void TypeRawPtr::dump2( Dict &d, uint depth, outputStream *st ) const {
|
||||
// Convenience common pre-built type.
|
||||
const TypeOopPtr *TypeOopPtr::BOTTOM;
|
||||
|
||||
TypeInterfaces::TypeInterfaces(ciInstanceKlass** interfaces_base, int nb_interfaces)
|
||||
: Type(Interfaces), _interfaces(interfaces_base, nb_interfaces),
|
||||
TypeInterfaces::TypeInterfaces()
|
||||
: Type(Interfaces), _list(Compile::current()->type_arena(), 0, 0, nullptr),
|
||||
_hash(0), _exact_klass(nullptr) {
|
||||
_interfaces.sort(compare);
|
||||
DEBUG_ONLY(_initialized = true);
|
||||
}
|
||||
|
||||
TypeInterfaces::TypeInterfaces(GrowableArray<ciInstanceKlass*>* interfaces)
|
||||
: Type(Interfaces), _list(Compile::current()->type_arena(), interfaces->length(), 0, nullptr),
|
||||
_hash(0), _exact_klass(nullptr) {
|
||||
for (int i = 0; i < interfaces->length(); i++) {
|
||||
add(interfaces->at(i));
|
||||
}
|
||||
initialize();
|
||||
}
|
||||
|
||||
const TypeInterfaces* TypeInterfaces::make(GrowableArray<ciInstanceKlass*>* interfaces) {
|
||||
// hashcons() can only delete the last thing that was allocated: to
|
||||
// make sure all memory for the newly created TypeInterfaces can be
|
||||
// freed if an identical one exists, allocate space for the array of
|
||||
// interfaces right after the TypeInterfaces object so that they
|
||||
// form a contiguous piece of memory.
|
||||
int nb_interfaces = interfaces == nullptr ? 0 : interfaces->length();
|
||||
size_t total_size = sizeof(TypeInterfaces) + nb_interfaces * sizeof(ciInstanceKlass*);
|
||||
|
||||
void* allocated_mem = operator new(total_size);
|
||||
ciInstanceKlass** interfaces_base = (ciInstanceKlass**)((char*)allocated_mem + sizeof(TypeInterfaces));
|
||||
for (int i = 0; i < nb_interfaces; ++i) {
|
||||
interfaces_base[i] = interfaces->at(i);
|
||||
}
|
||||
TypeInterfaces* result = ::new (allocated_mem) TypeInterfaces(interfaces_base, nb_interfaces);
|
||||
TypeInterfaces* result = (interfaces == nullptr) ? new TypeInterfaces() : new TypeInterfaces(interfaces);
|
||||
return (const TypeInterfaces*)result->hashcons();
|
||||
}
|
||||
|
||||
@@ -3306,18 +3301,20 @@ int TypeInterfaces::compare(ciInstanceKlass* const& k1, ciInstanceKlass* const&
|
||||
return 0;
|
||||
}
|
||||
|
||||
int TypeInterfaces::compare(ciInstanceKlass** k1, ciInstanceKlass** k2) {
|
||||
return compare(*k1, *k2);
|
||||
void TypeInterfaces::add(ciInstanceKlass* interface) {
|
||||
assert(interface->is_interface(), "for interfaces only");
|
||||
_list.insert_sorted<compare>(interface);
|
||||
verify();
|
||||
}
|
||||
|
||||
bool TypeInterfaces::eq(const Type* t) const {
|
||||
const TypeInterfaces* other = (const TypeInterfaces*)t;
|
||||
if (_interfaces.length() != other->_interfaces.length()) {
|
||||
if (_list.length() != other->_list.length()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < _interfaces.length(); i++) {
|
||||
ciKlass* k1 = _interfaces.at(i);
|
||||
ciKlass* k2 = other->_interfaces.at(i);
|
||||
for (int i = 0; i < _list.length(); i++) {
|
||||
ciKlass* k1 = _list.at(i);
|
||||
ciKlass* k2 = other->_list.at(i);
|
||||
if (!k1->equals(k2)) {
|
||||
return false;
|
||||
}
|
||||
@@ -3328,12 +3325,12 @@ bool TypeInterfaces::eq(const Type* t) const {
|
||||
bool TypeInterfaces::eq(ciInstanceKlass* k) const {
|
||||
assert(k->is_loaded(), "should be loaded");
|
||||
GrowableArray<ciInstanceKlass *>* interfaces = k->transitive_interfaces();
|
||||
if (_interfaces.length() != interfaces->length()) {
|
||||
if (_list.length() != interfaces->length()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < interfaces->length(); i++) {
|
||||
bool found = false;
|
||||
_interfaces.find_sorted<ciInstanceKlass*, compare>(interfaces->at(i), found);
|
||||
_list.find_sorted<ciInstanceKlass*, compare>(interfaces->at(i), found);
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
@@ -3353,8 +3350,8 @@ const Type* TypeInterfaces::xdual() const {
|
||||
|
||||
void TypeInterfaces::compute_hash() {
|
||||
uint hash = 0;
|
||||
for (int i = 0; i < _interfaces.length(); i++) {
|
||||
ciKlass* k = _interfaces.at(i);
|
||||
for (int i = 0; i < _list.length(); i++) {
|
||||
ciKlass* k = _list.at(i);
|
||||
hash += k->hash();
|
||||
}
|
||||
_hash = hash;
|
||||
@@ -3365,13 +3362,13 @@ static int compare_interfaces(ciInstanceKlass** k1, ciInstanceKlass** k2) {
|
||||
}
|
||||
|
||||
void TypeInterfaces::dump(outputStream* st) const {
|
||||
if (_interfaces.length() == 0) {
|
||||
if (_list.length() == 0) {
|
||||
return;
|
||||
}
|
||||
ResourceMark rm;
|
||||
st->print(" (");
|
||||
GrowableArray<ciInstanceKlass*> interfaces;
|
||||
interfaces.appendAll(&_interfaces);
|
||||
interfaces.appendAll(&_list);
|
||||
// Sort the interfaces so they are listed in the same order from one run to the other of the same compilation
|
||||
interfaces.sort(compare_interfaces);
|
||||
for (int i = 0; i < interfaces.length(); i++) {
|
||||
@@ -3386,9 +3383,9 @@ void TypeInterfaces::dump(outputStream* st) const {
|
||||
|
||||
#ifdef ASSERT
|
||||
void TypeInterfaces::verify() const {
|
||||
for (int i = 1; i < _interfaces.length(); i++) {
|
||||
ciInstanceKlass* k1 = _interfaces.at(i-1);
|
||||
ciInstanceKlass* k2 = _interfaces.at(i);
|
||||
for (int i = 1; i < _list.length(); i++) {
|
||||
ciInstanceKlass* k1 = _list.at(i-1);
|
||||
ciInstanceKlass* k2 = _list.at(i);
|
||||
assert(compare(k2, k1) > 0, "should be ordered");
|
||||
assert(k1 != k2, "no duplicate");
|
||||
}
|
||||
@@ -3399,23 +3396,23 @@ const TypeInterfaces* TypeInterfaces::union_with(const TypeInterfaces* other) co
|
||||
GrowableArray<ciInstanceKlass*> result_list;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
while (i < _interfaces.length() || j < other->_interfaces.length()) {
|
||||
while (i < _interfaces.length() &&
|
||||
(j >= other->_interfaces.length() ||
|
||||
compare(_interfaces.at(i), other->_interfaces.at(j)) < 0)) {
|
||||
result_list.push(_interfaces.at(i));
|
||||
while (i < _list.length() || j < other->_list.length()) {
|
||||
while (i < _list.length() &&
|
||||
(j >= other->_list.length() ||
|
||||
compare(_list.at(i), other->_list.at(j)) < 0)) {
|
||||
result_list.push(_list.at(i));
|
||||
i++;
|
||||
}
|
||||
while (j < other->_interfaces.length() &&
|
||||
(i >= _interfaces.length() ||
|
||||
compare(other->_interfaces.at(j), _interfaces.at(i)) < 0)) {
|
||||
result_list.push(other->_interfaces.at(j));
|
||||
while (j < other->_list.length() &&
|
||||
(i >= _list.length() ||
|
||||
compare(other->_list.at(j), _list.at(i)) < 0)) {
|
||||
result_list.push(other->_list.at(j));
|
||||
j++;
|
||||
}
|
||||
if (i < _interfaces.length() &&
|
||||
j < other->_interfaces.length() &&
|
||||
_interfaces.at(i) == other->_interfaces.at(j)) {
|
||||
result_list.push(_interfaces.at(i));
|
||||
if (i < _list.length() &&
|
||||
j < other->_list.length() &&
|
||||
_list.at(i) == other->_list.at(j)) {
|
||||
result_list.push(_list.at(i));
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
@@ -3423,14 +3420,14 @@ const TypeInterfaces* TypeInterfaces::union_with(const TypeInterfaces* other) co
|
||||
const TypeInterfaces* result = TypeInterfaces::make(&result_list);
|
||||
#ifdef ASSERT
|
||||
result->verify();
|
||||
for (int i = 0; i < _interfaces.length(); i++) {
|
||||
assert(result->_interfaces.contains(_interfaces.at(i)), "missing");
|
||||
for (int i = 0; i < _list.length(); i++) {
|
||||
assert(result->_list.contains(_list.at(i)), "missing");
|
||||
}
|
||||
for (int i = 0; i < other->_interfaces.length(); i++) {
|
||||
assert(result->_interfaces.contains(other->_interfaces.at(i)), "missing");
|
||||
for (int i = 0; i < other->_list.length(); i++) {
|
||||
assert(result->_list.contains(other->_list.at(i)), "missing");
|
||||
}
|
||||
for (int i = 0; i < result->_interfaces.length(); i++) {
|
||||
assert(_interfaces.contains(result->_interfaces.at(i)) || other->_interfaces.contains(result->_interfaces.at(i)), "missing");
|
||||
for (int i = 0; i < result->_list.length(); i++) {
|
||||
assert(_list.contains(result->_list.at(i)) || other->_list.contains(result->_list.at(i)), "missing");
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
@@ -3440,21 +3437,21 @@ const TypeInterfaces* TypeInterfaces::intersection_with(const TypeInterfaces* ot
|
||||
GrowableArray<ciInstanceKlass*> result_list;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
while (i < _interfaces.length() || j < other->_interfaces.length()) {
|
||||
while (i < _interfaces.length() &&
|
||||
(j >= other->_interfaces.length() ||
|
||||
compare(_interfaces.at(i), other->_interfaces.at(j)) < 0)) {
|
||||
while (i < _list.length() || j < other->_list.length()) {
|
||||
while (i < _list.length() &&
|
||||
(j >= other->_list.length() ||
|
||||
compare(_list.at(i), other->_list.at(j)) < 0)) {
|
||||
i++;
|
||||
}
|
||||
while (j < other->_interfaces.length() &&
|
||||
(i >= _interfaces.length() ||
|
||||
compare(other->_interfaces.at(j), _interfaces.at(i)) < 0)) {
|
||||
while (j < other->_list.length() &&
|
||||
(i >= _list.length() ||
|
||||
compare(other->_list.at(j), _list.at(i)) < 0)) {
|
||||
j++;
|
||||
}
|
||||
if (i < _interfaces.length() &&
|
||||
j < other->_interfaces.length() &&
|
||||
_interfaces.at(i) == other->_interfaces.at(j)) {
|
||||
result_list.push(_interfaces.at(i));
|
||||
if (i < _list.length() &&
|
||||
j < other->_list.length() &&
|
||||
_list.at(i) == other->_list.at(j)) {
|
||||
result_list.push(_list.at(i));
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
@@ -3462,14 +3459,14 @@ const TypeInterfaces* TypeInterfaces::intersection_with(const TypeInterfaces* ot
|
||||
const TypeInterfaces* result = TypeInterfaces::make(&result_list);
|
||||
#ifdef ASSERT
|
||||
result->verify();
|
||||
for (int i = 0; i < _interfaces.length(); i++) {
|
||||
assert(!other->_interfaces.contains(_interfaces.at(i)) || result->_interfaces.contains(_interfaces.at(i)), "missing");
|
||||
for (int i = 0; i < _list.length(); i++) {
|
||||
assert(!other->_list.contains(_list.at(i)) || result->_list.contains(_list.at(i)), "missing");
|
||||
}
|
||||
for (int i = 0; i < other->_interfaces.length(); i++) {
|
||||
assert(!_interfaces.contains(other->_interfaces.at(i)) || result->_interfaces.contains(other->_interfaces.at(i)), "missing");
|
||||
for (int i = 0; i < other->_list.length(); i++) {
|
||||
assert(!_list.contains(other->_list.at(i)) || result->_list.contains(other->_list.at(i)), "missing");
|
||||
}
|
||||
for (int i = 0; i < result->_interfaces.length(); i++) {
|
||||
assert(_interfaces.contains(result->_interfaces.at(i)) && other->_interfaces.contains(result->_interfaces.at(i)), "missing");
|
||||
for (int i = 0; i < result->_list.length(); i++) {
|
||||
assert(_list.contains(result->_list.at(i)) && other->_list.contains(result->_list.at(i)), "missing");
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
@@ -3482,13 +3479,13 @@ ciInstanceKlass* TypeInterfaces::exact_klass() const {
|
||||
}
|
||||
|
||||
void TypeInterfaces::compute_exact_klass() {
|
||||
if (_interfaces.length() == 0) {
|
||||
if (_list.length() == 0) {
|
||||
_exact_klass = nullptr;
|
||||
return;
|
||||
}
|
||||
ciInstanceKlass* res = nullptr;
|
||||
for (int i = 0; i < _interfaces.length(); i++) {
|
||||
ciInstanceKlass* interface = _interfaces.at(i);
|
||||
for (int i = 0; i < _list.length(); i++) {
|
||||
ciInstanceKlass* interface = _list.at(i);
|
||||
if (eq(interface)) {
|
||||
assert(res == nullptr, "");
|
||||
res = interface;
|
||||
@@ -3499,8 +3496,8 @@ void TypeInterfaces::compute_exact_klass() {
|
||||
|
||||
#ifdef ASSERT
|
||||
void TypeInterfaces::verify_is_loaded() const {
|
||||
for (int i = 0; i < _interfaces.length(); i++) {
|
||||
ciKlass* interface = _interfaces.at(i);
|
||||
for (int i = 0; i < _list.length(); i++) {
|
||||
ciKlass* interface = _list.at(i);
|
||||
assert(interface->is_loaded(), "Interface not loaded");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,18 +877,19 @@ public:
|
||||
// Set of implemented interfaces. Referenced from TypeOopPtr and TypeKlassPtr.
|
||||
class TypeInterfaces : public Type {
|
||||
private:
|
||||
GrowableArrayFromArray<ciInstanceKlass*> _interfaces;
|
||||
GrowableArray<ciInstanceKlass*> _list;
|
||||
uint _hash;
|
||||
ciInstanceKlass* _exact_klass;
|
||||
DEBUG_ONLY(bool _initialized;)
|
||||
|
||||
void initialize();
|
||||
|
||||
void add(ciInstanceKlass* interface);
|
||||
void verify() const NOT_DEBUG_RETURN;
|
||||
void compute_hash();
|
||||
void compute_exact_klass();
|
||||
|
||||
TypeInterfaces(ciInstanceKlass** interfaces_base, int nb_interfaces);
|
||||
TypeInterfaces();
|
||||
TypeInterfaces(GrowableArray<ciInstanceKlass*>* interfaces);
|
||||
|
||||
NONCOPYABLE(TypeInterfaces);
|
||||
public:
|
||||
@@ -903,13 +904,12 @@ public:
|
||||
bool contains(const TypeInterfaces* other) const {
|
||||
return intersection_with(other)->eq(other);
|
||||
}
|
||||
bool empty() const { return _interfaces.length() == 0; }
|
||||
bool empty() const { return _list.length() == 0; }
|
||||
|
||||
ciInstanceKlass* exact_klass() const;
|
||||
void verify_is_loaded() const NOT_DEBUG_RETURN;
|
||||
|
||||
static int compare(ciInstanceKlass* const& k1, ciInstanceKlass* const& k2);
|
||||
static int compare(ciInstanceKlass** k1, ciInstanceKlass** k2);
|
||||
|
||||
const Type* xmeet(const Type* t) const;
|
||||
|
||||
|
||||
86
src/java.base/share/classes/com/sun/IoOverNio.java
Normal file
86
src/java.base/share/classes/com/sun/IoOverNio.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun; // TODO better package
|
||||
|
||||
import jdk.internal.misc.VM;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
|
||||
public class IoOverNio {
|
||||
private IoOverNio() { }
|
||||
|
||||
public static final ThreadLocal<Boolean> ALLOW_IO_OVER_NIO = ThreadLocal.withInitial(() -> true);
|
||||
|
||||
public enum Debug {
|
||||
NO(false, false),
|
||||
ERROR(true, false),
|
||||
NO_ERROR(false, true),
|
||||
ALL(true, true);
|
||||
|
||||
private final boolean writeErrors;
|
||||
private final boolean writeTraces;
|
||||
|
||||
Debug(boolean writeErrors, boolean writeTraces) {
|
||||
this.writeErrors = writeErrors;
|
||||
this.writeTraces = writeTraces;
|
||||
}
|
||||
|
||||
private boolean mayWriteAnything() {
|
||||
return VM.isBooted() && IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
}
|
||||
|
||||
public boolean writeErrors() {
|
||||
return writeErrors && mayWriteAnything();
|
||||
}
|
||||
|
||||
public boolean writeTraces() {
|
||||
return writeTraces && mayWriteAnything();
|
||||
}
|
||||
}
|
||||
|
||||
public static final Debug DEBUG;
|
||||
|
||||
static {
|
||||
String value = GetPropertyAction.privilegedGetProperty("jbr.java.io.use.nio.debug");
|
||||
if (value == null) {
|
||||
DEBUG = Debug.NO;
|
||||
} else {
|
||||
switch (value) {
|
||||
case "error":
|
||||
DEBUG = Debug.ERROR;
|
||||
break;
|
||||
case "no_error":
|
||||
DEBUG = Debug.NO_ERROR;
|
||||
break;
|
||||
case "all":
|
||||
DEBUG = Debug.ALL;
|
||||
break;
|
||||
default:
|
||||
DEBUG = Debug.NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,10 @@ import java.nio.file.Path;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jdk.internal.util.StaticProperty;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
|
||||
/**
|
||||
* An abstract representation of file and directory pathnames.
|
||||
@@ -148,11 +151,23 @@ import jdk.internal.util.StaticProperty;
|
||||
public class File
|
||||
implements Serializable, Comparable<File>
|
||||
{
|
||||
static final Supplier<java.nio.file.FileSystem> acquireNioFs;
|
||||
|
||||
/**
|
||||
* The FileSystem object representing the platform's local file system.
|
||||
*/
|
||||
private static final FileSystem FS = DefaultFileSystem.getFileSystem();
|
||||
private static final FileSystem FS;
|
||||
|
||||
static {
|
||||
if (GetPropertyAction.privilegedGetBooleanProp("jbr.java.io.use.nio", true, null)) {
|
||||
IoOverNioFileSystem ioOverNio = new IoOverNioFileSystem(DefaultFileSystem.getFileSystem());
|
||||
FS = ioOverNio;
|
||||
acquireNioFs = ioOverNio::acquireNioFs;
|
||||
} else {
|
||||
FS = DefaultFileSystem.getFileSystem();
|
||||
acquireNioFs = () -> null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This abstract pathname's normalized pathname string. A normalized
|
||||
|
||||
@@ -25,12 +25,21 @@
|
||||
|
||||
package java.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.internal.misc.Blocker;
|
||||
|
||||
import jdk.internal.util.ArraysSupport;
|
||||
import sun.nio.ch.FileChannelImpl;
|
||||
|
||||
import static com.sun.IoOverNio.DEBUG;
|
||||
|
||||
/**
|
||||
* A {@code FileInputStream} obtains input bytes
|
||||
* from a file in a file system. What files
|
||||
@@ -75,6 +84,8 @@ public class FileInputStream extends InputStream
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
private final boolean useNio;
|
||||
|
||||
/**
|
||||
* Creates a {@code FileInputStream} by
|
||||
* opening a connection to an actual file,
|
||||
@@ -146,11 +157,46 @@ public class FileInputStream extends InputStream
|
||||
if (file.isInvalid()) {
|
||||
throw new FileNotFoundException("Invalid file path");
|
||||
}
|
||||
fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
|
||||
path = name;
|
||||
open(name);
|
||||
FileCleanable.register(fd); // open set the fd, register the cleanup
|
||||
java.nio.file.FileSystem nioFs = File.acquireNioFs.get();
|
||||
useNio = nioFs != null;
|
||||
if (useNio) {
|
||||
Path nioPath = nioFs.getPath(name);
|
||||
if (Files.isDirectory(nioPath)) {
|
||||
// Unfortunately, java.nio allows opening directories as file channels, and there's no way
|
||||
// to determine if an opened nio channel belongs to a directory.
|
||||
throw new FileNotFoundException(name + " (Is a directory)");
|
||||
}
|
||||
try {
|
||||
// NB: the channel will be closed in the close() method
|
||||
// TODO Handle UnsupportedOperationException from newFileChannel
|
||||
var ch = nioFs.provider().newFileChannel(nioPath, Set.of(StandardOpenOption.READ));
|
||||
channel = ch;
|
||||
if (ch instanceof FileChannelImpl fci) {
|
||||
fci.setUninterruptible();
|
||||
fd = fci.getFD(); // TODO: this is a temporary workaround
|
||||
fd.attach(this);
|
||||
FileCleanable.register(fd);
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't create a FileInputStream for %s with %s", file, nioFs), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw IoOverNioFileSystem.convertNioToIoExceptionInStreams(e);
|
||||
}
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
open(name);
|
||||
FileCleanable.register(fd); // open set the fd, register the cleanup
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a FileInputStream for %s%n", file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,6 +224,8 @@ public class FileInputStream extends InputStream
|
||||
* @see SecurityManager#checkRead(java.io.FileDescriptor)
|
||||
*/
|
||||
public FileInputStream(FileDescriptor fdObj) {
|
||||
useNio = false;
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
SecurityManager security = System.getSecurityManager();
|
||||
if (fdObj == null) {
|
||||
@@ -228,7 +276,7 @@ public class FileInputStream extends InputStream
|
||||
public int read() throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
return read0();
|
||||
return implRead();
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
@@ -236,6 +284,16 @@ public class FileInputStream extends InputStream
|
||||
|
||||
private native int read0() throws IOException;
|
||||
|
||||
private int implRead() throws IOException {
|
||||
if (useNio) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1);
|
||||
int nRead = getChannel().read(buffer);
|
||||
buffer.rewind();
|
||||
return nRead == 1 ? (buffer.get() & 0xFF) : -1;
|
||||
}
|
||||
return read0();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a subarray as a sequence of bytes.
|
||||
* @param b the data to be written
|
||||
@@ -260,12 +318,25 @@ public class FileInputStream extends InputStream
|
||||
public int read(byte[] b) throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
return readBytes(b, 0, b.length);
|
||||
return implRead(b);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private int implRead(byte[] b) throws IOException {
|
||||
if (useNio) {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b);
|
||||
return getChannel().read(buffer);
|
||||
} catch (OutOfMemoryError e) {
|
||||
// May fail to allocate direct buffer memory due to small -XX:MaxDirectMemorySize
|
||||
return readBytes(b, 0, b.length);
|
||||
}
|
||||
}
|
||||
return readBytes(b, 0, b.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to {@code len} bytes of data from this input stream
|
||||
* into an array of bytes. If {@code len} is not zero, the method
|
||||
@@ -284,12 +355,25 @@ public class FileInputStream extends InputStream
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
return readBytes(b, off, len);
|
||||
return implRead(b, off, len);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private int implRead(byte[] b, int off, int len) throws IOException {
|
||||
if (useNio) {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
|
||||
return getChannel().read(buffer);
|
||||
} catch (OutOfMemoryError e) {
|
||||
// May fail to allocate direct buffer memory due to small -XX:MaxDirectMemorySize
|
||||
return readBytes(b, off, len);
|
||||
}
|
||||
}
|
||||
return readBytes(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readAllBytes() throws IOException {
|
||||
long length = length();
|
||||
@@ -396,6 +480,9 @@ public class FileInputStream extends InputStream
|
||||
private long length() throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
if (useNio) {
|
||||
return getChannel().size();
|
||||
}
|
||||
return length0();
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
@@ -406,6 +493,9 @@ public class FileInputStream extends InputStream
|
||||
private long position() throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
if (useNio) {
|
||||
return getChannel().position();
|
||||
}
|
||||
return position0();
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
@@ -441,6 +531,12 @@ public class FileInputStream extends InputStream
|
||||
public long skip(long n) throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
if (useNio) {
|
||||
getChannel();
|
||||
long startPos = channel.position();
|
||||
channel.position(startPos + n);
|
||||
return channel.position() - startPos;
|
||||
}
|
||||
return skip0(n);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
@@ -470,7 +566,15 @@ public class FileInputStream extends InputStream
|
||||
public int available() throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
return available0();
|
||||
if (!useNio || path == null) {
|
||||
return available0();
|
||||
} else {
|
||||
FileChannel channel = getChannel();
|
||||
long size = channel.size();
|
||||
long pos = channel.position();
|
||||
long avail = size > pos ? (size - pos) : 0;
|
||||
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)avail;
|
||||
}
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
|
||||
@@ -25,11 +25,19 @@
|
||||
|
||||
package java.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.access.JavaIOFileDescriptorAccess;
|
||||
import jdk.internal.misc.Blocker;
|
||||
import sun.nio.ch.FileChannelImpl;
|
||||
import static com.sun.IoOverNio.DEBUG;
|
||||
|
||||
|
||||
/**
|
||||
@@ -89,6 +97,8 @@ public class FileOutputStream extends OutputStream
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
private final boolean useNio;
|
||||
|
||||
/**
|
||||
* Creates a file output stream to write to the file with the
|
||||
* specified name. A new {@code FileDescriptor} object is
|
||||
@@ -223,12 +233,48 @@ public class FileOutputStream extends OutputStream
|
||||
if (file.isInvalid()) {
|
||||
throw new FileNotFoundException("Invalid file path");
|
||||
}
|
||||
this.fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
this.path = name;
|
||||
|
||||
open(name, append);
|
||||
FileCleanable.register(fd); // open sets the fd, register the cleanup
|
||||
this.path = name;
|
||||
java.nio.file.FileSystem nioFs = File.acquireNioFs.get();
|
||||
useNio = nioFs != null;
|
||||
if (useNio) {
|
||||
Path nioPath = nioFs.getPath(name);
|
||||
if (Files.isDirectory(nioPath)) {
|
||||
throw new FileNotFoundException(name + " (Is a directory)");
|
||||
}
|
||||
try {
|
||||
// NB: the channel will be closed in the close() method
|
||||
// TODO Handle UOE
|
||||
var ch = FileSystems.getDefault().provider().newFileChannel(
|
||||
nioPath,
|
||||
append ? Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND)
|
||||
: Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
|
||||
channel = ch;
|
||||
if (ch instanceof FileChannelImpl fci) {
|
||||
fci.setUninterruptible();
|
||||
fd = fci.getFD(); // TODO: this is a temporary workaround
|
||||
fd.attach(this);
|
||||
FileCleanable.register(fd);
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't create a FileOutputStream for %s with %s", file, nioFs), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
// Since we can't throw IOException...
|
||||
throw IoOverNioFileSystem.convertNioToIoExceptionInStreams(e);
|
||||
}
|
||||
} else {
|
||||
this.fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
open(name, append);
|
||||
FileCleanable.register(fd); // open sets the fd, register the cleanup
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a FileOutputStream for %s%n", file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,6 +301,8 @@ public class FileOutputStream extends OutputStream
|
||||
* @see java.lang.SecurityManager#checkWrite(java.io.FileDescriptor)
|
||||
*/
|
||||
public FileOutputStream(FileDescriptor fdObj) {
|
||||
useNio = false;
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
SecurityManager security = System.getSecurityManager();
|
||||
if (fdObj == null) {
|
||||
@@ -313,12 +361,24 @@ public class FileOutputStream extends OutputStream
|
||||
boolean append = FD_ACCESS.getAppend(fd);
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
write(b, append);
|
||||
implWrite(b, append);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void implWrite(int b, boolean append) throws IOException {
|
||||
if (useNio) {
|
||||
// 'append' is ignored; the channel is supposed to obey the mode in which the file was opened
|
||||
byte[] array = new byte[1];
|
||||
array[0] = (byte) b;
|
||||
ByteBuffer buffer = ByteBuffer.wrap(array);
|
||||
getChannel().write(buffer);
|
||||
} else {
|
||||
write(b, append);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a sub array as a sequence of bytes.
|
||||
* @param b the data to be written
|
||||
@@ -343,7 +403,7 @@ public class FileOutputStream extends OutputStream
|
||||
boolean append = FD_ACCESS.getAppend(fd);
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
writeBytes(b, 0, b.length, append);
|
||||
implWriteBytes(b, 0, b.length, append);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
@@ -364,12 +424,27 @@ public class FileOutputStream extends OutputStream
|
||||
boolean append = FD_ACCESS.getAppend(fd);
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
writeBytes(b, off, len, append);
|
||||
implWriteBytes(b, off, len, append);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void implWriteBytes(byte[] b, int off, int len, boolean append) throws IOException {
|
||||
if (useNio) {
|
||||
// 'append' is ignored; the channel is supposed to obey the mode in which the file was opened
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
|
||||
getChannel().write(buffer);
|
||||
} catch (OutOfMemoryError e) {
|
||||
// May fail to allocate direct buffer memory due to small -XX:MaxDirectMemorySize
|
||||
writeBytes(b, off, len, append);
|
||||
}
|
||||
} else {
|
||||
writeBytes(b, off, len, append);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this file output stream and releases any system resources
|
||||
* associated with this stream. This file output stream may no longer
|
||||
|
||||
970
src/java.base/share/classes/java/io/IoOverNioFileSystem.java
Normal file
970
src/java.base/share/classes/java/io/IoOverNioFileSystem.java
Normal file
@@ -0,0 +1,970 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.io;
|
||||
|
||||
import com.sun.IoOverNio;
|
||||
import jdk.internal.misc.VM;
|
||||
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.*;
|
||||
import java.security.AccessControlException;
|
||||
import java.util.*;
|
||||
|
||||
import static com.sun.IoOverNio.DEBUG;
|
||||
|
||||
class IoOverNioFileSystem extends FileSystem {
|
||||
private final FileSystem defaultFileSystem;
|
||||
|
||||
IoOverNioFileSystem(FileSystem defaultFileSystem) {
|
||||
this.defaultFileSystem = defaultFileSystem;
|
||||
}
|
||||
|
||||
java.nio.file.FileSystem acquireNioFs() {
|
||||
if (!VM.isBooted()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IoOverNio.ALLOW_IO_OVER_NIO.get()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return FileSystems.getDefault();
|
||||
} catch (NullPointerException ignored) {
|
||||
// TODO Explain.
|
||||
// TODO Probably, it can be removed now.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static FileNotFoundException convertNioToIoExceptionInStreams(IOException source) {
|
||||
String message = convertNioToIoExceptionMessage(source);
|
||||
if (source instanceof FileSystemException s && s.getFile() != null) {
|
||||
message = s.getFile() + " (" + message + ")";
|
||||
}
|
||||
FileNotFoundException result = new FileNotFoundException(message);
|
||||
result.initCause(source);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IOException convertNioToIoExceptionInFile(IOException source) {
|
||||
return new IOException(convertNioToIoExceptionMessage(source), source);
|
||||
}
|
||||
|
||||
private static String convertNioToIoExceptionMessage(IOException source) {
|
||||
return switch (source) {
|
||||
case AccessDeniedException s -> "Permission denied";
|
||||
case NotDirectoryException s -> "Not a directory";
|
||||
case NoSuchFileException s -> "No such file or directory";
|
||||
case FileSystemLoopException s -> "Too many levels of symbolic links";
|
||||
case FileSystemException s -> {
|
||||
String message = source.getMessage();
|
||||
String expectedPrefix = s.getFile() + ": ";
|
||||
if (message.startsWith(expectedPrefix)) {
|
||||
message = message.substring(expectedPrefix.length());
|
||||
if (message.equals("Too many levels of symbolic links or unable to access attributes of symbolic link")) {
|
||||
yield "Too many levels of symbolic links";
|
||||
}
|
||||
}
|
||||
yield message;
|
||||
}
|
||||
default -> source.getMessage();
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean setPermission0(java.nio.file.FileSystem nioFs, File f, int access, boolean enable, boolean owneronly) {
|
||||
Path path;
|
||||
try {
|
||||
path = nioFs.getPath(f.getPath());
|
||||
} catch (InvalidPathException err) {
|
||||
throw new InternalError(err.getMessage(), err);
|
||||
}
|
||||
|
||||
// See the comment inside getBooleanAttributes that explains the order of file attribute view classes.
|
||||
BasicFileAttributeView view;
|
||||
try {
|
||||
view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
|
||||
} catch (UnsupportedOperationException ignored) {
|
||||
view = null;
|
||||
}
|
||||
if (view == null) {
|
||||
try {
|
||||
view = Files.getFileAttributeView(path, DosFileAttributeView.class);
|
||||
} catch (UnsupportedOperationException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("File system %s supports neither posix nor dos attributes", nioFs), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean result = true;
|
||||
if (view instanceof DosFileAttributeView dosView) {
|
||||
if (((access & ACCESS_READ) != 0)) {
|
||||
try {
|
||||
dosView.setReadOnly(enable);
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't set read only attributes for %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
} else if (view instanceof PosixFileAttributeView posixView) {
|
||||
try {
|
||||
Set<PosixFilePermission> perms = posixView.readAttributes().permissions();
|
||||
|
||||
if ((access & ACCESS_READ) != 0) {
|
||||
if (enable) {
|
||||
perms.add(PosixFilePermission.OWNER_READ);
|
||||
} else {
|
||||
perms.remove(PosixFilePermission.OWNER_READ);
|
||||
}
|
||||
if (!owneronly) {
|
||||
if (enable) {
|
||||
perms.add(PosixFilePermission.GROUP_READ);
|
||||
perms.add(PosixFilePermission.OTHERS_READ);
|
||||
} else {
|
||||
perms.remove(PosixFilePermission.GROUP_READ);
|
||||
perms.remove(PosixFilePermission.OTHERS_READ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((access & ACCESS_WRITE) != 0) {
|
||||
if (enable) {
|
||||
perms.add(PosixFilePermission.OWNER_WRITE);
|
||||
} else {
|
||||
perms.remove(PosixFilePermission.OWNER_WRITE);
|
||||
}
|
||||
if (!owneronly) {
|
||||
if (enable) {
|
||||
perms.add(PosixFilePermission.GROUP_WRITE);
|
||||
perms.add(PosixFilePermission.OTHERS_WRITE);
|
||||
} else {
|
||||
perms.remove(PosixFilePermission.GROUP_WRITE);
|
||||
perms.remove(PosixFilePermission.OTHERS_WRITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((access & ACCESS_EXECUTE) != 0) {
|
||||
if (enable) {
|
||||
perms.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
} else {
|
||||
perms.remove(PosixFilePermission.OWNER_EXECUTE);
|
||||
}
|
||||
if (!owneronly) {
|
||||
if (enable) {
|
||||
perms.add(PosixFilePermission.GROUP_EXECUTE);
|
||||
perms.add(PosixFilePermission.OTHERS_EXECUTE);
|
||||
} else {
|
||||
perms.remove(PosixFilePermission.GROUP_EXECUTE);
|
||||
perms.remove(PosixFilePermission.OTHERS_EXECUTE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
posixView.setPermissions(perms);
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't set posix attributes for %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getSeparator() {
|
||||
return defaultFileSystem.getSeparator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getPathSeparator() {
|
||||
return defaultFileSystem.getPathSeparator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String normalize(String path) {
|
||||
// java.nio.file.Path.normalize works differently compared to this normalizer.
|
||||
// Especially, Path.normalize converts "." into an empty string, which breaks
|
||||
// even tests in OpenJDK.
|
||||
return defaultFileSystem.normalize(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int prefixLength(String path) {
|
||||
// Although it's possible to represent this function via `Path.getRoot`,
|
||||
// the default file system and java.nio handle corner cases with incorrect paths differently.
|
||||
return defaultFileSystem.prefixLength(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolve(String parent, String child) {
|
||||
// java.nio is stricter to various invalid symbols than java.io.
|
||||
return defaultFileSystem.resolve(parent, child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultParent() {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
return nioFs.getSeparator();
|
||||
}
|
||||
return defaultFileSystem.getDefaultParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fromURIPath(String path) {
|
||||
return defaultFileSystem.fromURIPath(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAbsolute(File f) {
|
||||
boolean result = isAbsolute0(f);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.isAbsolute(%s) = %b%n", f, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isAbsolute0(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
return nioFs.getPath(f.getPath()).isAbsolute();
|
||||
} catch (InvalidPathException err) {
|
||||
// Path parsing in java.nio is stricter than in java.io.
|
||||
// Giving a chance to the original implementation.
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't check if a path %s is absolute", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.isAbsolute(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path ignored = nioFs.getPath(f.getPath());
|
||||
return false;
|
||||
} catch (InvalidPathException ignored) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.isInvalid(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolve(File f) {
|
||||
try {
|
||||
String result = resolve0(f);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.resolve(%s) = %s%n", f, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.resolve(%s) threw an error%n", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private String resolve0(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
return nioFs.getPath(f.getPath()).toAbsolutePath().toString();
|
||||
} catch (InvalidPathException err) {
|
||||
// Path parsing in java.nio is stricter than in java.io.
|
||||
// Giving a chance to the original implementation.
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't resolve a path %s", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.resolve(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String canonicalize(String path) throws IOException {
|
||||
try {
|
||||
String result = canonicalize0(path);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.canonicalize(%s) = %s%n", path, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.canonicalize(%s) threw an error%n", path), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private String canonicalize0(String path) throws IOException {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path nioPath = nioFs.getPath(path);
|
||||
if (!nioPath.isAbsolute()) {
|
||||
nioPath = Path.of(System.getProperty("user.dir")).resolve(nioPath);
|
||||
}
|
||||
Path suffix = nioFs.getPath("");
|
||||
|
||||
while (!nioPath.equals(nioPath.getRoot())) {
|
||||
Path parent = nioPath.getParent();
|
||||
if (parent == null) {
|
||||
parent = nioPath.getRoot();
|
||||
}
|
||||
|
||||
String fileNameStr = nioPath.getFileName().toString();
|
||||
if (!fileNameStr.isEmpty() && !fileNameStr.equals(".")) {
|
||||
try {
|
||||
nioPath = nioPath.toRealPath();
|
||||
break;
|
||||
} catch (IOException err) {
|
||||
// Nothing.
|
||||
}
|
||||
suffix = nioPath.getFileName().resolve(suffix);
|
||||
}
|
||||
nioPath = parent;
|
||||
}
|
||||
|
||||
return nioPath.resolve(suffix).normalize().toString();
|
||||
} catch (InvalidPathException err) {
|
||||
// Path parsing in java.nio is stricter than in java.io.
|
||||
// Giving a chance to the original implementation.
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't canonicalize a path %s", path), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.canonicalize(path);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasBooleanAttributes(File f, int attributes) {
|
||||
try {
|
||||
boolean result = hasBooleanAttributes0(f, attributes);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.hasBooleanAttributes(%s, %s) = %b%n", f, attributes, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.hasBooleanAttributes(%s, %s) threw an error", f, attributes), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasBooleanAttributes0(File f, int attributes) {
|
||||
if (acquireNioFs() != null) {
|
||||
return (getBooleanAttributes0(f) & attributes) == attributes;
|
||||
}
|
||||
return defaultFileSystem.hasBooleanAttributes(f, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBooleanAttributes(File f) {
|
||||
int result = getBooleanAttributes0(f);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.getBooleanAttributes(%s) = %d%n", f, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int getBooleanAttributes0(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
|
||||
if (path.getFileName() == null || path.getFileName().toString().isEmpty()) {
|
||||
// `stat` returns errors for such calls.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The order of checking Posix attributes first and DOS attributes next is deliberate.
|
||||
// WindowsFileSystem supports these attributes: basic, dos, acl, owner, user.
|
||||
// LinuxFileSystem supports: basic, posix, unix, owner, dos, user.
|
||||
// MacOSFileSystem and BsdFileSystem support: basic, posix, unix, owner, user.
|
||||
//
|
||||
// Notice that Linux FS supports DOS attributes, which is unexpected.
|
||||
// The DOS adapter implementation from LinuxFileSystem uses `open` for getting attributes.
|
||||
// It's not only more expensive than `stat`, but also doesn't work with Unix sockets.
|
||||
//
|
||||
// Also, notice that Windows FS does not support Posix attributes, which is expected.
|
||||
// Checking for Posix attributes allows filtering Windows out,
|
||||
// even though Posix permissions aren't used in this method.
|
||||
BasicFileAttributes attrs;
|
||||
try {
|
||||
attrs = Files.readAttributes(path, PosixFileAttributes.class);
|
||||
} catch (UnsupportedOperationException ignored) {
|
||||
attrs = Files.readAttributes(path, DosFileAttributes.class);
|
||||
}
|
||||
|
||||
return BA_EXISTS
|
||||
| (attrs.isDirectory() ? BA_DIRECTORY : 0)
|
||||
| (attrs.isRegularFile() ? BA_REGULAR : 0)
|
||||
| (attrs instanceof DosFileAttributes dosAttrs && dosAttrs.isHidden() ? BA_HIDDEN : 0);
|
||||
} catch (InvalidPathException err) {
|
||||
// Path parsing in java.nio is stricter than in java.io.
|
||||
// Giving a chance to the original implementation.
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't get attributes for a path %s", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
} catch (@SuppressWarnings("removal") IOException | AccessControlException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't get attributes for a path %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.getBooleanAttributes(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAccess(File f, int access) {
|
||||
boolean result = checkAccess0(f, access);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.checkAccess(%s, %d) = %b%n", f, access, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean checkAccess0(File f, int access) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
|
||||
int i = 0;
|
||||
AccessMode[] accessMode = new AccessMode[3];
|
||||
if ((access & ACCESS_READ) != 0) {
|
||||
accessMode[i++] = AccessMode.READ;
|
||||
}
|
||||
if ((access & ACCESS_WRITE) != 0) {
|
||||
accessMode[i++] = AccessMode.WRITE;
|
||||
}
|
||||
if ((access & ACCESS_EXECUTE) != 0) {
|
||||
accessMode[i++] = AccessMode.EXECUTE;
|
||||
}
|
||||
accessMode = Arrays.copyOf(accessMode, i);
|
||||
|
||||
nioFs.provider().checkAccess(path, accessMode);
|
||||
return true;
|
||||
} catch (@SuppressWarnings("removal") IOException | AccessControlException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't check access for a path %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return false;
|
||||
} catch (InvalidPathException err) {
|
||||
throw new InternalError(err.getMessage(), err);
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.checkAccess(f, access);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setPermission(File f, int access, boolean enable, boolean owneronly) {
|
||||
boolean result = setPermission0(f, access, enable, owneronly);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.setPermission(%s, %d, %b, %b) = %b%n", f, access, enable, owneronly, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean setPermission0(File f, int access, boolean enable, boolean owneronly) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
return setPermission0(nioFs, f, access, enable, owneronly);
|
||||
}
|
||||
return defaultFileSystem.setPermission(f, access, enable, owneronly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModifiedTime(File f) {
|
||||
try {
|
||||
long result = getLastModifiedTime0(f);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.getLastModifiedTime(%s) = %d%n", f, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.getLastModifiedTime(%s) threw an error", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private long getLastModifiedTime0(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
nioFs.provider().readAttributes(path, BasicFileAttributes.class).lastModifiedTime();
|
||||
} catch (IOException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't get last modified time for a path %s", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return 0;
|
||||
} catch (InvalidPathException err) {
|
||||
throw new InternalError(err.getMessage(), err);
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.getLastModifiedTime(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength(File f) {
|
||||
try {
|
||||
long result = getLength0(f);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.getLength(%s) = %d%n", f, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.getLength(%s) threw an error%n", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private long getLength0(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
try {
|
||||
if (nioFs != null) {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
return nioFs.provider().readAttributes(path, BasicFileAttributes.class).size();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't get file length for a path %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return 0;
|
||||
} catch (InvalidPathException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
}
|
||||
return defaultFileSystem.getLength(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFileExclusively(String pathname) throws IOException {
|
||||
try {
|
||||
boolean result = createFileExclusively0(pathname);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.createFileExclusively(%s) = %b%n", pathname, result);
|
||||
}
|
||||
return result;
|
||||
} catch (IOException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.createFileExclusively(%s) threw an error", pathname), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean createFileExclusively0(String pathname) throws IOException {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
try {
|
||||
if (nioFs != null) {
|
||||
Path path = nioFs.getPath(pathname);
|
||||
nioFs.provider().newByteChannel(
|
||||
path,
|
||||
EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
|
||||
).close();
|
||||
return true;
|
||||
}
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't exclusively create a file %s", pathname), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return false;
|
||||
} catch (InvalidPathException e) {
|
||||
throw new IOException(e.getMessage(), e); // The default file system would throw IOException too.
|
||||
} catch (IOException e) {
|
||||
throw convertNioToIoExceptionInFile(e);
|
||||
}
|
||||
return defaultFileSystem.createFileExclusively(pathname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(File f) {
|
||||
try {
|
||||
boolean result = delete0(f);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.delete(%s) = %b%n", f, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.delete(%s) threw an error", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean delete0(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
nioFs.provider().delete(path);
|
||||
return true;
|
||||
} catch (InvalidPathException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't delete a path %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.delete(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(File f) {
|
||||
try {
|
||||
String[] result = list0(f);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.list(%s) = %s%n", f, Arrays.toString(result));
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.list(%s) threw an error", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private String[] list0(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
try (DirectoryStream<Path> children = nioFs.provider().newDirectoryStream(path, AcceptAllFilter.FILTER)) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Path child : children) {
|
||||
result.add(child.getFileName().toString());
|
||||
}
|
||||
return result.toArray(String[]::new);
|
||||
}
|
||||
} catch (InvalidPathException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't list a path %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.list(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createDirectory(File f) {
|
||||
try {
|
||||
boolean result = createDirectory0(f);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.createDirectory(%s) = %b%n", f, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.createDirectory(%s) threw an error", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean createDirectory0(File f) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
nioFs.provider().createDirectory(path);
|
||||
return true;
|
||||
} catch (InvalidPathException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't create a directory %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.createDirectory(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rename(File f1, File f2) {
|
||||
try {
|
||||
boolean result = rename0(f1, f2);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.rename(%s, %s) = %b%n", f1, f2, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.rename(%s, %s) threw an error", f1, f2), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean rename0(File f1, File f2) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path1 = nioFs.getPath(f1.getPath());
|
||||
Path path2 = nioFs.getPath(f2.getPath());
|
||||
nioFs.provider().move(path1, path2, StandardCopyOption.REPLACE_EXISTING);
|
||||
return true;
|
||||
} catch (InvalidPathException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't rename %s to %s", f1, f2), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.rename(f1, f2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setLastModifiedTime(File f, long time) {
|
||||
try {
|
||||
boolean result = setLastModifiedTime0(f, time);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.setLastModifiedTime(%s, %d) = %b%n", f, time, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.setLastModifiedTime(%s, %d) threw an error", f, time), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setLastModifiedTime0(File f, long time) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
nioFs.provider()
|
||||
.getFileAttributeView(path, BasicFileAttributeView.class)
|
||||
.setTimes(FileTime.fromMillis(time), null, null);
|
||||
return true;
|
||||
} catch (InvalidPathException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't set last modified time of %s", f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.setLastModifiedTime(f, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setReadOnly(File f) {
|
||||
try {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
boolean result;
|
||||
if (nioFs != null) {
|
||||
result = setPermission0(nioFs, f, ACCESS_EXECUTE | ACCESS_WRITE, false, false);
|
||||
} else {
|
||||
result = defaultFileSystem.setReadOnly(f);
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.setReadOnly(%s) = %b%n", f, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.setReadOnly(%s) threw an error", f), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listRoots() {
|
||||
try {
|
||||
File[] result = listRoots0();
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.listRoots() = %s%n", Arrays.toString(result));
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable("IoOverNioFileSystem.listRoots() threw an error", err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private File[] listRoots0() {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
List<File> roots = new ArrayList<>();
|
||||
for (Path rootDirectory : nioFs.getRootDirectories()) {
|
||||
roots.add(rootDirectory.toFile());
|
||||
}
|
||||
return roots.toArray(File[]::new);
|
||||
}
|
||||
return defaultFileSystem.listRoots();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSpace(File f, int t) {
|
||||
try {
|
||||
long result = getSpace0(f, t);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.getSpace(%s, %d) = %d%n", f, t, result);
|
||||
}
|
||||
return result;
|
||||
} catch (RuntimeException err) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("IoOverNioFileSystem.getSpace(%s, %d) threw an error", f, t), err)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private long getSpace0(File f, int t) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path path = nioFs.getPath(f.getPath());
|
||||
FileStore store = nioFs.provider().getFileStore(path);
|
||||
return switch (t) {
|
||||
case SPACE_TOTAL -> store.getTotalSpace();
|
||||
case SPACE_USABLE -> store.getUsableSpace();
|
||||
case SPACE_FREE -> store.getUnallocatedSpace();
|
||||
default -> throw new IllegalArgumentException("Invalid space type: " + t);
|
||||
};
|
||||
} catch (InvalidPathException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't get space %s for a path %s", t, f), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.getSpace(f, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNameMax(String path) {
|
||||
// TODO Seems to be impossible with java.nio.
|
||||
return defaultFileSystem.getNameMax(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(File f1, File f2) {
|
||||
java.nio.file.FileSystem nioFs = acquireNioFs();
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
Path p1 = nioFs.getPath(f1.getPath());
|
||||
Path p2 = nioFs.getPath(f2.getPath());
|
||||
return p1.compareTo(p2);
|
||||
} catch (InvalidPathException e) {
|
||||
// Path parsing in java.nio is stricter than in java.io.
|
||||
// Giving a chance to the original implementation.
|
||||
}
|
||||
}
|
||||
return defaultFileSystem.compare(f1, f2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(File f) {
|
||||
return defaultFileSystem.hashCode(f);
|
||||
}
|
||||
|
||||
private static class AcceptAllFilter implements DirectoryStream.Filter<Path> {
|
||||
static final AcceptAllFilter FILTER = new AcceptAllFilter();
|
||||
|
||||
private AcceptAllFilter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Path entry) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,16 @@
|
||||
|
||||
package java.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static com.sun.IoOverNio.DEBUG;
|
||||
import jdk.internal.access.JavaIORandomAccessFileAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.misc.Blocker;
|
||||
@@ -90,6 +98,8 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
private volatile FileChannel channel;
|
||||
private volatile boolean closed;
|
||||
|
||||
private final boolean useNio;
|
||||
|
||||
/**
|
||||
* Creates a random access file stream to read from, and optionally
|
||||
* to write to, a file with the specified name. A new
|
||||
@@ -267,11 +277,64 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
if (file.isInvalid()) {
|
||||
throw new FileNotFoundException("Invalid file path");
|
||||
}
|
||||
fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
path = name;
|
||||
open(name, imode);
|
||||
FileCleanable.register(fd); // open sets the fd, register the cleanup
|
||||
FileSystem nioFs = File.acquireNioFs.get();
|
||||
useNio = nioFs != null;
|
||||
if (useNio) {
|
||||
Path nioPath = nioFs.getPath(name);
|
||||
if (Files.isDirectory(nioPath)) {
|
||||
// Unfortunately, java.nio allows opening directories as file channels, and there's no way
|
||||
// to determine if an opened nio channel belongs to a directory.
|
||||
throw new FileNotFoundException(name + " (Is a directory)");
|
||||
}
|
||||
try {
|
||||
var options = optionsForChannel(imode);
|
||||
// NB: the channel will be closed in the close() method
|
||||
// TODO Handle UOE
|
||||
var ch = nioFs.provider().newFileChannel(nioPath, options);
|
||||
channel = ch;
|
||||
if (ch instanceof FileChannelImpl fci) {
|
||||
fci.setUninterruptible();
|
||||
fd = fci.getFD(); // TODO: this is a temporary workaround
|
||||
fd.attach(this);
|
||||
FileCleanable.register(fd);
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (DEBUG.writeErrors()) {
|
||||
new Throwable(String.format("Can't create a RandomAccessFile for %s with %s", file, nioFs), e)
|
||||
.printStackTrace(System.err);
|
||||
}
|
||||
// Since we can't throw IOException...
|
||||
e = IoOverNioFileSystem.convertNioToIoExceptionInStreams(e);
|
||||
if (e instanceof FileNotFoundException fnne) {
|
||||
throw fnne;
|
||||
}
|
||||
throw new FileNotFoundException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
open(name, imode);
|
||||
FileCleanable.register(fd); // open sets the fd, register the cleanup
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a RandomAccessFile for %s%n", file);
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<StandardOpenOption> optionsForChannel(int imode) {
|
||||
HashSet<StandardOpenOption> options = new HashSet<>(6);
|
||||
options.add(StandardOpenOption.READ);
|
||||
if ((imode & O_RDONLY) == 0) {
|
||||
options.add(StandardOpenOption.WRITE);
|
||||
options.add(StandardOpenOption.CREATE);
|
||||
}
|
||||
if ((imode & O_SYNC) == O_SYNC) options.add(StandardOpenOption.SYNC);
|
||||
if ((imode & O_DSYNC) == O_DSYNC) options.add(StandardOpenOption.DSYNC);
|
||||
if ((imode & O_TEMPORARY) == O_TEMPORARY) options.add(StandardOpenOption.DELETE_ON_CLOSE);
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -379,12 +442,24 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
public int read() throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
return read0();
|
||||
return implRead();
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private int implRead() throws IOException {
|
||||
if (useNio) {
|
||||
// Really same to FileInputStream.read()
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1);
|
||||
int nRead = getChannel().read(buffer);
|
||||
buffer.rewind();
|
||||
return nRead == 1 ? (buffer.get() & 0xFF) : -1;
|
||||
} else {
|
||||
return read0();
|
||||
}
|
||||
}
|
||||
|
||||
private native int read0() throws IOException;
|
||||
|
||||
/**
|
||||
@@ -397,12 +472,26 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
private int readBytes(byte[] b, int off, int len) throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
return readBytes0(b, off, len);
|
||||
return implReadBytes(b, off, len);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private int implReadBytes(byte[] b, int off, int len) throws IOException {
|
||||
if (useNio) {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
|
||||
return getChannel().read(buffer);
|
||||
} catch (OutOfMemoryError e) {
|
||||
// May fail to allocate direct buffer memory due to small -XX:MaxDirectMemorySize
|
||||
return readBytes0(b, off, len);
|
||||
}
|
||||
} else {
|
||||
return readBytes0(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
private native int readBytes0(byte[] b, int off, int len) throws IOException;
|
||||
|
||||
/**
|
||||
@@ -431,7 +520,17 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
* {@code b.length - off}
|
||||
*/
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return readBytes(b, off, len);
|
||||
if (useNio) {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
|
||||
return getChannel().read(buffer);
|
||||
} catch (OutOfMemoryError e) {
|
||||
// May fail to allocate direct buffer memory due to small -XX:MaxDirectMemorySize
|
||||
return readBytes(b, off, len);
|
||||
}
|
||||
} else {
|
||||
return readBytes(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -454,7 +553,17 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
* @throws NullPointerException If {@code b} is {@code null}.
|
||||
*/
|
||||
public int read(byte[] b) throws IOException {
|
||||
return readBytes(b, 0, b.length);
|
||||
if (useNio) {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b);
|
||||
return getChannel().read(buffer);
|
||||
} catch (OutOfMemoryError e) {
|
||||
// May fail to allocate direct buffer memory due to small -XX:MaxDirectMemorySize
|
||||
return readBytes(b, 0, b.length);
|
||||
}
|
||||
} else {
|
||||
return readBytes(b, 0, b.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -550,12 +659,23 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
public void write(int b) throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
write0(b);
|
||||
implWrite(b);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void implWrite(int b) throws IOException {
|
||||
if (useNio) {
|
||||
byte[] array = new byte[1];
|
||||
array[0] = (byte) b;
|
||||
ByteBuffer buffer = ByteBuffer.wrap(array);
|
||||
getChannel().write(buffer);
|
||||
} else {
|
||||
write0(b);
|
||||
}
|
||||
}
|
||||
|
||||
private native void write0(int b) throws IOException;
|
||||
|
||||
/**
|
||||
@@ -569,12 +689,26 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
private void writeBytes(byte[] b, int off, int len) throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
writeBytes0(b, off, len);
|
||||
implWriteBytes(b, off, len);
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void implWriteBytes(byte[] b, int off, int len) throws IOException {
|
||||
if (useNio) {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
|
||||
getChannel().write(buffer);
|
||||
} catch (OutOfMemoryError e) {
|
||||
// May fail to allocate direct buffer memory due to small -XX:MaxDirectMemorySize
|
||||
writeBytes0(b, off, len);
|
||||
}
|
||||
} else {
|
||||
writeBytes0(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
private native void writeBytes0(byte[] b, int off, int len) throws IOException;
|
||||
|
||||
/**
|
||||
@@ -611,7 +745,15 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
* at which the next read or write occurs.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public native long getFilePointer() throws IOException;
|
||||
public long getFilePointer() throws IOException {
|
||||
if (useNio) {
|
||||
return getChannel().position();
|
||||
} else {
|
||||
return getFilePointer0();
|
||||
}
|
||||
}
|
||||
|
||||
private native long getFilePointer0() throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the file-pointer offset, measured from the beginning of this
|
||||
@@ -633,7 +775,11 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
}
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
seek0(pos);
|
||||
if (useNio) {
|
||||
getChannel().position(pos);
|
||||
} else {
|
||||
seek0(pos);
|
||||
}
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
@@ -650,7 +796,11 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
public long length() throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
return length0();
|
||||
if (useNio) {
|
||||
return getChannel().size();
|
||||
} else {
|
||||
return length0();
|
||||
}
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
@@ -680,7 +830,26 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
public void setLength(long newLength) throws IOException {
|
||||
long comp = Blocker.begin();
|
||||
try {
|
||||
setLength0(newLength);
|
||||
if (useNio) {
|
||||
FileChannel channel = getChannel();
|
||||
long oldSize = channel.size();
|
||||
if (newLength < oldSize) {
|
||||
channel.truncate(newLength);
|
||||
} else {
|
||||
byte[] buf = new byte[1 << 14];
|
||||
Arrays.fill(buf, (byte) 0);
|
||||
long remains = newLength - oldSize;
|
||||
while (remains > 0) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(buf);
|
||||
int length = (int)Math.min(remains, buf.length);
|
||||
buffer.limit(length);
|
||||
channel.write(buffer);
|
||||
remains -= length;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setLength0(newLength);
|
||||
}
|
||||
} finally {
|
||||
Blocker.end(comp);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
|
||||
import com.sun.IoOverNio;
|
||||
import jdk.internal.access.JavaSecurityPropertiesAccess;
|
||||
import jdk.internal.event.EventHelper;
|
||||
import jdk.internal.event.SecurityPropertyModificationEvent;
|
||||
@@ -94,6 +95,16 @@ public final class Security {
|
||||
}
|
||||
|
||||
private static void initialize() {
|
||||
boolean allowIoOverNioBackup = IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
try {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(false);
|
||||
initialize0();
|
||||
} finally {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(allowIoOverNioBackup);
|
||||
}
|
||||
}
|
||||
|
||||
private static void initialize0() {
|
||||
props = new Properties();
|
||||
boolean overrideAll = false;
|
||||
|
||||
@@ -123,7 +134,6 @@ public final class Security {
|
||||
props.getProperty(key));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static boolean loadProps(File masterFile, String extraPropFile, boolean overrideAll) {
|
||||
|
||||
@@ -57,6 +57,7 @@ import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.sun.IoOverNio;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.module.ModulePatcher.PatchedModuleReader;
|
||||
@@ -737,11 +738,17 @@ public class BuiltinClassLoader
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) {
|
||||
if (System.getSecurityManager() == null) {
|
||||
return defineClass(cn, loadedModule);
|
||||
} else {
|
||||
PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);
|
||||
return AccessController.doPrivileged(pa);
|
||||
boolean allowIoOverNioBackup = IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
try {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(false);
|
||||
if (System.getSecurityManager() == null) {
|
||||
return defineClass(cn, loadedModule);
|
||||
} else {
|
||||
PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);
|
||||
return AccessController.doPrivileged(pa);
|
||||
}
|
||||
} finally {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(allowIoOverNioBackup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,32 +760,38 @@ public class BuiltinClassLoader
|
||||
@SuppressWarnings("removal")
|
||||
private Class<?> findClassOnClassPathOrNull(String cn) {
|
||||
String path = cn.replace('.', '/').concat(".class");
|
||||
if (System.getSecurityManager() == null) {
|
||||
Resource res = ucp.getResource(path, false);
|
||||
if (res != null) {
|
||||
try {
|
||||
return defineClass(cn, res);
|
||||
} catch (IOException ioe) {
|
||||
// TBD on how I/O errors should be propagated
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// avoid use of lambda here
|
||||
PrivilegedAction<Class<?>> pa = new PrivilegedAction<>() {
|
||||
public Class<?> run() {
|
||||
Resource res = ucp.getResource(path, false);
|
||||
if (res != null) {
|
||||
try {
|
||||
return defineClass(cn, res);
|
||||
} catch (IOException ioe) {
|
||||
// TBD on how I/O errors should be propagated
|
||||
}
|
||||
boolean allowIoOverNioBackup = IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
try {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(false);
|
||||
if (System.getSecurityManager() == null) {
|
||||
Resource res = ucp.getResource(path, false);
|
||||
if (res != null) {
|
||||
try {
|
||||
return defineClass(cn, res);
|
||||
} catch (IOException ioe) {
|
||||
// TBD on how I/O errors should be propagated
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return AccessController.doPrivileged(pa);
|
||||
return null;
|
||||
} else {
|
||||
// avoid use of lambda here
|
||||
PrivilegedAction<Class<?>> pa = new PrivilegedAction<>() {
|
||||
public Class<?> run() {
|
||||
Resource res = ucp.getResource(path, false);
|
||||
if (res != null) {
|
||||
try {
|
||||
return defineClass(cn, res);
|
||||
} catch (IOException ioe) {
|
||||
// TBD on how I/O errors should be propagated
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return AccessController.doPrivileged(pa);
|
||||
}
|
||||
} finally {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(allowIoOverNioBackup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package jdk.internal.loader;
|
||||
|
||||
import com.sun.IoOverNio;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.ref.CleanerFactory;
|
||||
import jdk.internal.util.StaticProperty;
|
||||
@@ -122,13 +123,17 @@ public final class NativeLibraries {
|
||||
if (!isBuiltin) {
|
||||
name = AccessController.doPrivileged(new PrivilegedAction<>() {
|
||||
public String run() {
|
||||
boolean allowIoOverNioBackup = IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
try {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(false);
|
||||
if (loadLibraryOnlyIfPresent && !file.exists()) {
|
||||
return null;
|
||||
}
|
||||
return file.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(allowIoOverNioBackup);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -339,8 +344,14 @@ public final class NativeLibraries {
|
||||
// will include the error message from dlopen to provide diagnostic information
|
||||
return AccessController.doPrivileged(new PrivilegedAction<>() {
|
||||
public Boolean run() {
|
||||
File file = new File(name);
|
||||
return file.exists();
|
||||
boolean allowIoOverNioBackup = IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
try {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(false);
|
||||
File file = new File(name);
|
||||
return file.exists();
|
||||
} finally {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(allowIoOverNioBackup);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1559,4 +1559,8 @@ public class FileChannelImpl
|
||||
assert fileLockTable != null;
|
||||
fileLockTable.remove(fli);
|
||||
}
|
||||
|
||||
public FileDescriptor getFD() {
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import javax.security.auth.x500.X500Principal;
|
||||
import java.net.SocketPermission;
|
||||
import java.net.NetPermission;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.sun.IoOverNio;
|
||||
import jdk.internal.access.JavaSecurityAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.util.StaticProperty;
|
||||
@@ -309,6 +311,16 @@ public class PolicyFile extends java.security.Policy {
|
||||
* initialize the Policy object.
|
||||
*/
|
||||
private void init(URL url) {
|
||||
boolean allowIoOverNioBackup = IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
try {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(false);
|
||||
init0(url);
|
||||
} finally {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(allowIoOverNioBackup);
|
||||
}
|
||||
}
|
||||
|
||||
private void init0(URL url) {
|
||||
// Properties are set once for each init(); ignore changes
|
||||
// between diff invocations of initPolicyFile(policy, url, info).
|
||||
String numCacheStr =
|
||||
@@ -1095,6 +1107,17 @@ public class PolicyFile extends java.security.Policy {
|
||||
*/
|
||||
private PermissionCollection getPermissions(Permissions perms,
|
||||
ProtectionDomain pd ) {
|
||||
boolean allowIoOverNioBackup = IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
try {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(false);
|
||||
return getPermissions0(perms, pd);
|
||||
} finally {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(allowIoOverNioBackup);
|
||||
}
|
||||
}
|
||||
|
||||
private Permissions getPermissions0(Permissions perms,
|
||||
ProtectionDomain pd) {
|
||||
if (debug != null) {
|
||||
debug.println("getPermissions:\n\t" + printPD(pd));
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ Java_java_io_RandomAccessFile_writeBytes0(JNIEnv *env,
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_java_io_RandomAccessFile_getFilePointer(JNIEnv *env, jobject this) {
|
||||
Java_java_io_RandomAccessFile_getFilePointer0(JNIEnv *env, jobject this) {
|
||||
FD fd;
|
||||
jlong ret;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2008, 2021, 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
|
||||
@@ -197,10 +197,11 @@ class UnixChannelFactory {
|
||||
|
||||
// create flags
|
||||
if (flags.createNew) {
|
||||
byte[] pathForSysCall = path.getByteArrayForSysCalls();
|
||||
byte[] pathForSysCall = path.asByteArray();
|
||||
|
||||
// throw exception if file name is "." to avoid confusing error
|
||||
if ((pathForSysCall[pathForSysCall.length-1] == '.') &&
|
||||
// throw exception if file name is "." or "" to avoid confusing error
|
||||
if ((pathForSysCall.length == 0) ||
|
||||
(pathForSysCall[pathForSysCall.length-1] == '.') &&
|
||||
(pathForSysCall.length == 1 ||
|
||||
(pathForSysCall[pathForSysCall.length-2] == '/')))
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.net.*;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.sun.IoOverNio;
|
||||
import sun.security.util.Debug;
|
||||
|
||||
/**
|
||||
@@ -126,8 +127,18 @@ public final class NativePRNG extends SecureRandomSpi {
|
||||
/**
|
||||
* Create a RandomIO object for all I/O of this Variant type.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private static RandomIO initIO(final Variant v) {
|
||||
boolean allowIoOverNioBackup = IoOverNio.ALLOW_IO_OVER_NIO.get();
|
||||
try {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(false);
|
||||
return initIOImpl(v);
|
||||
} finally {
|
||||
IoOverNio.ALLOW_IO_OVER_NIO.set(allowIoOverNioBackup);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static RandomIO initIOImpl(final Variant v) {
|
||||
return AccessController.doPrivileged(
|
||||
new PrivilegedAction<>() {
|
||||
@Override
|
||||
|
||||
@@ -47,8 +47,6 @@ import sun.java2d.SunGraphicsEnvironment;
|
||||
import sun.java2d.SurfaceManagerFactory;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
|
||||
/**
|
||||
* This is an implementation of a GraphicsEnvironment object for the default
|
||||
* local GraphicsEnvironment used by the Java Runtime Environment for Mac OS X
|
||||
@@ -364,14 +362,4 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment {
|
||||
return newFonts;
|
||||
}
|
||||
|
||||
@JBRApi.Provides("GraphicsUtils#isBuiltinDisplay")
|
||||
public static boolean isBuiltinDisplay(GraphicsDevice display) {
|
||||
if (display instanceof CGraphicsDevice) {
|
||||
CGraphicsDevice cgd = (CGraphicsDevice) display;
|
||||
return isBuiltinDisplayNative(cgd.getDisplayID());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static native boolean isBuiltinDisplayNative(int displayID);
|
||||
}
|
||||
|
||||
@@ -25,16 +25,9 @@
|
||||
|
||||
package sun.java2d;
|
||||
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Image;
|
||||
|
||||
import sun.awt.CGraphicsEnvironment;
|
||||
import sun.awt.image.SunVolatileImage;
|
||||
import sun.awt.image.SurfaceManager;
|
||||
import sun.awt.image.TextureWrapperSurfaceManager;
|
||||
import sun.awt.image.VolatileSurfaceManager;
|
||||
import sun.java2d.metal.MTLGraphicsConfig;
|
||||
import sun.java2d.metal.MTLSurfaceData;
|
||||
import sun.java2d.metal.MTLVolatileSurfaceManager;
|
||||
import sun.java2d.opengl.CGLVolatileSurfaceManager;
|
||||
|
||||
@@ -61,15 +54,4 @@ public class MacosxSurfaceManagerFactory extends SurfaceManagerFactory {
|
||||
return CGraphicsEnvironment.usingMetalPipeline() ? new MTLVolatileSurfaceManager(vImg, context) :
|
||||
new CGLVolatileSurfaceManager(vImg, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceManager createTextureWrapperSurfaceManager(GraphicsConfiguration gc, Image image, long texture) {
|
||||
SurfaceData sd;
|
||||
if (gc instanceof MTLGraphicsConfig) {
|
||||
sd = MTLSurfaceData.createData((MTLGraphicsConfig) gc, image, texture);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unsupported GraphicsConfiguration");
|
||||
}
|
||||
return new TextureWrapperSurfaceManager(sd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ import java.awt.Transparency;
|
||||
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.Raster;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static sun.java2d.pipe.BufferedOpCodes.DISPOSE_SURFACE;
|
||||
import static sun.java2d.pipe.BufferedOpCodes.FLUSH_SURFACE;
|
||||
@@ -201,10 +200,6 @@ public abstract class MTLSurfaceData extends SurfaceData
|
||||
type);
|
||||
}
|
||||
|
||||
public static MTLTextureWrapperSurfaceData createData(MTLGraphicsConfig gc, Image image, long pTexture) {
|
||||
return new MTLTextureWrapperSurfaceData(gc, image, pTexture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDefaultScaleX() {
|
||||
return scale;
|
||||
@@ -224,8 +219,6 @@ public abstract class MTLSurfaceData extends SurfaceData
|
||||
|
||||
protected native boolean initTexture(long pData, boolean isOpaque, int width, int height);
|
||||
|
||||
protected native boolean initWithTexture(long pData, boolean isOpaque, long texturePtr);
|
||||
|
||||
protected native boolean initRTexture(long pData, boolean isOpaque, int width, int height);
|
||||
|
||||
protected native boolean initFlipBackbuffer(long pData, boolean isOpaque, int width, int height);
|
||||
@@ -681,46 +674,4 @@ public abstract class MTLSurfaceData extends SurfaceData
|
||||
}
|
||||
|
||||
private static native boolean loadNativeRasterWithRects(long sdops, long pRaster, int width, int height, long pRects, int rectsCount);
|
||||
|
||||
/**
|
||||
* Surface data for an existing texture
|
||||
*/
|
||||
public static final class MTLTextureWrapperSurfaceData extends MTLSurfaceData {
|
||||
private final Image myImage;
|
||||
|
||||
private MTLTextureWrapperSurfaceData(MTLGraphicsConfig gc, Image image, long pTexture) throws IllegalArgumentException {
|
||||
super(null, gc, ColorModel.getRGBdefault(), RT_TEXTURE, /*width=*/ 0, /*height=*/ 0);
|
||||
myImage = image;
|
||||
|
||||
MTLRenderQueue rq = MTLRenderQueue.getInstance();
|
||||
AtomicBoolean success = new AtomicBoolean(false);
|
||||
|
||||
rq.lock();
|
||||
try {
|
||||
MTLContext.setScratchSurface(gc);
|
||||
rq.flushAndInvokeNow(() -> success.set(initWithTexture(getNativeOps(), false, pTexture)));
|
||||
} finally {
|
||||
rq.unlock();
|
||||
}
|
||||
|
||||
if (!success.get()) {
|
||||
throw new IllegalArgumentException("Failed to init the surface data");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceData getReplacement() {
|
||||
throw new UnsupportedOperationException("not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDestination() {
|
||||
return myImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle getBounds() {
|
||||
return getNativeBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,111 +26,37 @@
|
||||
package sun.lwawt;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Point;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import sun.awt.AWTAccessor;
|
||||
import sun.awt.SunToolkit;
|
||||
import sun.awt.CachedCursorManager;
|
||||
|
||||
public abstract class LWCursorManager {
|
||||
public abstract class LWCursorManager extends CachedCursorManager {
|
||||
|
||||
/**
|
||||
* A flag to indicate if the update is scheduled, so we don't process it
|
||||
* twice.
|
||||
*/
|
||||
private final AtomicBoolean updatePending = new AtomicBoolean(false);
|
||||
|
||||
protected LWCursorManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cursor to correspond the component currently under mouse.
|
||||
*
|
||||
* This method should not be executed on the toolkit thread as it
|
||||
* calls to user code (e.g. Container.findComponentAt).
|
||||
*/
|
||||
public final void updateCursor() {
|
||||
updatePending.set(false);
|
||||
updateCursorImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules updating the cursor on the corresponding event dispatch
|
||||
* thread for the given window.
|
||||
*
|
||||
* This method is called on the toolkit thread as a result of a
|
||||
* native update cursor request (e.g. WM_SETCURSOR on Windows).
|
||||
*/
|
||||
public final void updateCursorLater(final LWWindowPeer window) {
|
||||
if (updatePending.compareAndSet(false, true)) {
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateCursor();
|
||||
}
|
||||
};
|
||||
SunToolkit.executeOnEventHandlerThread(window.getTarget(), r);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCursorImpl() {
|
||||
final Point cursorPos = getCursorPosition();
|
||||
final Component c = findComponent(cursorPos);
|
||||
final Cursor cursor;
|
||||
@Override
|
||||
protected Cursor getCursorByPosition(final Point cursorPos, Component c) {
|
||||
final Object peer = LWToolkit.targetToPeer(c);
|
||||
if (peer instanceof LWComponentPeer) {
|
||||
final LWComponentPeer<?, ?> lwpeer = (LWComponentPeer<?, ?>) peer;
|
||||
final Point p = lwpeer.getLocationOnScreen();
|
||||
cursor = lwpeer.getCursor(new Point(cursorPos.x - p.x,
|
||||
cursorPos.y - p.y));
|
||||
} else {
|
||||
cursor = (c != null) ? c.getCursor() : null;
|
||||
return lwpeer.getCursor(new Point(cursorPos.x - p.x,
|
||||
cursorPos.y - p.y));
|
||||
}
|
||||
setCursor(cursor);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first visible, enabled and showing component under cursor.
|
||||
* Returns null for modal blocked windows.
|
||||
*
|
||||
* @param cursorPos Current cursor position.
|
||||
* @return Component or null.
|
||||
*/
|
||||
private static final Component findComponent(final Point cursorPos) {
|
||||
@Override
|
||||
protected Component getComponentUnderCursor() {
|
||||
final LWComponentPeer<?, ?> peer = LWWindowPeer.getPeerUnderCursor();
|
||||
Component c = null;
|
||||
if (peer != null && peer.getWindowPeerOrSelf().getBlocker() == null) {
|
||||
c = peer.getTarget();
|
||||
if (c instanceof Container) {
|
||||
final Point p = peer.getLocationOnScreen();
|
||||
c = AWTAccessor.getContainerAccessor().findComponentAt(
|
||||
(Container) c, cursorPos.x - p.x, cursorPos.y - p.y, false);
|
||||
|
||||
}
|
||||
while (c != null) {
|
||||
final Object p = AWTAccessor.getComponentAccessor().getPeer(c);
|
||||
if (c.isVisible() && c.isEnabled() && p != null) {
|
||||
break;
|
||||
}
|
||||
c = c.getParent();
|
||||
}
|
||||
return peer.getTarget();
|
||||
}
|
||||
return c;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current cursor position.
|
||||
*/
|
||||
// TODO: make it public to reuse for MouseInfo
|
||||
protected abstract Point getCursorPosition();
|
||||
|
||||
/**
|
||||
* Sets a cursor. The cursor can be null if the mouse is not over a Java
|
||||
* window.
|
||||
* @param cursor the new {@code Cursor}.
|
||||
*/
|
||||
protected abstract void setCursor(Cursor cursor);
|
||||
@Override
|
||||
protected Point getLocationOnScreen(Component component) {
|
||||
return AWTAccessor.getComponentAccessor().getPeer(component).getLocationOnScreen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ public abstract class LWToolkit extends SunToolkit implements Runnable {
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
log.fine("LWToolkit.waitForRunState: interrupted");
|
||||
log.fine("LWToolkit.run: interrupted");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,7 +783,7 @@ public class LWWindowPeer
|
||||
|
||||
@Override
|
||||
public void notifyUpdateCursor() {
|
||||
getLWToolkit().getCursorManager().updateCursorLater(this);
|
||||
getLWToolkit().getCursorManager().updateCursorLater(this.getTarget());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -373,37 +373,4 @@ class CAccessibleText {
|
||||
}
|
||||
}, c);
|
||||
}
|
||||
|
||||
static void setText(final Accessible a, final Component c, final String newText) {
|
||||
if (a == null) return;
|
||||
|
||||
CAccessibility.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final AccessibleContext ac = a.getAccessibleContext();
|
||||
if (ac == null) return;
|
||||
|
||||
final AccessibleEditableText aet = ac.getAccessibleEditableText();
|
||||
if (aet == null) return;
|
||||
|
||||
aet.setTextContents(newText);
|
||||
}
|
||||
}, c);
|
||||
}
|
||||
|
||||
static boolean isSetAccessibilityValueAllowed(final Accessible a, final Component c) {
|
||||
if (a == null) return false;
|
||||
|
||||
return CAccessibility.invokeAndWait(new Callable<Boolean>() {
|
||||
public Boolean call() throws Exception {
|
||||
final AccessibleContext ac = a.getAccessibleContext();
|
||||
if (ac == null || ac.getAccessibleEditableText() == null) return false;
|
||||
|
||||
final Accessible sa = CAccessible.getSwingAccessible(a);
|
||||
if (sa instanceof JTextComponent textComponent) {
|
||||
return textComponent.isEditable() && textComponent.isEnabled();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}, c, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ final class CCursorManager extends LWCursorManager {
|
||||
private CCursorManager() { }
|
||||
|
||||
@Override
|
||||
protected Point getCursorPosition() {
|
||||
public Point getCursorPosition() {
|
||||
final Point2D nativePosition = nativeGetCursorPosition();
|
||||
return new Point((int)nativePosition.getX(), (int)nativePosition.getY());
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowStateListener;
|
||||
import java.awt.peer.ComponentPeer;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
@@ -1066,6 +1067,33 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
return isFullScreenMode;
|
||||
}
|
||||
|
||||
private void waitForWindowState(int state) {
|
||||
if (peer.getState() == state) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object lock = new Object();
|
||||
WindowStateListener wsl = new WindowStateListener() {
|
||||
public void windowStateChanged(WindowEvent e) {
|
||||
synchronized (lock) {
|
||||
if (e.getNewState() == state) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
target.addWindowStateListener(wsl);
|
||||
if (peer.getState() != state) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
target.removeWindowStateListener(wsl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWindowState(int windowState) {
|
||||
if (peer == null || !peer.isVisible()) {
|
||||
@@ -1087,6 +1115,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
// let's return into the normal states first
|
||||
// the zoom call toggles between the normal and the max states
|
||||
unmaximize();
|
||||
waitForWindowState(Frame.NORMAL);
|
||||
}
|
||||
execute(CWrapper.NSWindow::miniaturize);
|
||||
break;
|
||||
@@ -1094,6 +1123,8 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
if (prevWindowState == Frame.ICONIFIED) {
|
||||
// let's return into the normal states first
|
||||
execute(CWrapper.NSWindow::deminiaturize);
|
||||
waitForWindowState(Frame.NORMAL);
|
||||
|
||||
}
|
||||
maximize();
|
||||
break;
|
||||
@@ -1185,15 +1216,15 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
|
||||
private final static int INVOKE_LATER_FLUSH_BUFFERS = getInvokeLaterMode();
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static int getInvokeLaterMode() {
|
||||
final String invokeLaterKey = "awt.mac.flushBuffers.invokeLater";
|
||||
@SuppressWarnings("removal")
|
||||
final String invokeLaterArg = AccessController.doPrivileged(
|
||||
new GetPropertyAction(invokeLaterKey));
|
||||
final int result;
|
||||
if (invokeLaterArg == null) {
|
||||
// default = 'enabled' to avoid any potential freeze (safe) until better solution:
|
||||
result = INVOKE_LATER_ENABLED;
|
||||
// default = 'auto':
|
||||
result = INVOKE_LATER_AUTO;
|
||||
} else {
|
||||
switch (invokeLaterArg.toLowerCase()) {
|
||||
default:
|
||||
@@ -1215,33 +1246,9 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private final static boolean INVOKE_LATER_USE_PWM = getInvokeLaterUsePWM();
|
||||
|
||||
private static boolean getInvokeLaterUsePWM() {
|
||||
final String usePwmKey = "awt.mac.flushBuffers.pwm";
|
||||
@SuppressWarnings("removal")
|
||||
final String usePwmArg = AccessController.doPrivileged(
|
||||
new GetPropertyAction(usePwmKey));
|
||||
final boolean result;
|
||||
if (usePwmArg == null) {
|
||||
// default = 'false':
|
||||
result = false;
|
||||
} else {
|
||||
result = "true".equalsIgnoreCase(usePwmArg);
|
||||
logger.info("CPlatformWindow: property \"{0}={1}\", using usePWM={2}.",
|
||||
usePwmKey, usePwmArg, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/* 10s period arround reference times (sleep/wake-up...)
|
||||
* to ensure all displays are awaken properly */
|
||||
private final static long NANOS_PER_SEC = 1000000000L;
|
||||
private final static long STATE_CHANGE_PERIOD = 10L * NANOS_PER_SEC;
|
||||
|
||||
private final AtomicBoolean mirroringState = new AtomicBoolean(false);
|
||||
/** per window timestamp of disabling mirroring */
|
||||
private final AtomicLong mirroringDisablingTime = new AtomicLong(0L);
|
||||
private final static int INVOKE_LATER_COUNT = 5;
|
||||
/** per window counter of remaining invokeLater calls */
|
||||
private final AtomicInteger invokeLaterCount = new AtomicInteger();
|
||||
|
||||
// Specific class needed to get obvious stack traces:
|
||||
private final class EmptyRunnable implements Runnable {
|
||||
@@ -1257,10 +1264,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
private final EmptyRunnable emptyTask = new EmptyRunnable();
|
||||
|
||||
void flushBuffers() {
|
||||
// Only 1 usage by deliverMoveResizeEvent():
|
||||
// System-dependent appearance optimization.
|
||||
// May be blocking so postpone this event processing:
|
||||
|
||||
// only 1 usage by deliverMoveResizeEvent():
|
||||
if (isVisible() && !nativeBounds.isEmpty() && !isFullScreenMode) {
|
||||
// use the system property 'awt.mac.flushBuffers.invokeLater' to true/auto (default: auto)
|
||||
// to avoid deadlocks caused by the LWCToolkit.invokeAndWait() call below:
|
||||
@@ -1273,68 +1277,38 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
default:
|
||||
case INVOKE_LATER_AUTO:
|
||||
useInvokeLater = false;
|
||||
|
||||
// JBR-5497: force using invokeLater() when computer returns from sleep or displayChanged()
|
||||
// (mirroring case especially) to avoid deadlocks until solved definitely:
|
||||
|
||||
boolean mirroring = false;
|
||||
if (peer != null) {
|
||||
final GraphicsDevice device = peer.getGraphicsConfiguration().getDevice();
|
||||
if (device instanceof CGraphicsDevice) {
|
||||
// JBR-5497: avoid deadlock in mirroring mode (laptop + external screen):
|
||||
// Note: the CGraphicsDevice instance will be recreated when mirroring is enabled/disabled.
|
||||
mirroring = ((CGraphicsDevice)device).isMirroring();
|
||||
useInvokeLater = ((CGraphicsDevice)device).isMirroring();
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("CPlatformWindow.flushBuffers[auto]: CGraphicsDevice.isMirroring = {0}",
|
||||
mirroring);
|
||||
logger.fine("CPlatformWindow.flushBuffers: CGraphicsDevice.isMirroring = {0}",
|
||||
useInvokeLater);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mirroring) {
|
||||
mirroringState.set(true);
|
||||
} else if (mirroringState.get()) {
|
||||
// mirroringState trigger enabled but mirroring=false:
|
||||
// keep mirroring enabled for some time (STATE_CHANGE_PERIOD):
|
||||
mirroring = true;
|
||||
final long now = System.nanoTime(); // timestamp
|
||||
|
||||
// should disable mirroring now ? check timestamp:
|
||||
final long lastTime = mirroringDisablingTime.get();
|
||||
final long delta;
|
||||
if (lastTime == 0L) {
|
||||
delta = 0L;
|
||||
// unset: set timestamp of disabling mirroring:
|
||||
mirroringDisablingTime.set(now);
|
||||
} else {
|
||||
delta = Math.abs(now - lastTime);
|
||||
if (delta > STATE_CHANGE_PERIOD) {
|
||||
// disable mirroring as period is elapsed:
|
||||
mirroring = false;
|
||||
mirroringState.set(false);
|
||||
// reset timestamp:
|
||||
mirroringDisablingTime.set(0L);
|
||||
}
|
||||
}
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("CPlatformWindow.flushBuffers[auto]: mirroring = {0} (mirroring = {1} delta = {2} ms)",
|
||||
mirroring, mirroringState.get(), delta * 1e-6);
|
||||
// JBR-5497: keep few more invokeLater() when computer returns from sleep or displayChanged()
|
||||
// to avoid deadlocks until solved definitely:
|
||||
if (useInvokeLater) {
|
||||
// reset to max count:
|
||||
invokeLaterCount.set(INVOKE_LATER_COUNT);
|
||||
} else {
|
||||
final int prev = invokeLaterCount.get();
|
||||
if (prev > 0) {
|
||||
invokeLaterCount.compareAndSet(prev, prev - 1);
|
||||
useInvokeLater = true;
|
||||
}
|
||||
}
|
||||
useInvokeLater = mirroring;
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("CPlatformWindow.flushBuffers: useInvokeLater = {0} (count = {1})",
|
||||
useInvokeLater, invokeLaterCount.get());
|
||||
}
|
||||
break;
|
||||
case INVOKE_LATER_ENABLED:
|
||||
useInvokeLater = true;
|
||||
break;
|
||||
}
|
||||
if (!useInvokeLater && INVOKE_LATER_USE_PWM) {
|
||||
// If the system property 'awt.mac.flushBuffers.pwm' is true,
|
||||
// invokeLater is enforced during power transitions.
|
||||
final boolean inTransition = LWCToolkit.isWithinPowerTransition();
|
||||
if (inTransition) {
|
||||
logger.fine("CPlatformWindow.flushBuffers[pwm]: inTransition = true");
|
||||
useInvokeLater = true;
|
||||
}
|
||||
}
|
||||
try {
|
||||
// check invokeAndWait: KO (operations require AWTLock and main thread)
|
||||
// => use invokeLater as it is an empty event to force refresh ASAP
|
||||
@@ -1347,13 +1321,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
getIdentifier(target));
|
||||
}
|
||||
|
||||
/* Ensure >500ms = 666ms timeout to avoid any deadlock among
|
||||
* appkit, EDT, Flusher & a11y threads, locks
|
||||
* and various synchronization patterns... */
|
||||
final double timeoutSeconds = 0.666; // seconds
|
||||
|
||||
// FUCK: appKit is calling this method !
|
||||
LWCToolkit.invokeAndWait(emptyTask, target, timeoutSeconds);
|
||||
LWCToolkit.invokeAndWait(emptyTask, target);
|
||||
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("CPlatformWindow.flushBuffers: exit " +
|
||||
@@ -1362,9 +1330,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
}
|
||||
}
|
||||
} catch (InvocationTargetException ite) {
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("CPlatformWindow.flushBuffers: timeout or LWCToolkit.invoke failure: ", ite);
|
||||
}
|
||||
logger.severe("CPlatformWindow.flushBuffers: exception occurred: ", ite);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1399,16 +1365,10 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
responder.handleWindowFocusEvent(gained, oppositePeer);
|
||||
}
|
||||
|
||||
/* useless ? */
|
||||
public void doDeliverMoveResizeEvent() {
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("CPlatformWindow.doDeliverMoveResizeEvent() called by {0}",
|
||||
Thread.currentThread());
|
||||
}
|
||||
execute(ptr -> nativeCallDeliverMoveResizeEvent(ptr));
|
||||
}
|
||||
|
||||
/* native call by AWTWindow._deliverMoveResizeEvent() */
|
||||
protected void deliverMoveResizeEvent(int x, int y, int width, int height,
|
||||
boolean byUser) {
|
||||
AtomicBoolean ref = new AtomicBoolean();
|
||||
@@ -1422,17 +1382,9 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
nativeBounds = new Rectangle(x, y, width, height);
|
||||
if (peer != null) {
|
||||
peer.notifyReshape(x, y, width, height);
|
||||
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("CPlatformWindow.deliverMoveResizeEvent(): byUser = {0} " +
|
||||
"isFullScreenAnimationOn = {1}", byUser, isFullScreenAnimationOn);
|
||||
}
|
||||
|
||||
// System-dependent appearance optimization.
|
||||
if ((byUser && !oldB.getSize().equals(nativeBounds.getSize()))
|
||||
|| isFullScreenAnimationOn) {
|
||||
|
||||
// May be blocking so postpone this event processing:
|
||||
flushBuffers();
|
||||
}
|
||||
}
|
||||
@@ -1661,46 +1613,30 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
private void windowWillMiniaturize() {
|
||||
logger.fine("windowWillMiniaturize");
|
||||
isIconifyAnimationActive = true;
|
||||
}
|
||||
|
||||
private void windowDidBecomeMain() {
|
||||
logger.fine("windowDidBecomeMain");
|
||||
lastBecomeMainTime = System.currentTimeMillis();
|
||||
checkBlockingAndOrder();
|
||||
}
|
||||
|
||||
private void windowWillEnterFullScreen() {
|
||||
isFullScreenAnimationOn = true;
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("windowWillEnterFullScreen: isFullScreenAnimationOn = {0}", isFullScreenAnimationOn);
|
||||
}
|
||||
}
|
||||
|
||||
private void windowDidEnterFullScreen() {
|
||||
isInFullScreen = true;
|
||||
isFullScreenAnimationOn = false;
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("windowWillEnterFullScreen: isFullScreenAnimationOn = {0} isInFullScreen = {1}",
|
||||
isFullScreenAnimationOn, isInFullScreen);
|
||||
}
|
||||
}
|
||||
|
||||
private void windowWillExitFullScreen() {
|
||||
isFullScreenAnimationOn = true;
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("windowWillExitFullScreen: isFullScreenAnimationOn = {0}", isFullScreenAnimationOn);
|
||||
}
|
||||
}
|
||||
|
||||
private void windowDidExitFullScreen() {
|
||||
isInFullScreen = false;
|
||||
isFullScreenAnimationOn = false;
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
logger.fine("windowDidExitFullScreen: isFullScreenAnimationOn = {0} isInFullScreen = {1}",
|
||||
isFullScreenAnimationOn, isInFullScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@JBRApi.Provides("java.awt.Window.CustomTitleBarPeer#update")
|
||||
|
||||
@@ -815,33 +815,16 @@ public final class LWCToolkit extends LWToolkit {
|
||||
public static void invokeAndWait(Runnable runnable, Component component)
|
||||
throws InvocationTargetException
|
||||
{
|
||||
invokeAndWait(runnable, component, false, 0.0);
|
||||
invokeAndWait(runnable, component, -1);
|
||||
}
|
||||
|
||||
/* 25.01.25: keep public methods with (int timeoutSeconds) */
|
||||
@Deprecated(since = "25")
|
||||
public static void invokeAndWait(Runnable runnable, Component component, int timeoutSeconds)
|
||||
throws InvocationTargetException
|
||||
{
|
||||
invokeAndWait(runnable, component, false, timeoutSeconds);
|
||||
}
|
||||
|
||||
@Deprecated(since = "25")
|
||||
public static void invokeAndWait(Runnable runnable, Component component, boolean processEvents, int timeoutSeconds)
|
||||
throws InvocationTargetException {
|
||||
final double timeout = (timeoutSeconds > 0) ? timeoutSeconds : 0.0;
|
||||
invokeAndWait(runnable, component, processEvents, timeout);
|
||||
}
|
||||
|
||||
/* 25.01.25: added public methods with (double timeoutSeconds) to have timeouts between 0.0 and 1.0 */
|
||||
|
||||
public static void invokeAndWait(Runnable runnable, Component component, double timeoutSeconds)
|
||||
throws InvocationTargetException
|
||||
{
|
||||
invokeAndWait(runnable, component, false, timeoutSeconds);
|
||||
}
|
||||
|
||||
public static void invokeAndWait(Runnable runnable, Component component, boolean processEvents, double timeoutSeconds)
|
||||
throws InvocationTargetException
|
||||
{
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
@@ -905,10 +888,6 @@ public final class LWCToolkit extends LWToolkit {
|
||||
|
||||
private static native boolean isBlockingEventDispatchThread();
|
||||
|
||||
static native String getThreadTraceContexts();
|
||||
|
||||
static native boolean isWithinPowerTransition();
|
||||
|
||||
public static void invokeLater(Runnable event, Component component)
|
||||
throws InvocationTargetException {
|
||||
Objects.requireNonNull(component, "Null component provided to invokeLater");
|
||||
@@ -1094,13 +1073,13 @@ public final class LWCToolkit extends LWToolkit {
|
||||
* if false - all events come after exit form the nested loop
|
||||
*/
|
||||
static void doAWTRunLoop(long mediator, boolean processEvents) {
|
||||
doAWTRunLoop(mediator, processEvents, 0.0);
|
||||
doAWTRunLoop(mediator, processEvents, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts run-loop with the provided timeout. Use (<=0.0) for the infinite value.
|
||||
* Starts run-loop with the provided timeout. Use (-1) for the infinite value.
|
||||
*/
|
||||
static boolean doAWTRunLoop(long mediator, boolean processEvents, double timeoutSeconds) {
|
||||
static boolean doAWTRunLoop(long mediator, boolean processEvents, int timeoutSeconds) {
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("doAWTRunLoop: enter: mediator: " + mediator + " processEvents: "+ processEvents + " timeoutSeconds: " + timeoutSeconds);
|
||||
}
|
||||
@@ -1112,7 +1091,7 @@ public final class LWCToolkit extends LWToolkit {
|
||||
}
|
||||
}
|
||||
}
|
||||
private static native boolean doAWTRunLoopImpl(long mediator, boolean processEvents, boolean inAWT, double timeoutSeconds);
|
||||
private static native boolean doAWTRunLoopImpl(long mediator, boolean processEvents, boolean inAWT, int timeoutSeconds);
|
||||
static native void stopAWTRunLoop(long mediator);
|
||||
|
||||
private native boolean nativeSyncQueue(long timeout);
|
||||
|
||||
@@ -145,8 +145,6 @@ canCreateDirectories:(BOOL)inCreateDirectories
|
||||
UTType *contentType = [UTType typeWithFilenameExtension:fileType conformingToType:UTTypeData];
|
||||
if (contentType != nil) {
|
||||
[contentTypes addObject:contentType];
|
||||
} else if (fileType.length == 0) {
|
||||
[contentTypes addObject:UTTypeUnixExecutable];
|
||||
}
|
||||
}
|
||||
[thePanel setAllowedContentTypes:contentTypes];
|
||||
|
||||
@@ -35,8 +35,6 @@
|
||||
#define DEFAULT_DEVICE_HEIGHT 768
|
||||
#define DEFAULT_DEVICE_DPI 72
|
||||
|
||||
#define TRACE_DISPLAY_CHANGE_CONF 0
|
||||
|
||||
static NSInteger architecture = -1;
|
||||
/*
|
||||
* Convert the mode string to the more convenient bits per pixel value
|
||||
@@ -62,9 +60,6 @@ int getBPPFromModeString(CFStringRef mode)
|
||||
}
|
||||
|
||||
static BOOL isValidDisplayMode(CGDisplayModeRef mode) {
|
||||
if (!CGDisplayModeIsUsableForDesktopGUI(mode)) {
|
||||
return NO;
|
||||
}
|
||||
// Workaround for apple bug FB13261205, since it only affects arm based macs
|
||||
// and arm support started with macOS 11 ignore the workaround for previous versions
|
||||
if (@available(macOS 11, *)) {
|
||||
@@ -78,36 +73,18 @@ static BOOL isValidDisplayMode(CGDisplayModeRef mode) {
|
||||
return (1 < CGDisplayModeGetWidth(mode) && 1 < CGDisplayModeGetHeight(mode));
|
||||
}
|
||||
|
||||
static CFDictionaryRef getDisplayModesOptions() {
|
||||
// note: this dictionnary is never released:
|
||||
static CFDictionaryRef options = NULL;
|
||||
if (options == NULL) {
|
||||
CFStringRef keys[1] = { kCGDisplayShowDuplicateLowResolutionModes };
|
||||
CFBooleanRef values[1] = { kCFBooleanTrue };
|
||||
options = CFDictionaryCreate(kCFAllocatorDefault, (const void**) keys, (const void**) values, 1,
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
static CFMutableArrayRef getAllValidDisplayModes(jint displayID) {
|
||||
// Use the options dictionnary to get low resolution modes (scaled):
|
||||
static CFMutableArrayRef getAllValidDisplayModes(jint displayID){
|
||||
// CGDisplayCopyAllDisplayModes can return NULL if displayID is invalid
|
||||
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(displayID, getDisplayModesOptions());
|
||||
CFMutableArrayRef validModes = NULL;
|
||||
if (allModes != NULL) {
|
||||
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(displayID, NULL);
|
||||
CFMutableArrayRef validModes = nil;
|
||||
if (allModes) {
|
||||
CFIndex numModes = CFArrayGetCount(allModes);
|
||||
validModes = CFArrayCreateMutable(kCFAllocatorDefault, numModes + 1, &kCFTypeArrayCallBacks);
|
||||
|
||||
CFIndex n;
|
||||
for (n=0; n < numModes; n++) {
|
||||
CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
|
||||
if ((cRef != NULL) && isValidDisplayMode(cRef)) {
|
||||
if (TRACE_DISPLAY_CHANGE_CONF) {
|
||||
NSLog(@"getAllValidDisplayModes[%d]: w=%d, h=%d, freq=%.2lf hz", displayID,
|
||||
(int)CGDisplayModeGetWidth(cRef), (int)CGDisplayModeGetHeight(cRef),
|
||||
CGDisplayModeGetRefreshRate(cRef));
|
||||
}
|
||||
if (cRef != NULL && isValidDisplayMode(cRef)) {
|
||||
CFArrayAppendValue(validModes, cRef);
|
||||
}
|
||||
}
|
||||
@@ -115,11 +92,11 @@ static CFMutableArrayRef getAllValidDisplayModes(jint displayID) {
|
||||
|
||||
// CGDisplayCopyDisplayMode can return NULL if displayID is invalid
|
||||
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(displayID);
|
||||
if (currentMode != NULL) {
|
||||
if (currentMode) {
|
||||
BOOL containsCurrentMode = NO;
|
||||
numModes = CFArrayGetCount(validModes);
|
||||
for (n=0; n < numModes; n++) {
|
||||
if(CFArrayGetValueAtIndex(validModes, n) == currentMode) {
|
||||
if(CFArrayGetValueAtIndex(validModes, n) == currentMode){
|
||||
containsCurrentMode = YES;
|
||||
break;
|
||||
}
|
||||
@@ -130,6 +107,7 @@ static CFMutableArrayRef getAllValidDisplayModes(jint displayID) {
|
||||
CGDisplayModeRelease(currentMode);
|
||||
}
|
||||
}
|
||||
|
||||
return validModes;
|
||||
}
|
||||
|
||||
@@ -183,17 +161,16 @@ static jobject createJavaDisplayMode(CGDisplayModeRef mode, JNIEnv *env) {
|
||||
jobject ret = NULL;
|
||||
jint h = DEFAULT_DEVICE_HEIGHT, w = DEFAULT_DEVICE_WIDTH, bpp = 0, refrate = 0;
|
||||
JNI_COCOA_ENTER(env);
|
||||
BOOL isDisplayModeDefault = NO;
|
||||
if (mode) {
|
||||
CFStringRef currentBPP = CGDisplayModeCopyPixelEncoding(mode);
|
||||
bpp = getBPPFromModeString(currentBPP);
|
||||
CFRelease(currentBPP);
|
||||
refrate = CGDisplayModeGetRefreshRate(mode);
|
||||
h = CGDisplayModeGetHeight(mode);
|
||||
w = CGDisplayModeGetWidth(mode);
|
||||
uint32_t flags = CGDisplayModeGetIOFlags(mode);
|
||||
isDisplayModeDefault = (flags & kDisplayModeDefaultFlag) ? YES : NO;
|
||||
CFRelease(currentBPP);
|
||||
}
|
||||
uint32_t flags = CGDisplayModeGetIOFlags(mode);
|
||||
BOOL isDisplayModeDefault = (flags & kDisplayModeDefaultFlag) ? YES : NO;
|
||||
DECLARE_CLASS_RETURN(jc_DisplayMode, "java/awt/DisplayMode", ret);
|
||||
DECLARE_METHOD_RETURN(jc_DisplayMode_ctor, jc_DisplayMode, "<init>", "(IIIIZ)V", ret);
|
||||
ret = (*env)->NewObject(env, jc_DisplayMode, jc_DisplayMode_ctor, w, h, bpp, refrate, (jboolean)isDisplayModeDefault);
|
||||
@@ -331,71 +308,32 @@ JNIEXPORT void JNICALL
|
||||
Java_sun_awt_CGraphicsDevice_nativeSetDisplayMode
|
||||
(JNIEnv *env, jclass class, jint displayID, jint w, jint h, jint bpp, jint refrate)
|
||||
{
|
||||
CGError retCode = kCGErrorSuccess;
|
||||
JNI_COCOA_ENTER(env);
|
||||
CFArrayRef allModes = getAllValidDisplayModes(displayID);
|
||||
CGDisplayModeRef closestMatch = getBestModeForParameters(allModes, (int)w, (int)h, (int)bpp, (int)refrate);
|
||||
|
||||
JNI_COCOA_ENTER(env);
|
||||
// global lock to ensure only 1 display change transaction at the same time:
|
||||
static NSLock* configureDisplayLock;
|
||||
static dispatch_once_t oncePredicate;
|
||||
|
||||
dispatch_once(&oncePredicate, ^{
|
||||
configureDisplayLock = [[NSLock alloc] init];
|
||||
});
|
||||
|
||||
@try {
|
||||
// Avoid reentrance and ensure consistency between the best mode and ConfigureDisplay transaction:
|
||||
[configureDisplayLock lock];
|
||||
|
||||
if (TRACE_DISPLAY_CHANGE_CONF) {
|
||||
NSLog(@"nativeSetDisplayMode: displayID: %d w:%d h:%d bpp: %d refrate:%d", displayID, w, h, bpp, refrate);
|
||||
}
|
||||
CFArrayRef allModes = getAllValidDisplayModes(displayID);
|
||||
CGDisplayModeRef closestMatch = getBestModeForParameters(allModes, (int)w, (int)h, (int)bpp, (int)refrate);
|
||||
|
||||
if (closestMatch != NULL) {
|
||||
/*
|
||||
* 2025.01: Do not call the following DisplayConfiguration transaction on
|
||||
* main thread as it hangs for several seconds on macbook intel + macOS 15
|
||||
*/
|
||||
__block CGError retCode = kCGErrorSuccess;
|
||||
if (closestMatch != NULL) {
|
||||
CGDisplayModeRetain(closestMatch);
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
|
||||
CGDisplayConfigRef config;
|
||||
retCode = CGBeginDisplayConfiguration(&config);
|
||||
if (TRACE_DISPLAY_CHANGE_CONF) {
|
||||
NSLog(@"nativeSetDisplayMode: CGBeginDisplayConfiguration = %d", retCode);
|
||||
}
|
||||
|
||||
if (retCode == kCGErrorSuccess) {
|
||||
retCode = CGConfigureDisplayWithDisplayMode(config, displayID, closestMatch, NULL);
|
||||
if (TRACE_DISPLAY_CHANGE_CONF) {
|
||||
NSLog(@"nativeSetDisplayMode: CGConfigureDisplayWithDisplayMode = %d", retCode);
|
||||
}
|
||||
|
||||
if (retCode == kCGErrorSuccess) {
|
||||
retCode = CGCompleteDisplayConfiguration(config, kCGConfigureForAppOnly);
|
||||
if (TRACE_DISPLAY_CHANGE_CONF) {
|
||||
NSLog(@"nativeSetDisplayMode: CGCompleteDisplayConfiguration = %d", retCode);
|
||||
}
|
||||
} else {
|
||||
int retCode2 = CGCancelDisplayConfiguration(config);
|
||||
if (TRACE_DISPLAY_CHANGE_CONF) {
|
||||
NSLog(@"nativeSetDisplayMode: CGCancelDisplayConfiguration = %d", retCode2);
|
||||
}
|
||||
}
|
||||
CGConfigureDisplayWithDisplayMode(config, displayID, closestMatch, NULL);
|
||||
retCode = CGCompleteDisplayConfiguration(config, kCGConfigureForAppOnly);
|
||||
}
|
||||
} else {
|
||||
JNU_ThrowIllegalArgumentException(env, "Invalid display mode");
|
||||
}
|
||||
if (allModes) {
|
||||
CFRelease(allModes);
|
||||
}
|
||||
} @finally {
|
||||
[configureDisplayLock unlock];
|
||||
CGDisplayModeRelease(closestMatch);
|
||||
}];
|
||||
} else {
|
||||
JNU_ThrowIllegalArgumentException(env, "Invalid display mode");
|
||||
}
|
||||
if (retCode != kCGErrorSuccess) {
|
||||
|
||||
if (retCode != kCGErrorSuccess){
|
||||
JNU_ThrowIllegalArgumentException(env, "Unable to set display mode!");
|
||||
}
|
||||
JNI_COCOA_EXIT(env);
|
||||
CFRelease(allModes);
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_awt_CGraphicsDevice
|
||||
* Method: nativeGetDisplayMode
|
||||
@@ -406,13 +344,10 @@ Java_sun_awt_CGraphicsDevice_nativeGetDisplayMode
|
||||
(JNIEnv *env, jclass class, jint displayID)
|
||||
{
|
||||
jobject ret = NULL;
|
||||
|
||||
JNI_COCOA_ENTER(env);
|
||||
// CGDisplayCopyDisplayMode can return NULL if displayID is invalid
|
||||
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(displayID);
|
||||
ret = createJavaDisplayMode(currentMode, env);
|
||||
CGDisplayModeRelease(currentMode);
|
||||
JNI_COCOA_EXIT(env);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -426,7 +361,6 @@ Java_sun_awt_CGraphicsDevice_nativeGetDisplayModes
|
||||
(JNIEnv *env, jclass class, jint displayID)
|
||||
{
|
||||
jobjectArray jreturnArray = NULL;
|
||||
|
||||
JNI_COCOA_ENTER(env);
|
||||
CFArrayRef allModes = getAllValidDisplayModes(displayID);
|
||||
|
||||
@@ -436,20 +370,21 @@ Java_sun_awt_CGraphicsDevice_nativeGetDisplayModes
|
||||
jreturnArray = (*env)->NewObjectArray(env, (jsize)numModes, jc_DisplayMode, NULL);
|
||||
if (!jreturnArray) {
|
||||
NSLog(@"CGraphicsDevice can't create java array of DisplayMode objects");
|
||||
} else {
|
||||
CFIndex n;
|
||||
for (n = 0; n < numModes; n++) {
|
||||
CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
|
||||
if (cRef != NULL) {
|
||||
jobject oneMode = createJavaDisplayMode(cRef, env);
|
||||
(*env)->SetObjectArrayElement(env, jreturnArray, n, oneMode);
|
||||
if ((*env)->ExceptionOccurred(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
(*env)->ExceptionClear(env);
|
||||
continue;
|
||||
}
|
||||
(*env)->DeleteLocalRef(env, oneMode);
|
||||
return nil;
|
||||
}
|
||||
|
||||
CFIndex n;
|
||||
for (n=0; n < numModes; n++) {
|
||||
CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
|
||||
if (cRef != NULL) {
|
||||
jobject oneMode = createJavaDisplayMode(cRef, env);
|
||||
(*env)->SetObjectArrayElement(env, jreturnArray, n, oneMode);
|
||||
if ((*env)->ExceptionOccurred(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
(*env)->ExceptionClear(env);
|
||||
continue;
|
||||
}
|
||||
(*env)->DeleteLocalRef(env, oneMode);
|
||||
}
|
||||
}
|
||||
if (allModes) {
|
||||
|
||||
@@ -214,9 +214,4 @@ JNIEXPORT jint JNICALL Java_sun_awt_CGraphicsEnvironment_initMetal
|
||||
|
||||
JNI_COCOA_EXIT(env);
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_sun_awt_CGraphicsEnvironment_isBuiltinDisplayNative
|
||||
(JNIEnv *env, jclass cgenv, jint displayId) {
|
||||
return CGDisplayIsBuiltin(displayId);
|
||||
}
|
||||
}
|
||||
@@ -61,12 +61,6 @@
|
||||
/* RunLoop critical run max duration = 1 millis */
|
||||
#define RUN_LOOP_TICK_CRITICAL (0.001)
|
||||
|
||||
/* max wait timeout for AWTRunLoop > 10s */
|
||||
#define WAIT_TIMEOUT_LIMIT (13.333)
|
||||
|
||||
/* power transition period = 10s */
|
||||
#define PWM_TRANSITION_PERIOD (10.000)
|
||||
|
||||
#define TRACE_RUN_LOOP 0
|
||||
|
||||
int gNumberOfButtons;
|
||||
@@ -599,7 +593,7 @@ JNI_COCOA_EXIT(env);
|
||||
* Signature: (JZZI)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_LWCToolkit_doAWTRunLoopImpl
|
||||
(JNIEnv *env, jclass clz, jlong mediator, jboolean processEvents, jboolean inAWT, jdouble timeoutSeconds/*(<=0.0>) for infinite*/)
|
||||
(JNIEnv *env, jclass clz, jlong mediator, jboolean processEvents, jboolean inAWT, jint timeoutSeconds/*(-1) for infinite*/)
|
||||
{
|
||||
AWT_ASSERT_APPKIT_THREAD;
|
||||
jboolean result = JNI_TRUE;
|
||||
@@ -611,19 +605,14 @@ JNI_COCOA_ENTER(env);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* 2025.02: infinite timeout means possible deadlocks or freezes may happen.
|
||||
* To ensure responsiveness, infinite is limited to a huge delay (~10s)
|
||||
*/
|
||||
if (timeoutSeconds < WAIT_TIMEOUT_LIMIT) {
|
||||
timeoutSeconds = WAIT_TIMEOUT_LIMIT;
|
||||
NSDate *timeoutDate = timeoutSeconds > 0 ? [NSDate dateWithTimeIntervalSinceNow:timeoutSeconds] : nil;
|
||||
if (timeoutDate != nil) {
|
||||
if (TRACE_RUN_LOOP) NSLog(@"LWCToolkit_doAWTRunLoopImpl: timeoutDate = %s",
|
||||
[[timeoutDate description] UTF8String]);
|
||||
}
|
||||
|
||||
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSeconds];
|
||||
if (TRACE_RUN_LOOP) {
|
||||
NSLog(@"LWCToolkit_doAWTRunLoopImpl: timeoutDate = %s", [[timeoutDate description] UTF8String]);
|
||||
NSLog(@"LWCToolkit_doAWTRunLoopImpl: processEvents = %d", processEvents);
|
||||
}
|
||||
if (TRACE_RUN_LOOP) NSLog(@"LWCToolkit_doAWTRunLoopImpl: processEvents = %d", processEvents);
|
||||
|
||||
NSRunLoopMode criticalRunMode = [ThreadUtilities criticalRunLoopMode];
|
||||
NSRunLoopMode runMode = inAWT ? [ThreadUtilities javaRunLoopMode] : NSDefaultRunLoopMode;
|
||||
if (TRACE_RUN_LOOP) NSLog(@"LWCToolkit_doAWTRunLoopImpl: runMode = %@", runMode);
|
||||
@@ -650,13 +639,16 @@ JNI_COCOA_ENTER(env);
|
||||
[deadlineDate release];
|
||||
if (TRACE_RUN_LOOP) NSLog(@"LWCToolkit_doAWTRunLoopImpl: isRunning = %d", isRunning);
|
||||
|
||||
NSDate *now = [[NSDate alloc] init];
|
||||
if ([timeoutDate compare:(now)] == NSOrderedAscending) {
|
||||
if (timeoutDate != nil) {
|
||||
NSDate *now = [[NSDate alloc] init];
|
||||
if ([timeoutDate compare:(now)] == NSOrderedAscending) {
|
||||
result = JNI_FALSE;
|
||||
}
|
||||
[now release];
|
||||
result = JNI_FALSE;
|
||||
break;
|
||||
if (result == JNI_FALSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
[now release];
|
||||
|
||||
if (processEvents) {
|
||||
// We do not spin a runloop here as date is nil, so does not matter which mode to use
|
||||
@@ -677,8 +669,7 @@ JNI_COCOA_ENTER(env);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // while loop
|
||||
|
||||
}
|
||||
[timeoutDate release];
|
||||
[mediatorObject release];
|
||||
|
||||
@@ -699,39 +690,6 @@ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_LWCToolkit_isBlockingEventDispa
|
||||
return ThreadUtilities.blockingEventDispatchThread;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_lwawt_macosx_LWCToolkit
|
||||
* Method: getThreadTraceContexts
|
||||
* Signature: (Ljava/lang/String)
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_sun_lwawt_macosx_LWCToolkit_getThreadTraceContexts
|
||||
(JNIEnv *env, jclass clz)
|
||||
{
|
||||
JNI_COCOA_ENTER(env);
|
||||
|
||||
// Convert NSString* to JavaString
|
||||
NSString* result = [ThreadUtilities getThreadTraceContexts];
|
||||
|
||||
jstring javaString = (*env)->NewStringUTF(env, result.UTF8String);
|
||||
[result release];
|
||||
return javaString;
|
||||
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_lwawt_macosx_LWCToolkit
|
||||
* Method: isWithinPowerTransition
|
||||
* Signature: ()Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_LWCToolkit_isWithinPowerTransition
|
||||
(JNIEnv *env, jclass clz)
|
||||
{
|
||||
JNI_COCOA_ENTER(env);
|
||||
return [ThreadUtilities isWithinPowerTransition:PWM_TRANSITION_PERIOD] ? JNI_TRUE : JNI_FALSE;
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_lwawt_macosx_LWCToolkit
|
||||
* Method: stopAWTRunLoop
|
||||
|
||||
@@ -164,34 +164,6 @@ static jmethodID sjm_getAccessibleEditableText = NULL;
|
||||
return string;
|
||||
}
|
||||
|
||||
- (void)setAccessibilityValue:(id)value {
|
||||
if (![value isKindOfClass:[NSString class]]) return;
|
||||
|
||||
NSString *stringValue = (NSString *) value;
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
jstring jstringValue = NSStringToJavaString(env, stringValue);
|
||||
GET_CACCESSIBLETEXT_CLASS();
|
||||
DECLARE_STATIC_METHOD(jm_setText, sjc_CAccessibleText, "setText",
|
||||
"(Ljavax/accessibility/Accessible;Ljava/awt/Component;Ljava/lang/String;)V");
|
||||
(*env)->CallStaticVoidMethod(env, sjc_CAccessibleText, jm_setText, fAccessible, fComponent, jstringValue);
|
||||
CHECK_EXCEPTION();
|
||||
}
|
||||
|
||||
- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector {
|
||||
if (selector == @selector(setAccessibilityValue:)) {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
GET_CACCESSIBLETEXT_CLASS_RETURN(NO);
|
||||
DECLARE_STATIC_METHOD_RETURN(sjm_isSetAccessibilityValueAllowed, sjc_CAccessibleText,
|
||||
"isSetAccessibilityValueAllowed",
|
||||
"(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Z", NO);
|
||||
BOOL isAllowed = (*env)->CallStaticBooleanMethod(env, sjc_CAccessibleText, sjm_isSetAccessibilityValueAllowed,
|
||||
fAccessible, fComponent);
|
||||
CHECK_EXCEPTION();
|
||||
return isAllowed;
|
||||
}
|
||||
return [super isAccessibilitySelectorAllowed:selector];
|
||||
}
|
||||
|
||||
- (NSAccessibilitySubrole)accessibilitySubrole {
|
||||
if ([self accessibleIsPasswordText]) {
|
||||
return NSAccessibilitySecureTextFieldSubrole;
|
||||
|
||||
@@ -104,81 +104,6 @@ static jboolean MTLSurfaceData_initTexture(BMTLSDOps *bmtlsdo, jboolean isOpaque
|
||||
}
|
||||
}
|
||||
|
||||
static jboolean MTLSurfaceData_initWithTexture(BMTLSDOps *bmtlsdo, jboolean isOpaque, void* pTexture) {
|
||||
@autoreleasepool {
|
||||
if (bmtlsdo == NULL) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initWithTexture: ops are null");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (pTexture == NULL) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initWithTexture: texture is null");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
id <MTLTexture> texture = (__bridge id <MTLTexture>) pTexture;
|
||||
if (texture == NULL) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initWithTexture: failed to cast texture to MTLTexture");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (texture.width >= MTL_GPU_FAMILY_MAC_TXT_SIZE || texture.height >= MTL_GPU_FAMILY_MAC_TXT_SIZE ||
|
||||
texture.width == 0 || texture.height == 0) {
|
||||
J2dRlsTraceLn2(J2D_TRACE_ERROR, "MTLSurfaceData_initWithTexture: wrong texture size %d x %d",
|
||||
texture.width, texture.height);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (texture.pixelFormat != MTLPixelFormatBGRA8Unorm) {
|
||||
J2dRlsTraceLn1(J2D_TRACE_ERROR, "MTLSurfaceData_initWithTexture: unsupported pixel format: %d",
|
||||
texture.pixelFormat);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
bmtlsdo->pTexture = texture;
|
||||
bmtlsdo->pOutTexture = NULL;
|
||||
|
||||
MTLSDOps *mtlsdo = (MTLSDOps *)bmtlsdo->privOps;
|
||||
if (mtlsdo == NULL) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initWithTexture: MTLSDOps are null");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (mtlsdo->configInfo == NULL || mtlsdo->configInfo->context == NULL) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLSurfaceData_initWithTexture: MTLSDOps wasn't initialized (context is null)");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
MTLContext* ctx = mtlsdo->configInfo->context;
|
||||
MTLTextureDescriptor *stencilDataDescriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Uint
|
||||
width:texture.width
|
||||
height:texture.height
|
||||
mipmapped:NO];
|
||||
stencilDataDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
stencilDataDescriptor.storageMode = MTLStorageModePrivate;
|
||||
bmtlsdo->pStencilData = [ctx.device newTextureWithDescriptor:stencilDataDescriptor];
|
||||
|
||||
MTLTextureDescriptor *stencilTextureDescriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatStencil8
|
||||
width:texture.width
|
||||
height:texture.height
|
||||
mipmapped:NO];
|
||||
stencilTextureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
|
||||
stencilTextureDescriptor.storageMode = MTLStorageModePrivate;
|
||||
bmtlsdo->pStencilTexture = [ctx.device newTextureWithDescriptor:stencilTextureDescriptor];
|
||||
|
||||
bmtlsdo->isOpaque = isOpaque;
|
||||
bmtlsdo->width = texture.width;
|
||||
bmtlsdo->height = texture.height;
|
||||
bmtlsdo->drawableType = MTLSD_RT_TEXTURE;
|
||||
|
||||
[texture retain];
|
||||
|
||||
J2dTraceLn6(J2D_TRACE_VERBOSE, "MTLSurfaceData_initTexture: w=%d h=%d bp=%p [tex=%p] opaque=%d sfType=%d",
|
||||
bmtlsdo->width, bmtlsdo->height, bmtlsdo, bmtlsdo->pTexture, isOpaque, bmtlsdo->drawableType);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an MTL texture, using the given width and height as
|
||||
* a guide.
|
||||
@@ -195,19 +120,6 @@ Java_sun_java2d_metal_MTLSurfaceData_initTexture(
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_sun_java2d_metal_MTLSurfaceData_initWithTexture(
|
||||
JNIEnv *env, jobject mtlds,
|
||||
jlong pData, jboolean isOpaque,
|
||||
jlong pTexture) {
|
||||
BMTLSDOps *bmtlsdops = (BMTLSDOps *) pData;
|
||||
if (!MTLSurfaceData_initWithTexture(bmtlsdops, isOpaque, jlong_to_ptr(pTexture))) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
MTLSD_SetNativeDimensions(env, (BMTLSDOps *) pData, bmtlsdops->width, bmtlsdops->height);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a framebuffer object, using the given width and height as
|
||||
* a guide. See MTLSD_InitTextureObject() and MTLSD_initRTexture()
|
||||
|
||||
@@ -128,24 +128,24 @@ do { \
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
@interface ThreadTraceContext : NSObject <NSCopying>
|
||||
@property (readwrite, atomic) BOOL sleep;
|
||||
@property (readwrite, atomic) BOOL useJavaModes;
|
||||
@property (readwrite, atomic) long actionId;
|
||||
@property (readwrite, atomic) char* operation;
|
||||
@property (readwrite, atomic) CFTimeInterval timestamp;
|
||||
@property (readwrite, atomic, retain) NSString* threadName;
|
||||
@property (readwrite, atomic, retain) NSString* caller;
|
||||
@property (readwrite, atomic, retain) NSString* callStack;
|
||||
|
||||
/* autorelease in init and copy */
|
||||
- (id)init;
|
||||
- (void)reset;
|
||||
- (void)updateThreadState:(BOOL)sleepValue;
|
||||
@property (readwrite, atomic) BOOL sleep;
|
||||
@property (readwrite, atomic) BOOL useJavaModes;
|
||||
@property (readwrite, atomic) long actionId;
|
||||
@property (readwrite, atomic) char* operation;
|
||||
@property (readwrite, atomic) CFTimeInterval timestamp;
|
||||
@property (readwrite, atomic, retain) NSString* caller;
|
||||
@property (readwrite, atomic, retain) NSString* callStack;
|
||||
|
||||
- (void)set:(long)pActionId operation:(char*)pOperation useJavaModes:(BOOL)pUseJavaModes
|
||||
caller:(NSString *)pCaller callstack:(NSString *)pCallStack;
|
||||
/* autorelease in init and copy */
|
||||
- (id)init;
|
||||
- (void)reset;
|
||||
- (void)updateThreadState:(BOOL)sleepValue;
|
||||
|
||||
- (const char*)identifier;
|
||||
- (id)set:(long)pActionId operation:(char*)pOperation useJavaModes:(BOOL)pUseJavaModes
|
||||
caller:(NSString *)pCaller callstack:(NSString *)pCallStack;
|
||||
|
||||
- (const char*)identifier;
|
||||
@end
|
||||
|
||||
|
||||
@@ -183,12 +183,13 @@ __attribute__((visibility("default")))
|
||||
+ (ThreadTraceContext*)recordTraceContext:(NSString*)prefix;
|
||||
+ (ThreadTraceContext*)recordTraceContext:(NSString*)prefix actionId:(long)actionId useJavaModes:(BOOL)useJavaModes operation:(char*) operation;
|
||||
|
||||
+ (void)dumpThreadTraceContext;
|
||||
|
||||
+ (NSString*)getThreadTraceContexts;
|
||||
|
||||
+ (void)registerForSystemAndScreenNotifications;
|
||||
+ (BOOL)isWithinPowerTransition:(double)periodInSeconds;
|
||||
+ (BOOL)isWithinPowerTransition;
|
||||
|
||||
+ (BOOL)nanoUpTime:(atomic_uint_least64_t*)nanotime;
|
||||
+ (BOOL)nowNearTime:(NSString*)src refTime:(atomic_uint_least64_t*)refTime;
|
||||
@end
|
||||
|
||||
JNIEXPORT void OSXAPP_SetJavaVM(JavaVM *vm);
|
||||
|
||||
@@ -31,15 +31,6 @@
|
||||
#import "ThreadUtilities.h"
|
||||
|
||||
|
||||
#define RUN_BLOCK_IF(COND, block) \
|
||||
if ((COND)) { \
|
||||
block(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define RUN_BLOCK_IF_MAIN(block) \
|
||||
RUN_BLOCK_IF([NSThread isMainThread], block)
|
||||
|
||||
/* Returns the MainThread latency threshold in milliseconds
|
||||
* used to detect slow operations that may cause high latencies or delays.
|
||||
* If negative, the MainThread monitor is disabled */
|
||||
@@ -76,11 +67,9 @@ static NSArray<NSString*> *javaModes = nil;
|
||||
static NSArray<NSString*> *allModesExceptJava = nil;
|
||||
|
||||
/* Traceability data */
|
||||
static const BOOL forceTracing = NO;
|
||||
static const BOOL enableTracing = NO || forceTracing;
|
||||
static const BOOL enableTracingLog = YES && enableTracing;
|
||||
static const BOOL enableCallStacks = YES && enableTracing;
|
||||
|
||||
static const BOOL enableTracing = NO;
|
||||
static const BOOL enableTracingLog = NO;
|
||||
static const BOOL enableCallStacks = NO;
|
||||
static const BOOL enableRunLoopObserver = NO;
|
||||
|
||||
/* Traceability data */
|
||||
@@ -88,8 +77,10 @@ static const BOOL TRACE_PWM = NO;
|
||||
static const BOOL TRACE_PWM_EVENTS = NO;
|
||||
static const BOOL TRACE_CLOCKS = NO;
|
||||
|
||||
/* 10s period arround reference times (sleep/wake-up...)
|
||||
* to ensure all displays are awaken properly */
|
||||
static const uint64_t NANOS_PER_SEC = 1000000000ULL;
|
||||
static const double SEC_PER_NANOS = 1e9;
|
||||
static const uint64_t RDV_PERIOD = 10ULL * NANOS_PER_SEC;
|
||||
|
||||
/* RunLoop traceability identifier generators */
|
||||
static atomic_long runLoopId = 0L;
|
||||
@@ -98,48 +89,6 @@ static atomic_long mainThreadActionId = 0L;
|
||||
static atomic_uint_least64_t sleepTime = 0LL;
|
||||
static atomic_uint_least64_t wakeUpTime = 0LL;
|
||||
|
||||
bool _getTime_nanos(clockid_t clock_id, atomic_uint_least64_t *nanotime) {
|
||||
struct timespec tp;
|
||||
// Use the given clock:
|
||||
int status = clock_gettime(clock_id, &tp);
|
||||
if (status != 0) {
|
||||
return false;
|
||||
}
|
||||
*nanotime = tp.tv_sec * NANOS_PER_SEC + tp.tv_nsec;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _nanoUpTime(atomic_uint_least64_t *nanotime) {
|
||||
// Use a monotonic clock (linearly increasing by each tick)
|
||||
// but not counting the time while sleeping.
|
||||
// NOTE:CLOCK_UPTIME_RAW seems counting more elapsed time
|
||||
// arround sleep/wake-up cycle than CLOCK_PROCESS_CPUTIME_ID (adopted):
|
||||
return _getTime_nanos(CLOCK_PROCESS_CPUTIME_ID, nanotime);
|
||||
}
|
||||
|
||||
static inline void doLog(JNIEnv* env, const char *formatMsg, ...) {
|
||||
if (forceTracing) {
|
||||
va_list args;
|
||||
va_start(args, formatMsg);
|
||||
|
||||
/* formatted message can be large (stack trace ?) => 16 kb */
|
||||
const int bufSize = 16 * 1024;
|
||||
char buf[bufSize];
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wformat-nonliteral"
|
||||
vsnprintf(buf, bufSize, formatMsg, args);
|
||||
#pragma clang diagnostic pop
|
||||
va_end(args);
|
||||
/* use NSLog to get timestamp + outputs in console and stderr */
|
||||
NSLog(@"%s\n", buf);
|
||||
} else {
|
||||
va_list args;
|
||||
va_start(args, formatMsg);
|
||||
lwc_plog(env, formatMsg, args);
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static inline void attachCurrentThread(void** env) {
|
||||
if ([NSThread isMainThread]) {
|
||||
@@ -331,10 +280,12 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
+ (void)performOnMainThreadNowOrLater:(BOOL)useJavaModes
|
||||
block:(void (^)())block
|
||||
{
|
||||
RUN_BLOCK_IF_MAIN(block);
|
||||
|
||||
[ThreadUtilities performOnMainThread:@selector(invokeBlockCopy:) on:self withObject:Block_copy(block)
|
||||
waitUntilDone:NO useJavaModes:useJavaModes];
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
} else {
|
||||
[ThreadUtilities performOnMainThread:@selector(invokeBlockCopy:) on:self withObject:Block_copy(block)
|
||||
waitUntilDone:NO useJavaModes:useJavaModes];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -353,10 +304,12 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
useJavaModes:(BOOL)useJavaModes
|
||||
block:(void (^)())block
|
||||
{
|
||||
RUN_BLOCK_IF_MAIN(block);
|
||||
|
||||
[ThreadUtilities performOnMainThread:@selector(invokeBlockCopy:) on:self withObject:Block_copy(block)
|
||||
waitUntilDone:wait useJavaModes:useJavaModes];
|
||||
if ([NSThread isMainThread] && wait) {
|
||||
block();
|
||||
} else {
|
||||
[ThreadUtilities performOnMainThread:@selector(invokeBlockCopy:) on:self withObject:Block_copy(block)
|
||||
waitUntilDone:wait useJavaModes:useJavaModes];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -390,7 +343,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
{
|
||||
const int mtThreshold = getMainThreadLatencyThreshold();
|
||||
|
||||
if (!forceTracing && (!enableTracing || (mtThreshold < 0))) {
|
||||
if (mtThreshold < 0) {
|
||||
const BOOL invokeDirect = ([NSThread isMainThread] && wait);
|
||||
|
||||
// Fast Path:
|
||||
@@ -434,68 +387,71 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
waitUntilDone:(BOOL)wait
|
||||
useJavaModes:(BOOL)useJavaModes
|
||||
{
|
||||
const BOOL invokeDirect = NSThread.isMainThread && wait;
|
||||
const BOOL doWait = !invokeDirect && wait;
|
||||
const BOOL blockingEDT = doWait && isEventDispatchThread();
|
||||
|
||||
// Slow path:
|
||||
const int mtThreshold = getMainThreadLatencyThreshold();
|
||||
const bool doTrace = (enableTracing && doWait);
|
||||
|
||||
NSArray<NSString*> *runLoopModes = (useJavaModes) ? javaModes : allModesExceptJava;
|
||||
const BOOL invokeDirect = ([NSThread isMainThread] && wait);
|
||||
|
||||
// Perform instrumentation on selector:
|
||||
BOOL blockingEDT = NO;
|
||||
if (!invokeDirect && (wait && isEventDispatchThread())) {
|
||||
blockingEDT = YES;
|
||||
}
|
||||
/* Increment global main action id */
|
||||
long actionId = ++mainThreadActionId;
|
||||
|
||||
/* tracing info */
|
||||
JNIEnv* cenv = NULL;
|
||||
JNIEnv* env = NULL;
|
||||
ThreadTraceContext* callerCtx = nil;
|
||||
|
||||
if (doTrace) {
|
||||
if (enableTracing) {
|
||||
// Get current thread env:
|
||||
cenv = [ThreadUtilities getJNIEnvUncached];
|
||||
env = [ThreadUtilities getJNIEnvUncached];
|
||||
char* operation = (invokeDirect ? "now " : (blockingEDT ? "blocking" : "later"));
|
||||
|
||||
// Record thread stack now and return another copy (auto-released):
|
||||
callerCtx = [ThreadUtilities recordTraceContext:nil actionId:actionId useJavaModes:useJavaModes operation:operation];
|
||||
[callerCtx retain];
|
||||
|
||||
if (enableTracingLog) {
|
||||
doLog(cenv, "%s performOnMainThread[caller]: %s",
|
||||
[callerCtx identifier], toCString([callerCtx description]));
|
||||
lwc_plog(env, "%s performOnMainThread[caller]", toCString([callerCtx description]));
|
||||
if ([callerCtx callStack] != nil) {
|
||||
lwc_plog(env, "%s performOnMainThread[caller]: call stack:\n%s",
|
||||
[callerCtx identifier], toCString([callerCtx callStack]));
|
||||
}
|
||||
}
|
||||
|
||||
// will be released in blockCopy() later:
|
||||
[callerCtx retain];
|
||||
}
|
||||
|
||||
// will be released in blockCopy() later:
|
||||
[callerCtx retain];
|
||||
|
||||
void (^blockCopy)(void) = Block_copy(^(){
|
||||
AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
JNIEnv* renv = NULL;
|
||||
JNIEnv* runEnv = NULL;
|
||||
ThreadTraceContext* runCtx = nil;
|
||||
|
||||
if (doTrace) {
|
||||
if (enableTracing) {
|
||||
// Get current thread env:
|
||||
renv = [ThreadUtilities getJNIEnv];
|
||||
runEnv = [ThreadUtilities getJNIEnv];
|
||||
|
||||
// Record thread stack now and return another copy (auto-released):
|
||||
runCtx = [ThreadUtilities recordTraceContext];
|
||||
|
||||
if (enableTracingLog) {
|
||||
const double latencyMs = ([runCtx timestamp] - [callerCtx timestamp]) * 1000.0;
|
||||
doLog(renv, "%s performOnMainThread[blockCopy:before]: latency = %.5lf ms. Calling: [%s]",
|
||||
[callerCtx identifier], latencyMs, aSelector);
|
||||
doLog(renv, "%s performOnMainThread[blockCopy:before]: caller = %s",
|
||||
[callerCtx identifier], toCString([callerCtx description]));
|
||||
|
||||
if (false && [runCtx callStack] != nil) {
|
||||
doLog(renv, "%s performOnMainThread[blockCopy:before]: run stack:\n%s",
|
||||
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: latency = %.5lf ms. Calling: [%s]",
|
||||
[callerCtx identifier], latencyMs, aSelector);
|
||||
|
||||
if ([runCtx callStack] != nil) {
|
||||
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: run stack:\n%s",
|
||||
[callerCtx identifier], toCString([runCtx callStack]));
|
||||
}
|
||||
}
|
||||
}
|
||||
const CFTimeInterval start = (doTrace) ? CACurrentMediaTime() : 0.0;
|
||||
const CFTimeInterval start = (enableTracing) ? CACurrentMediaTime() : 0.0;
|
||||
@try {
|
||||
if (blockingEDT) {
|
||||
setBlockingEventDispatchThread(YES);
|
||||
@@ -505,14 +461,12 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
if (blockingEDT) {
|
||||
setBlockingEventDispatchThread(NO);
|
||||
}
|
||||
if (doTrace) {
|
||||
if (enableTracing) {
|
||||
if (enableTracingLog) {
|
||||
const double elapsedMs = (CACurrentMediaTime() - start) * 1000.0;
|
||||
if (doTrace || (elapsedMs > mtThreshold)) {
|
||||
doLog(renv, "%s performOnMainThread[blockCopy:after]: time = %.5lf ms. Called: [%s]",
|
||||
[callerCtx identifier], elapsedMs, aSelector);
|
||||
doLog(renv, "%s performOnMainThread[blockCopy:after]: caller = %s",
|
||||
[callerCtx identifier], toCString([callerCtx description]));
|
||||
if (elapsedMs > mtThreshold) {
|
||||
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: time = %.5lf ms. Caller=[%s]",
|
||||
[callerCtx identifier], elapsedMs, toCString([callerCtx caller]));
|
||||
}
|
||||
}
|
||||
[callerCtx release];
|
||||
@@ -526,20 +480,20 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
if (invokeDirect) {
|
||||
[ThreadUtilities invokeBlockCopy:blockCopy];
|
||||
} else {
|
||||
if (doTrace && enableTracingLog) {
|
||||
doLog(cenv, "%s performOnMainThread[caller]: waiting on MainThread(%s). Caller=[%s] [%s]",
|
||||
if (enableTracingLog) {
|
||||
lwc_plog(env, "%s performOnMainThread[caller]: waiting on MainThread(%s). Caller=[%s] [%s]",
|
||||
[callerCtx identifier], aSelector, toCString([callerCtx caller]),
|
||||
wait ? "WAIT" : "ASYNC");
|
||||
}
|
||||
|
||||
[ThreadUtilities performSelectorOnMainThread:@selector(invokeBlockCopy:) withObject:blockCopy waitUntilDone:wait modes:runLoopModes];
|
||||
|
||||
if (doTrace && enableTracingLog) {
|
||||
doLog(cenv, "%s performOnMainThread[caller]: finished on MainThread(%s). Caller=[%s] [DONE]",
|
||||
if (enableTracingLog) {
|
||||
lwc_plog(env, "%s performOnMainThread[caller]: finished on MainThread(%s). Caller=[%s] [DONE]",
|
||||
[callerCtx identifier], aSelector, toCString([callerCtx caller]));
|
||||
}
|
||||
// Finally reset thread context in context store:
|
||||
[ThreadUtilities resetTraceContext];
|
||||
|
||||
[callerCtx retain];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,6 +512,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
dispatch_once(&oncePredicate, ^{
|
||||
_threadTraceContextPerName = [[NSMutableDictionary alloc] init];
|
||||
});
|
||||
|
||||
return _threadTraceContextPerName;
|
||||
}
|
||||
|
||||
@@ -605,20 +560,9 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
// Record stack trace:
|
||||
NSString *caller = [ThreadUtilities getCaller:prefix];
|
||||
NSString *callStack = (enableCallStacks) ? [ThreadUtilities getCallerStack:prefix] : nil;
|
||||
// update recorded thread state:
|
||||
[thCtx set:actionId operation:operation useJavaModes:useJavaModes caller:caller callstack:callStack];
|
||||
|
||||
// Record thread stack now and return another copy (auto-released):
|
||||
return [[thCtx copy] autorelease];
|
||||
}
|
||||
|
||||
+ (void)dumpThreadTraceContext {
|
||||
if (enableTracingLog) {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
// Record thread stack now and return another copy (auto-released):
|
||||
ThreadTraceContext* thCtx = [ThreadUtilities recordTraceContext];
|
||||
doLog(env, "dumpThreadTraceContext: %s", toCString([thCtx description]));
|
||||
}
|
||||
return [thCtx set:actionId operation:operation useJavaModes:useJavaModes caller:caller callstack:callStack];
|
||||
}
|
||||
|
||||
+ (NSString*)getThreadTraceContexts
|
||||
@@ -650,17 +594,17 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
return dump;
|
||||
}
|
||||
|
||||
+ (BOOL)isWithinPowerTransition:(double)periodInSeconds {
|
||||
+ (BOOL)isWithinPowerTransition {
|
||||
if (wakeUpTime != 0LL) {
|
||||
// check last wake-up time:
|
||||
if (_nowNearTime("wake-up", &wakeUpTime, (SEC_PER_NANOS * periodInSeconds))) {
|
||||
if (nowNearTime("wake-up", &wakeUpTime)) {
|
||||
return true;
|
||||
}
|
||||
// reset invalid time:
|
||||
wakeUpTime = 0LL;
|
||||
} else if (sleepTime != 0LL) {
|
||||
// check last sleep time:
|
||||
if (_nowNearTime("sleep", &sleepTime, (SEC_PER_NANOS * periodInSeconds))) {
|
||||
if (nowNearTime("sleep", &sleepTime)) {
|
||||
return true;
|
||||
}
|
||||
// reset invalid time:
|
||||
@@ -673,7 +617,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
+ (void)_systemOrScreenWillSleep:(NSNotification*)notification {
|
||||
atomic_uint_least64_t now;
|
||||
if (_nanoUpTime(&now))
|
||||
if (nanoUpTime(&now))
|
||||
{
|
||||
// keep most-recent wake-up time (system or display):
|
||||
sleepTime = now;
|
||||
@@ -694,7 +638,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
+ (void)_systemOrScreenDidWake:(NSNotification*)notification {
|
||||
atomic_uint_least64_t now;
|
||||
if (_nanoUpTime(&now))
|
||||
if (nanoUpTime(&now))
|
||||
{
|
||||
// keep most-recent wake-up time (system or display):
|
||||
wakeUpTime = now;
|
||||
@@ -720,10 +664,18 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
}
|
||||
}
|
||||
|
||||
bool _nowNearTime(const char* src, atomic_uint_least64_t *refTime, atomic_uint_least64_t periodNanos) {
|
||||
+ (BOOL)nanoUpTime:(atomic_uint_least64_t*)nanotime {
|
||||
return nanoUpTime(nanotime);
|
||||
}
|
||||
|
||||
+ (BOOL)nowNearTime:(NSString*)src refTime:(atomic_uint_least64_t*)refTime {
|
||||
return nowNearTime(src.UTF8String, refTime);
|
||||
}
|
||||
|
||||
bool nowNearTime(const char* src, atomic_uint_least64_t *refTime) {
|
||||
if (*refTime != 0LL) {
|
||||
atomic_uint_least64_t now;
|
||||
if (_nanoUpTime(&now)) {
|
||||
if (nanoUpTime(&now)) {
|
||||
if (now < *refTime) {
|
||||
// should not happen with monotonic clocks, but:
|
||||
now = *refTime;
|
||||
@@ -734,12 +686,31 @@ bool _nowNearTime(const char* src, atomic_uint_least64_t *refTime, atomic_uint_l
|
||||
if (TRACE_PWM) {
|
||||
NSLog(@"EAWT: nowNearTime[%s]: delta time = %.5lf ms", src, 1e-6 * now);
|
||||
}
|
||||
return (now <= periodNanos);
|
||||
return (now <= RDV_PERIOD);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool nanoUpTime(atomic_uint_least64_t *nanotime) {
|
||||
// Use a monotonic clock (linearly increasing by each tick)
|
||||
// but not counting the time while sleeping.
|
||||
// NOTE:CLOCK_UPTIME_RAW seems counting more elapsed time
|
||||
// arround sleep/wake-up cycle than CLOCK_PROCESS_CPUTIME_ID (adopted):
|
||||
return getTime_nanos(CLOCK_PROCESS_CPUTIME_ID, nanotime);
|
||||
}
|
||||
|
||||
bool getTime_nanos(clockid_t clock_id, atomic_uint_least64_t *nanotime) {
|
||||
struct timespec tp;
|
||||
// Use the given clock:
|
||||
int status = clock_gettime(clock_id, &tp);
|
||||
if (status != 0) {
|
||||
return false;
|
||||
}
|
||||
*nanotime = tp.tv_sec * NANOS_PER_SEC + tp.tv_nsec;
|
||||
return true;
|
||||
}
|
||||
|
||||
void dumpClocks() {
|
||||
if (TRACE_CLOCKS) {
|
||||
logTime_nanos(CLOCK_REALTIME);
|
||||
@@ -756,7 +727,7 @@ void dumpClocks() {
|
||||
void logTime_nanos(clockid_t clock_id) {
|
||||
if (TRACE_CLOCKS) {
|
||||
atomic_uint_least64_t now;
|
||||
if (_getTime_nanos(clock_id, &now)) {
|
||||
if (getTime_nanos(clock_id, &now)) {
|
||||
const char *clock_name;
|
||||
switch (clock_id) {
|
||||
case CLOCK_REALTIME:
|
||||
@@ -895,13 +866,13 @@ JNIEXPORT void lwc_plog(JNIEnv* env, const char *formatMsg, ...) {
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.threadName = [[NSThread currentThread] name];
|
||||
[self reset];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ThreadTraceContext *newCtx = [[ThreadTraceContext alloc] init];
|
||||
if (newCtx) {
|
||||
[newCtx setSleep:[self sleep]];
|
||||
@@ -911,25 +882,30 @@ JNIEXPORT void lwc_plog(JNIEnv* env, const char *formatMsg, ...) {
|
||||
[newCtx setTimestamp:[self timestamp]];
|
||||
|
||||
// shallow copies:
|
||||
[newCtx setThreadName:[self threadName]];
|
||||
[newCtx setCaller:[self caller]];
|
||||
[[newCtx caller] retain];
|
||||
[newCtx setCallStack:[self callStack]];
|
||||
return newCtx;
|
||||
[[newCtx callStack] retain];
|
||||
}
|
||||
return nil;
|
||||
return [newCtx autorelease];
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
- (void)reset
|
||||
{
|
||||
self.sleep = NO;
|
||||
self.useJavaModes = NO;
|
||||
self.actionId = -1;
|
||||
self.operation = nil;
|
||||
self.timestamp = 0.0;
|
||||
[[self caller] release];
|
||||
self.caller = nil;
|
||||
[[self callStack] release];
|
||||
self.callStack = nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[self caller] release];
|
||||
[[self callStack] release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -938,28 +914,36 @@ JNIEXPORT void lwc_plog(JNIEnv* env, const char *formatMsg, ...) {
|
||||
self.sleep = sleepValue;
|
||||
}
|
||||
|
||||
- (void)set:(long)pActionId operation:(char*) pOperation useJavaModes:(BOOL) pUseJavaModes
|
||||
caller:(NSString*) pCaller callstack:(NSString*) pCallStack {
|
||||
- (ThreadTraceContext*)set:(long) pActionId
|
||||
operation:(char*) pOperation
|
||||
useJavaModes:(BOOL) pUseJavaModes
|
||||
caller:(NSString*) pCaller
|
||||
callstack:(NSString*) pCallStack
|
||||
{
|
||||
[self updateThreadState:NO];
|
||||
self.useJavaModes = pUseJavaModes;
|
||||
self.actionId = pActionId;
|
||||
self.operation = pOperation;
|
||||
|
||||
[[self caller] release];
|
||||
self.caller = pCaller;
|
||||
[pCaller release];
|
||||
self.callStack = pCallStack;
|
||||
[pCallStack release];
|
||||
}
|
||||
[[self caller] retain];
|
||||
|
||||
[[self callStack] release];
|
||||
self.callStack = pCallStack;
|
||||
[[self callStack] retain];
|
||||
|
||||
return [self copy];
|
||||
}
|
||||
- (const char*)identifier {
|
||||
return toCString([NSString stringWithFormat:@"[%.6lf '%@' Trace[actionId = %ld](%s)",
|
||||
timestamp, [self threadName], actionId, operation]);
|
||||
return toCString([NSString stringWithFormat:@"[%.6lf] ThreadTrace[actionId = %ld](%s)",
|
||||
timestamp, actionId, operation]);
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
// creates autorelease string:
|
||||
return [NSString stringWithFormat:@"%s useJavaModes=%d sleep=%d caller=[%@] callStack={\n%@}",
|
||||
[self identifier], useJavaModes, sleep, caller,
|
||||
([self callStack] == nil) ? @"-" : [self callStack]];
|
||||
[self identifier], useJavaModes, sleep, caller, callStack];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011, 2024, 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
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#import "JNIUtilities.h"
|
||||
#import <JavaRuntimeSupport/JavaRuntimeSupport.h>
|
||||
#import <ThreadUtilities.h>
|
||||
|
||||
#import "apple_laf_JRSUIControl.h"
|
||||
#import "apple_laf_JRSUIConstants_DoubleValue.h"
|
||||
@@ -159,8 +160,12 @@ static inline jint doPaintCGContext(CGContextRef cgRef, jlong controlPtr, jlong
|
||||
{
|
||||
JRSUIControlRef control = (JRSUIControlRef)jlong_to_ptr(controlPtr);
|
||||
_SyncEncodedProperties(control, oldProperties, newProperties);
|
||||
CGRect bounds = CGRectMake(x, y, w, h);
|
||||
JRSUIControlDraw(gRenderer, control, cgRef, bounds);
|
||||
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES useJavaModes:NO // critical
|
||||
block:^(){
|
||||
CGRect bounds = CGRectMake(x, y, w, h);
|
||||
JRSUIControlDraw(gRenderer, control, cgRef, bounds);
|
||||
}];
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -248,7 +253,13 @@ JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_getNativeHitPart
|
||||
CGRect bounds = CGRectMake(x, y, w, h);
|
||||
CGPoint point = CGPointMake(pointX, pointY);
|
||||
|
||||
return JRSUIControlGetHitPart(gRenderer, control, bounds, point);
|
||||
__block jint result;
|
||||
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES useJavaModes:NO // critical
|
||||
block:^(){
|
||||
result = JRSUIControlGetHitPart(gRenderer, control, bounds, point);
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -259,7 +270,13 @@ JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_getNativeHitPart
|
||||
JNIEXPORT jboolean JNICALL Java_apple_laf_JRSUIUtils_00024ScrollBar_shouldUseScrollToClick
|
||||
(JNIEnv *env, jclass clazz)
|
||||
{
|
||||
return JRSUIControlShouldScrollToClick();
|
||||
__block Boolean result;
|
||||
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES useJavaModes:NO // critical
|
||||
block:^(){
|
||||
result = JRSUIControlShouldScrollToClick();
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -273,8 +290,13 @@ JNIEXPORT void JNICALL Java_apple_laf_JRSUIControl_getNativePartBounds
|
||||
JRSUIControlRef control = (JRSUIControlRef)jlong_to_ptr(controlPtr);
|
||||
_SyncEncodedProperties(control, oldProperties, newProperties);
|
||||
|
||||
CGRect frame = CGRectMake(x, y, w, h);
|
||||
CGRect partBounds = JRSUIControlGetScrollBarPartBounds(control, frame, part);
|
||||
__block CGRect partBounds;
|
||||
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES useJavaModes:NO // critical
|
||||
block:^(){
|
||||
CGRect frame = CGRectMake(x, y, w, h);
|
||||
partBounds = JRSUIControlGetScrollBarPartBounds(control, frame, part);
|
||||
}];
|
||||
|
||||
jdouble *rect = (*env)->GetPrimitiveArrayCritical(env, rectArray, NULL);
|
||||
if (rect != NULL) {
|
||||
@@ -297,6 +319,12 @@ JNIEXPORT jdouble JNICALL Java_apple_laf_JRSUIControl_getNativeScrollBarOffsetCh
|
||||
JRSUIControlRef control = (JRSUIControlRef)jlong_to_ptr(controlPtr);
|
||||
_SyncEncodedProperties(control, oldProperties, newProperties);
|
||||
|
||||
CGRect frame = CGRectMake(x, y, w, h);
|
||||
return (jdouble)JRSUIControlGetScrollBarOffsetFor(control, frame, offset, visibleAmount, extent);
|
||||
__block jdouble result;
|
||||
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES useJavaModes:NO // critical
|
||||
block:^(){
|
||||
CGRect frame = CGRectMake(x, y, w, h);
|
||||
result = (jdouble)JRSUIControlGetScrollBarOffsetFor(control, frame, offset, visibleAmount, extent);
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.jetbrains.desktop;
|
||||
|
||||
import com.jetbrains.desktop.image.TextureWrapperImage;
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import sun.awt.SunToolkit;
|
||||
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Image;
|
||||
|
||||
@JBRApi.Service
|
||||
@JBRApi.Provides("SharedTextures")
|
||||
public class SharedTextures {
|
||||
public final static int METAL_TEXTURE_TYPE = 1;
|
||||
|
||||
private final int textureType;
|
||||
|
||||
public static SharedTextures create() {
|
||||
return new SharedTextures();
|
||||
}
|
||||
|
||||
private SharedTextures() {
|
||||
textureType = getTextureTypeImpl();
|
||||
if (textureType == 0) {
|
||||
throw new JBRApi.ServiceNotAvailableException();
|
||||
}
|
||||
}
|
||||
|
||||
public int getTextureType() {
|
||||
return textureType;
|
||||
}
|
||||
|
||||
public Image wrapTexture(GraphicsConfiguration gc, long texture) {
|
||||
return new TextureWrapperImage(gc, texture);
|
||||
}
|
||||
|
||||
private static int getTextureTypeImpl() {
|
||||
GraphicsConfiguration gc = GraphicsEnvironment
|
||||
.getLocalGraphicsEnvironment()
|
||||
.getDefaultScreenDevice()
|
||||
.getDefaultConfiguration();
|
||||
try {
|
||||
if (SunToolkit.isInstanceOf(gc, "sun.java2d.metal.MTLGraphicsConfig")) {
|
||||
return METAL_TEXTURE_TYPE;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new InternalError("Unexpected exception during reflection", e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.jetbrains.desktop.image;
|
||||
|
||||
import sun.awt.image.SurfaceManager;
|
||||
import sun.java2d.SurfaceData;
|
||||
import sun.java2d.SurfaceManagerFactory;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Image;
|
||||
import java.awt.ImageCapabilities;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ImageObserver;
|
||||
import java.awt.image.ImageProducer;
|
||||
|
||||
/**
|
||||
* This class is a wrapper for a GPU texture-based image.
|
||||
* It provides functionalities to integrate a texture image with AWT's Image class.
|
||||
* The wrapped texture has to correspond to the current rendering pipeline (see details below).
|
||||
* <p>
|
||||
* Only Metal textures are supported at the moment.
|
||||
* <p>
|
||||
* Platform-specific details:
|
||||
* 1. macOS.
|
||||
* The MTLTexture reference counter will be incremented in the constructor and decremented on
|
||||
* the surface flushing.
|
||||
*/
|
||||
public class TextureWrapperImage extends Image {
|
||||
final GraphicsConfiguration gc;
|
||||
final SurfaceData sd;
|
||||
final static ImageCapabilities capabilities = new ImageCapabilities(true);
|
||||
|
||||
/**
|
||||
* Constructs a TextureWrapperImage instance with the specified graphics configuration
|
||||
* and a texture.
|
||||
*
|
||||
* @param gc the graphics configuration
|
||||
* @param texture the texture that will be wrapped by this instance.
|
||||
* Platform-specific details:
|
||||
* macOS (with the Metal rendering pipeline) - a pointer to an MTLTexture object is expected
|
||||
*
|
||||
* @throws UnsupportedOperationException if the current pipeline is not supported
|
||||
* @throws IllegalArgumentException if the texture cannot be wrapped
|
||||
*/
|
||||
public TextureWrapperImage(GraphicsConfiguration gc, long texture)
|
||||
throws UnsupportedOperationException, IllegalArgumentException {
|
||||
this.gc = gc;
|
||||
SurfaceManager surfaceManager = SurfaceManagerFactory.getInstance().createTextureWrapperSurfaceManager(gc, this, texture);
|
||||
sd = surfaceManager.getPrimarySurfaceData();
|
||||
SurfaceManager.setManager(this, surfaceManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth(ImageObserver observer) {
|
||||
return (int) (sd.getBounds().width / sd.getDefaultScaleX());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(ImageObserver observer) {
|
||||
return (int) (sd.getBounds().height / sd.getDefaultScaleY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageProducer getSource() {
|
||||
BufferedImage bi = sd.getDeviceConfiguration().createCompatibleImage(getWidth(null), getHeight(null));
|
||||
Graphics2D g = bi.createGraphics();
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.drawImage(this, 0, 0, null);
|
||||
g.dispose();
|
||||
return bi.getSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Graphics getGraphics() {
|
||||
throw new UnsupportedOperationException("This image can't be the drawing destination");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name, ImageObserver observer) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("null property name is not allowed");
|
||||
}
|
||||
return java.awt.Image.UndefinedProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageCapabilities getCapabilities(GraphicsConfiguration gc) {
|
||||
return capabilities;
|
||||
}
|
||||
}
|
||||
@@ -4887,6 +4887,9 @@ public abstract class Component implements ImageObserver, MenuContainer,
|
||||
eventLog.finest("{0}", e);
|
||||
}
|
||||
|
||||
if (id == MouseEvent.MOUSE_ENTERED && getToolkit() instanceof SunToolkit toolkit) {
|
||||
toolkit.updateLastMouseEventComponent(this);
|
||||
}
|
||||
/*
|
||||
* 0. Set timestamp and modifiers of current event.
|
||||
*/
|
||||
@@ -7168,6 +7171,12 @@ public abstract class Component implements ImageObserver, MenuContainer,
|
||||
setGlobalPermanentFocusOwner(null);
|
||||
}
|
||||
|
||||
if (getToolkit() instanceof SunToolkit toolkit) {
|
||||
if (toolkit.getLastMouseEventComponent() == this) {
|
||||
toolkit.updateLastMouseEventComponent(null);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (getTreeLock()) {
|
||||
if (isFocusOwner() && KeyboardFocusManager.isAutoFocusTransferEnabledFor(this)) {
|
||||
transferFocus(true);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1998, 2024, 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
|
||||
@@ -851,6 +851,22 @@ public class CSS implements Serializable {
|
||||
return r != null ? r : conv.parseCssValue(key.getDefaultValue());
|
||||
}
|
||||
|
||||
static Object mergeTextDecoration(String value) {
|
||||
if (value.startsWith("none")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean underline = value.contains("underline");
|
||||
boolean strikeThrough = value.contains("line-through");
|
||||
if (!underline && !strikeThrough) {
|
||||
return null;
|
||||
}
|
||||
String newValue = underline && strikeThrough
|
||||
? "underline,line-through"
|
||||
: (underline ? "underline" : "line-through");
|
||||
return new StringValue().parseCssValue(newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps from a StyleConstants to a CSS Attribute.
|
||||
*/
|
||||
|
||||
@@ -2506,7 +2506,7 @@ public class HTMLDocument extends DefaultStyledDocument {
|
||||
tagMap.put(HTML.Tag.SCRIPT, ha);
|
||||
tagMap.put(HTML.Tag.SELECT, fa);
|
||||
tagMap.put(HTML.Tag.SMALL, ca);
|
||||
tagMap.put(HTML.Tag.SPAN, ca);
|
||||
tagMap.put(HTML.Tag.SPAN, new ConvertSpanAction());
|
||||
tagMap.put(HTML.Tag.STRIKE, conv);
|
||||
tagMap.put(HTML.Tag.S, conv);
|
||||
tagMap.put(HTML.Tag.STRONG, ca);
|
||||
@@ -3430,11 +3430,43 @@ public class HTMLDocument extends DefaultStyledDocument {
|
||||
if (styleAttributes != null) {
|
||||
charAttr.addAttributes(styleAttributes);
|
||||
}
|
||||
|
||||
convertAttributes(t, attr);
|
||||
}
|
||||
|
||||
public void end(HTML.Tag t) {
|
||||
popCharacterStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts HTML tags to CSS attributes.
|
||||
* @param t the current HTML tag
|
||||
* @param attr the attributes of the HTML tag
|
||||
*/
|
||||
void convertAttributes(HTML.Tag t, MutableAttributeSet attr) {
|
||||
}
|
||||
}
|
||||
|
||||
final class ConvertSpanAction extends CharacterAction {
|
||||
@Override
|
||||
void convertAttributes(HTML.Tag t, MutableAttributeSet attr) {
|
||||
Object newDecoration = attr.getAttribute(CSS.Attribute.TEXT_DECORATION);
|
||||
Object previousDecoration =
|
||||
charAttrStack.peek()
|
||||
.getAttribute(CSS.Attribute.TEXT_DECORATION);
|
||||
|
||||
if (newDecoration != null
|
||||
&& !"none".equals(newDecoration.toString())
|
||||
&& previousDecoration != null
|
||||
&& !"none".equals(previousDecoration.toString())) {
|
||||
StyleSheet sheet = getStyleSheet();
|
||||
sheet.addCSSAttribute(charAttr,
|
||||
CSS.Attribute.TEXT_DECORATION,
|
||||
CSS.mergeTextDecoration(newDecoration + ","
|
||||
+ previousDecoration)
|
||||
.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3442,35 +3474,9 @@ public class HTMLDocument extends DefaultStyledDocument {
|
||||
* mappings that have a corresponding StyleConstants
|
||||
* and CSS mapping. The conversion is to CSS attributes.
|
||||
*/
|
||||
class ConvertAction extends TagAction {
|
||||
|
||||
public void start(HTML.Tag t, MutableAttributeSet attr) {
|
||||
pushCharacterStyle();
|
||||
if (!foundInsertTag) {
|
||||
// Note that the third argument should really be based off
|
||||
// inParagraph and impliedP. If we're wrong (that is
|
||||
// insertTagDepthDelta shouldn't be changed), we'll end up
|
||||
// removing an extra EndSpec, which won't matter anyway.
|
||||
boolean insert = canInsertTag(t, attr, false);
|
||||
if (foundInsertTag) {
|
||||
if (!inParagraph) {
|
||||
inParagraph = impliedP = true;
|
||||
}
|
||||
}
|
||||
if (!insert) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (attr.isDefined(IMPLIED)) {
|
||||
attr.removeAttribute(IMPLIED);
|
||||
}
|
||||
if (styleAttributes != null) {
|
||||
charAttr.addAttributes(styleAttributes);
|
||||
}
|
||||
// We also need to add attr, otherwise we lose custom
|
||||
// attributes, including class/id for style lookups, and
|
||||
// further confuse style lookup (doesn't have tag).
|
||||
charAttr.addAttribute(t, attr.copyAttributes());
|
||||
final class ConvertAction extends CharacterAction {
|
||||
@Override
|
||||
void convertAttributes(HTML.Tag t, MutableAttributeSet attr) {
|
||||
StyleSheet sheet = getStyleSheet();
|
||||
if (t == HTML.Tag.B) {
|
||||
sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold");
|
||||
@@ -3511,11 +3517,6 @@ public class HTMLDocument extends DefaultStyledDocument {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void end(HTML.Tag t) {
|
||||
popCharacterStyle();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AnchorAction extends CharacterAction {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2024, 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
|
||||
@@ -24,9 +24,16 @@
|
||||
*/
|
||||
package javax.swing.text.html;
|
||||
|
||||
import javax.swing.text.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.text.AttributeSet;
|
||||
import javax.swing.text.MutableAttributeSet;
|
||||
import javax.swing.text.SimpleAttributeSet;
|
||||
|
||||
/**
|
||||
* An implementation of <code>AttributeSet</code> that can multiplex
|
||||
@@ -196,15 +203,24 @@ class MuxingAttributeSet implements AttributeSet, Serializable {
|
||||
* @see AttributeSet#getAttribute
|
||||
*/
|
||||
public Object getAttribute(Object key) {
|
||||
AttributeSet[] as = getAttributes();
|
||||
int n = as.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
Object o = as[i].getAttribute(key);
|
||||
if (o != null) {
|
||||
return o;
|
||||
final AttributeSet[] as = getAttributes();
|
||||
final int n = as.length;
|
||||
if (key != CSS.Attribute.TEXT_DECORATION) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
Object o = as[i].getAttribute(key);
|
||||
if (o != null) {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
String values = Arrays.stream(as)
|
||||
.map(a -> a.getAttribute(key))
|
||||
.filter(Objects::nonNull)
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.joining(","));
|
||||
return CSS.mergeTextDecoration(values);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2024, 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
|
||||
@@ -24,17 +24,53 @@
|
||||
*/
|
||||
package javax.swing.text.html;
|
||||
|
||||
import sun.swing.SwingUtilities2;
|
||||
import java.util.*;
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.Serializable;
|
||||
import java.io.StringReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.EmptyStackException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Stack;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.border.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.text.*;
|
||||
import javax.swing.text.AttributeSet;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.Element;
|
||||
import javax.swing.text.MutableAttributeSet;
|
||||
import javax.swing.text.SimpleAttributeSet;
|
||||
import javax.swing.text.Style;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyleContext;
|
||||
import javax.swing.text.StyledDocument;
|
||||
import javax.swing.text.View;
|
||||
|
||||
import sun.swing.SwingUtilities2;
|
||||
|
||||
/**
|
||||
* Support for defining the visual characteristics of
|
||||
@@ -2820,10 +2856,31 @@ public class StyleSheet extends StyleContext {
|
||||
return doGetAttribute(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the current value of the 'text-decoration' property
|
||||
* with the value from parent.
|
||||
*/
|
||||
private Object getTextDecoration(Object value) {
|
||||
AttributeSet parent = getResolveParent();
|
||||
if (parent == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
Object parentValue = parent.getAttribute(CSS.Attribute.TEXT_DECORATION);
|
||||
return parentValue == null
|
||||
? value
|
||||
: CSS.mergeTextDecoration(value + "," + parentValue);
|
||||
}
|
||||
|
||||
Object doGetAttribute(Object key) {
|
||||
Object retValue = super.getAttribute(key);
|
||||
if (retValue != null) {
|
||||
return retValue;
|
||||
if (key != CSS.Attribute.TEXT_DECORATION) {
|
||||
return retValue;
|
||||
} else {
|
||||
// Merge current value with parent
|
||||
return getTextDecoration(retValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (key == CSS.Attribute.FONT_SIZE) {
|
||||
|
||||
134
src/java.desktop/share/classes/sun/awt/CachedCursorManager.java
Normal file
134
src/java.desktop/share/classes/sun/awt/CachedCursorManager.java
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 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.
|
||||
*/
|
||||
|
||||
package sun.awt;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Point;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public abstract class CachedCursorManager {
|
||||
|
||||
/**
|
||||
* A flag to indicate if the update is scheduled, so we don't process it
|
||||
* twice.
|
||||
*/
|
||||
private final AtomicBoolean updatePending = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Sets the cursor to correspond the component currently under mouse.
|
||||
*
|
||||
* This method should not be executed on the toolkit thread as it
|
||||
* calls to user code (e.g. Container.findComponentAt).
|
||||
*/
|
||||
public final void updateCursor() {
|
||||
updatePending.set(false);
|
||||
updateCursorImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules updating the cursor on the corresponding event dispatch
|
||||
* thread for the given window.
|
||||
*
|
||||
* This method is called on the toolkit thread as a result of a
|
||||
* native update cursor request (e.g. WM_SETCURSOR on Windows).
|
||||
*/
|
||||
public final void updateCursorLater(final Component window) {
|
||||
if (updatePending.compareAndSet(false, true)) {
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateCursor();
|
||||
}
|
||||
};
|
||||
SunToolkit.executeOnEventHandlerThread(window, r);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Cursor getCursorByPosition(final Point cursorPos, Component c);
|
||||
|
||||
private void updateCursorImpl() {
|
||||
final Point cursorPos = getCursorPosition();
|
||||
final Component component = getComponentUnderCursor();
|
||||
if (component != null) {
|
||||
synchronized (component.getTreeLock()) {
|
||||
final Component parentComponent = findComponent(cursorPos, component);
|
||||
Cursor cursor = getCursorByPosition(cursorPos, parentComponent);
|
||||
|
||||
if (cursor == null) {
|
||||
cursor = (parentComponent != null) ? parentComponent.getCursor() : null;
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
setCursor(cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Component getComponentUnderCursor();
|
||||
protected abstract Point getLocationOnScreen(Component component);
|
||||
|
||||
/**
|
||||
* Returns the first visible, enabled and showing component under cursor.
|
||||
* Returns null for modal blocked windows.
|
||||
*
|
||||
* @param cursorPos Current cursor position.
|
||||
* @return Component or null.
|
||||
*/
|
||||
private Component findComponent(final Point cursorPos, Component currentComponent) {
|
||||
Component component = currentComponent;
|
||||
if (component instanceof Container && component.isShowing()) {
|
||||
final Point p = getLocationOnScreen(component);
|
||||
component = AWTAccessor.getContainerAccessor().findComponentAt(
|
||||
(Container) component, cursorPos.x - p.x, cursorPos.y - p.y, false);
|
||||
|
||||
}
|
||||
while (component != null) {
|
||||
final Object p = AWTAccessor.getComponentAccessor().getPeer(component);
|
||||
if (component.isVisible() && component.isEnabled() && p != null) {
|
||||
break;
|
||||
}
|
||||
component = component.getParent();
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current cursor position.
|
||||
*/
|
||||
public abstract Point getCursorPosition();
|
||||
|
||||
/**
|
||||
* Sets a cursor. The cursor can be null if the mouse is not over a Java
|
||||
* window.
|
||||
* @param cursor the new {@code Cursor}.
|
||||
*/
|
||||
protected abstract void setCursor(Cursor cursor);
|
||||
}
|
||||
@@ -236,6 +236,17 @@ public abstract class SunToolkit extends Toolkit
|
||||
AccessController.doPrivileged(new GetBooleanAction("awt.lock.fair")));
|
||||
private static final Condition AWT_LOCK_COND = AWT_LOCK.newCondition();
|
||||
|
||||
/*
|
||||
* A component where the last mouse event came to. Used by cursor manager to
|
||||
* find the component under cursor. Currently, uses only on Windows
|
||||
*/
|
||||
public void updateLastMouseEventComponent(Component component) {
|
||||
}
|
||||
|
||||
public Component getLastMouseEventComponent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public interface AwtLockListener {
|
||||
void afterAwtLocked();
|
||||
void beforeAwtUnlocked();
|
||||
|
||||
@@ -37,7 +37,6 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import com.jetbrains.desktop.image.TextureWrapperImage;
|
||||
import sun.java2d.InvalidPipeException;
|
||||
import sun.java2d.SurfaceData;
|
||||
import sun.java2d.SurfaceDataProxy;
|
||||
@@ -301,7 +300,7 @@ public abstract class SurfaceManager {
|
||||
* @see SurfaceData#getDefaultScaleX
|
||||
*/
|
||||
public static double getImageScaleX(final Image img) {
|
||||
if (!(img instanceof VolatileImage || img instanceof TextureWrapperImage)) {
|
||||
if (!(img instanceof VolatileImage)) {
|
||||
return 1;
|
||||
}
|
||||
final SurfaceManager sm = getManager(img);
|
||||
@@ -315,7 +314,7 @@ public abstract class SurfaceManager {
|
||||
* @see SurfaceData#getDefaultScaleY
|
||||
*/
|
||||
public static double getImageScaleY(final Image img) {
|
||||
if (!(img instanceof VolatileImage || img instanceof TextureWrapperImage)) {
|
||||
if (!(img instanceof VolatileImage)) {
|
||||
return 1;
|
||||
}
|
||||
final SurfaceManager sm = getManager(img);
|
||||
|
||||
@@ -71,7 +71,6 @@ import java.lang.annotation.Native;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jetbrains.desktop.image.TextureWrapperImage;
|
||||
import sun.awt.ConstrainableGraphics;
|
||||
import sun.awt.SunHints;
|
||||
import sun.awt.image.MultiResolutionToolkitImage;
|
||||
@@ -3100,7 +3099,7 @@ public final class SunGraphics2D
|
||||
Color bgcolor, ImageObserver observer,
|
||||
AffineTransform xform) {
|
||||
try {
|
||||
if (img instanceof VolatileImage || img instanceof TextureWrapperImage) {
|
||||
if (img instanceof VolatileImage) {
|
||||
final SurfaceData sd = SurfaceManager.getManager(img)
|
||||
.getPrimarySurfaceData();
|
||||
final double scaleX = sd.getDefaultScaleX();
|
||||
|
||||
@@ -26,12 +26,8 @@
|
||||
package sun.java2d;
|
||||
|
||||
import sun.awt.image.SunVolatileImage;
|
||||
import sun.awt.image.SurfaceManager;
|
||||
import sun.awt.image.VolatileSurfaceManager;
|
||||
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Image;
|
||||
|
||||
/**
|
||||
* This factory creates platform specific VolatileSurfaceManager
|
||||
* implementations.
|
||||
@@ -92,6 +88,4 @@ public abstract class SurfaceManagerFactory {
|
||||
*/
|
||||
public abstract VolatileSurfaceManager
|
||||
createVolatileManager(SunVolatileImage image, Object context);
|
||||
|
||||
public abstract SurfaceManager createTextureWrapperSurfaceManager(GraphicsConfiguration gc, Image image, long texture);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ public class VKInstance {
|
||||
|
||||
private static final PlatformLogger log = PlatformLogger.getLogger("sun.java2d.vulkan.VKInstance");
|
||||
private static Boolean initialized;
|
||||
private static Boolean sdAccelerated;
|
||||
|
||||
private static native boolean initNative(long nativePtr, boolean verbose, int deviceNumber);
|
||||
|
||||
@@ -56,16 +55,10 @@ public class VKInstance {
|
||||
final int deviceNumber = parsedDeviceNumber;
|
||||
final boolean verbose = "True".equals(vulkanOption);
|
||||
System.loadLibrary("awt");
|
||||
@SuppressWarnings("removal")
|
||||
String sdOption = AccessController.doPrivileged(
|
||||
(PrivilegedAction<String>) () -> System.getProperty("sun.java2d.vulkan.accelsd", ""));
|
||||
initialized = initNative(nativePtr, verbose, deviceNumber);
|
||||
sdAccelerated = initialized && "true".equalsIgnoreCase(sdOption);
|
||||
} else initialized = false;
|
||||
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Vulkan rendering enabled: " + (initialized ? "YES" : "NO"));
|
||||
log.fine("Vulkan accelerated surface data enabled: " + (sdAccelerated ? "YES" : "NO"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +66,4 @@ public class VKInstance {
|
||||
if (initialized == null) throw new RuntimeException("Vulkan not initialized");
|
||||
return initialized;
|
||||
}
|
||||
|
||||
public static boolean isSurfaceDataAccelerated() {
|
||||
if (initialized == null) throw new RuntimeException("Vulkan not initialized");
|
||||
return sdAccelerated;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.java2d.vulkan;
|
||||
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Image;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.ColorModel;
|
||||
import sun.java2d.SurfaceData;
|
||||
import sun.java2d.pipe.BufferedContext;
|
||||
|
||||
/**
|
||||
* SurfaceData object representing an off-screen buffer
|
||||
*/
|
||||
public class VKOffScreenSurfaceData extends VKSurfaceData {
|
||||
private final Image offscreenImage;
|
||||
private native void initOps(int width, int height);
|
||||
|
||||
public VKOffScreenSurfaceData(VKGraphicsConfig gc, Image image, ColorModel cm,
|
||||
int type, int width, int height)
|
||||
{
|
||||
super(gc, cm, type, width, height);
|
||||
offscreenImage = image;
|
||||
initOps(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceData getReplacement() {
|
||||
return restoreContents(offscreenImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphicsConfiguration getDeviceConfiguration() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeResource(int resType) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle getBounds() {
|
||||
return new Rectangle(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns destination Image associated with this SurfaceData.
|
||||
*/
|
||||
@Override
|
||||
public Object getDestination() {
|
||||
return offscreenImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedContext getContext() {
|
||||
return getGraphicsConfig().getContext();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isOnScreen() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -26,40 +26,89 @@
|
||||
|
||||
package sun.java2d.vulkan;
|
||||
|
||||
import jdk.internal.misc.InnocuousThread;
|
||||
import sun.java2d.pipe.RenderQueue;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
import static sun.java2d.pipe.BufferedOpCodes.SYNC;
|
||||
|
||||
/**
|
||||
* VK-specific implementation of RenderQueue.
|
||||
* VK-specific implementation of RenderQueue. This class provides a
|
||||
* single (daemon) thread that is responsible for periodically flushing
|
||||
* the queue.
|
||||
*/
|
||||
public class VKRenderQueue extends RenderQueue {
|
||||
|
||||
private static final VKRenderQueue theInstance = new VKRenderQueue();
|
||||
private static VKRenderQueue theInstance;
|
||||
private final QueueFlusher flusher;
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private VKRenderQueue() {
|
||||
/*
|
||||
* The thread must be a member of a thread group
|
||||
* which will not get GCed before VM exit.
|
||||
*/
|
||||
flusher = AccessController.doPrivileged((PrivilegedAction<QueueFlusher>) QueueFlusher::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the single VKRenderQueue instance.
|
||||
* Returns the single VKRenderQueue instance. If it has not yet been
|
||||
* initialized, this method will first construct the single instance
|
||||
* before returning it.
|
||||
*/
|
||||
public static VKRenderQueue getInstance() {
|
||||
public static synchronized VKRenderQueue getInstance() {
|
||||
if (theInstance == null) {
|
||||
theInstance = new VKRenderQueue();
|
||||
}
|
||||
return theInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the single VKRenderQueue instance synchronously.
|
||||
* Flushes the single VKRenderQueue instance synchronously. If an
|
||||
* VKRenderQueue has not yet been instantiated, this method is a no-op.
|
||||
* This method is useful in the case of Toolkit.sync(), in which we want
|
||||
* to flush the Vulkan pipeline, but only if the Vulkan pipeline is currently
|
||||
* enabled.
|
||||
*/
|
||||
public static void sync() {
|
||||
theInstance.lock();
|
||||
try {
|
||||
theInstance.ensureCapacity(4);
|
||||
theInstance.getBuffer().putInt(SYNC);
|
||||
theInstance.flushNow();
|
||||
} finally {
|
||||
theInstance.unlock();
|
||||
if (theInstance != null) {
|
||||
theInstance.lock();
|
||||
try {
|
||||
theInstance.ensureCapacity(4);
|
||||
theInstance.getBuffer().putInt(SYNC);
|
||||
theInstance.flushNow();
|
||||
} finally {
|
||||
theInstance.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushNow() {
|
||||
// assert lock.isHeldByCurrentThread();
|
||||
try {
|
||||
flusher.flushNow();
|
||||
} catch (Exception e) {
|
||||
System.err.println("exception in flushNow:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void flushAndInvokeNow(Runnable r) {
|
||||
// assert lock.isHeldByCurrentThread();
|
||||
try {
|
||||
flusher.flushAndInvokeNow(r);
|
||||
} catch (Exception e) {
|
||||
System.err.println("exception in flushAndInvokeNow:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private native void flushBuffer(long buf, int limit);
|
||||
|
||||
private void flushBuffer() {
|
||||
// assert lock.isHeldByCurrentThread();
|
||||
int limit = buf.position();
|
||||
if (limit > 0) {
|
||||
@@ -72,10 +121,97 @@ public class VKRenderQueue extends RenderQueue {
|
||||
refSet.clear();
|
||||
}
|
||||
|
||||
public void flushAndInvokeNow(Runnable r) {
|
||||
flushNow();
|
||||
r.run();
|
||||
}
|
||||
private class QueueFlusher implements Runnable {
|
||||
private boolean needsFlush;
|
||||
private Runnable task;
|
||||
private Error error;
|
||||
private final Thread thread;
|
||||
|
||||
private native void flushBuffer(long buf, int limit);
|
||||
public QueueFlusher() {
|
||||
thread = InnocuousThread.newThread("Java2D Queue Flusher", this);
|
||||
thread.setDaemon(true);
|
||||
thread.setPriority(Thread.MAX_PRIORITY);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public synchronized void flushNow() {
|
||||
// wake up the flusher
|
||||
needsFlush = true;
|
||||
notify();
|
||||
|
||||
// wait for flush to complete
|
||||
while (needsFlush) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// re-throw any error that may have occurred during the flush
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void flushAndInvokeNow(Runnable task) {
|
||||
this.task = task;
|
||||
flushNow();
|
||||
}
|
||||
|
||||
public synchronized void run() {
|
||||
boolean timedOut = false;
|
||||
while (true) {
|
||||
while (!needsFlush) {
|
||||
try {
|
||||
timedOut = false;
|
||||
/*
|
||||
* Wait until we're woken up with a flushNow() call,
|
||||
* or the timeout period elapses (so that we can
|
||||
* flush the queue periodically).
|
||||
*/
|
||||
wait(100);
|
||||
/*
|
||||
* We will automatically flush the queue if the
|
||||
* following conditions apply:
|
||||
* - the wait() timed out
|
||||
* - we can lock the queue (without blocking)
|
||||
* - there is something in the queue to flush
|
||||
* Otherwise, just continue (we'll flush eventually).
|
||||
*/
|
||||
if (!needsFlush && (timedOut = tryLock())) {
|
||||
if (buf.position() > 0) {
|
||||
needsFlush = true;
|
||||
} else {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
// reset the throwable state
|
||||
error = null;
|
||||
// flush the buffer now
|
||||
flushBuffer();
|
||||
// if there's a task, invoke that now as well
|
||||
if (task != null) {
|
||||
task.run();
|
||||
}
|
||||
} catch (Error e) {
|
||||
error = e;
|
||||
} catch (Exception x) {
|
||||
System.err.println("exception in QueueFlusher:");
|
||||
x.printStackTrace();
|
||||
} finally {
|
||||
if (timedOut) {
|
||||
unlock();
|
||||
}
|
||||
task = null;
|
||||
// allow the waiting thread to continue
|
||||
needsFlush = false;
|
||||
notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import sun.java2d.SurfaceData;
|
||||
import sun.java2d.loops.CompositeType;
|
||||
import sun.java2d.loops.GraphicsPrimitive;
|
||||
import sun.java2d.loops.SurfaceType;
|
||||
import static sun.java2d.pipe.BufferedOpCodes.CONFIGURE_SURFACE;
|
||||
import sun.java2d.pipe.ParallelogramPipe;
|
||||
import sun.java2d.pipe.PixelToParallelogramConverter;
|
||||
import sun.java2d.pipe.RenderBuffer;
|
||||
@@ -304,22 +303,5 @@ public abstract class VKSurfaceData extends SurfaceData
|
||||
return graphicsConfig;
|
||||
}
|
||||
|
||||
protected synchronized void configure() {
|
||||
VKRenderQueue rq = VKRenderQueue.getInstance();
|
||||
rq.lock();
|
||||
try {
|
||||
RenderBuffer buf = rq.getBuffer();
|
||||
rq.ensureCapacityAndAlignment(20, 4);
|
||||
buf.putInt(CONFIGURE_SURFACE);
|
||||
buf.putLong(getNativeOps());
|
||||
buf.putInt(width);
|
||||
buf.putInt(height);
|
||||
|
||||
rq.flushNow();
|
||||
} finally {
|
||||
rq.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean isOnScreen();
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec2 viewportNormalizer; // 2.0 / viewport
|
||||
} push;
|
||||
|
||||
layout(location = 0) in ivec2 in_Position;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(vec2(in_Position) * push.viewportNormalizer - vec2(1.0), 0.0, 1.0);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0, r8) uniform readonly restrict imageBuffer u_Mask;
|
||||
|
||||
layout(origin_upper_left) in vec4 gl_FragCoord;
|
||||
|
||||
layout(location = 0) in flat ivec4 in_OriginOffsetAndScanline;
|
||||
layout(location = 1) in flat vec4 in_Color;
|
||||
|
||||
layout(location = 0) out vec4 out_Color;
|
||||
|
||||
void main() {
|
||||
ivec2 maskPos = ivec2(gl_FragCoord.xy) - in_OriginOffsetAndScanline.xy;
|
||||
int offset = in_OriginOffsetAndScanline.z;
|
||||
int scanline = in_OriginOffsetAndScanline.w;
|
||||
int maskIndex = offset + scanline * maskPos.y + min(scanline, maskPos.x);
|
||||
out_Color = in_Color * imageLoad(u_Mask, maskIndex).r;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec2 viewportNormalizer; // 2.0 / viewport
|
||||
} push;
|
||||
|
||||
layout(location = 0) in ivec4 in_PositionOffsetAndScanline;
|
||||
layout(location = 1) in vec4 in_Color;
|
||||
|
||||
// This starts with "Origin" and not "Position" intentionally.
|
||||
// When drawing, vertices are ordered in a such way, that provoking vertex is always the top-left one.
|
||||
// This gives us an easy way to calculate offset within the rectangle without additional inputs.
|
||||
layout(location = 0) out flat ivec4 out_OriginOffsetAndScanline;
|
||||
layout(location = 1) out flat vec4 out_Color;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(vec2(in_PositionOffsetAndScanline.xy) * push.viewportNormalizer - vec2(1.0), 0.0, 1.0);
|
||||
out_OriginOffsetAndScanline = in_PositionOffsetAndScanline;
|
||||
out_Color = in_Color;
|
||||
}
|
||||
@@ -824,6 +824,8 @@ ATexturePoolHandle* ATexturePool_getTexture(ATexturePool* pool,
|
||||
pool->memoryAllocated += requestedBytes;
|
||||
pool->totalMemoryAllocated += requestedBytes;
|
||||
|
||||
J2dTraceLn6(J2D_TRACE_VERBOSE, "ATexturePool_getTexture: created pool item: tex=%p, w=%d h=%d, pf=%d | allocated memory = %lld Kb (allocs: %d)",
|
||||
minDeltaTpi->texture, width, height, format, pool->memoryAllocated / UNIT_KB, pool->allocatedCount)
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn6(J2D_TRACE_VERBOSE, "ATexturePool_getTexture: created pool item: tex=%p, w=%d h=%d, pf=%d | allocated memory = %lld Kb (allocs: %d)",
|
||||
minDeltaTpi->texture, width, height, format, pool->memoryAllocated / UNIT_KB, pool->allocatedCount)
|
||||
} else {
|
||||
|
||||
@@ -2,381 +2,55 @@
|
||||
#include <stddef.h>
|
||||
#include "CArrayUtil.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# include <malloc.h>
|
||||
# define ALIGNED_ALLOC(ALIGNMENT, SIZE) _aligned_malloc((SIZE), (ALIGNMENT))
|
||||
# define ALIGNED_FREE(PTR) _aligned_free(PTR)
|
||||
#else
|
||||
# include <stdlib.h>
|
||||
# define ALIGNED_ALLOC(ALIGNMENT, SIZE) aligned_alloc((ALIGNMENT), (SIZE))
|
||||
# define ALIGNED_FREE(PTR) free(PTR)
|
||||
#endif
|
||||
#define MIN(a,b) (((a)<(b))?(a):(b))
|
||||
|
||||
// === Allocation helpers ===
|
||||
|
||||
typedef struct {
|
||||
size_t total_alignment;
|
||||
size_t aligned_header_size;
|
||||
void* new_data;
|
||||
} CARR_context_t;
|
||||
|
||||
static size_t CARR_align_size(size_t alignment, size_t size) {
|
||||
// assert alignment is power of 2
|
||||
size_t alignment_mask = alignment - 1;
|
||||
return (size + alignment_mask) & ~alignment_mask;
|
||||
}
|
||||
|
||||
static CARR_context_t CARR_context_init(size_t header_alignment, size_t header_size, size_t data_alignment) {
|
||||
CARR_context_t context;
|
||||
// assert header_alignment and data_alignment are powers of 2
|
||||
context.total_alignment = CARR_MAX(header_alignment, data_alignment);
|
||||
// assert header_size is multiple of header_alignment
|
||||
context.aligned_header_size = CARR_align_size(context.total_alignment, header_size);
|
||||
context.new_data = NULL;
|
||||
return context;
|
||||
}
|
||||
|
||||
static bool CARR_context_alloc(CARR_context_t* context, size_t data_size) {
|
||||
void* block = ALIGNED_ALLOC(context->total_alignment, context->aligned_header_size + data_size);
|
||||
if (block == NULL) return false;
|
||||
context->new_data = (char*)block + context->aligned_header_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CARR_context_free(CARR_context_t* context, void* old_data) {
|
||||
if (old_data != NULL) {
|
||||
void* block = (char*)old_data - context->aligned_header_size;
|
||||
ALIGNED_FREE(block);
|
||||
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->size = 0;
|
||||
pvec->capacity = capacity;
|
||||
return pvec->data;
|
||||
}
|
||||
|
||||
// === Arrays ===
|
||||
void* CARR_array_realloc(CARR_array_t* vec, size_t elem_size, size_t new_capacity) {
|
||||
if (vec->capacity == new_capacity) {
|
||||
return vec->data;
|
||||
}
|
||||
CARR_array_t* new_vec =
|
||||
(CARR_array_t*)((char*)CARR_array_alloc(elem_size, new_capacity) - offsetof(CARR_array_t, data));
|
||||
if (new_vec == NULL) {
|
||||
return vec == NULL ? NULL : vec->data;
|
||||
}
|
||||
new_vec->capacity = new_capacity;
|
||||
new_vec->size = MIN(vec->size, new_capacity);
|
||||
memcpy(new_vec->data, vec->data, new_vec->size*elem_size);
|
||||
free(vec);
|
||||
return new_vec->data;
|
||||
}
|
||||
|
||||
bool CARR_array_realloc(void** handle, size_t element_alignment, size_t element_size, size_t new_capacity) {
|
||||
void* old_data = *handle;
|
||||
if (old_data != NULL && CARR_ARRAY_T(old_data)->capacity == new_capacity) return true;
|
||||
CARR_context_t context = CARR_context_init(alignof(CARR_array_t), sizeof(CARR_array_t), element_alignment);
|
||||
if (new_capacity != 0) {
|
||||
if (!CARR_context_alloc(&context, element_size * new_capacity)) return false;
|
||||
CARR_ARRAY_T(context.new_data)->capacity = new_capacity;
|
||||
if (old_data == NULL) {
|
||||
CARR_ARRAY_T(context.new_data)->size = 0;
|
||||
} else {
|
||||
CARR_ARRAY_T(context.new_data)->size = CARR_MIN(CARR_ARRAY_T(old_data)->size, new_capacity);
|
||||
memcpy(context.new_data, old_data, element_size * CARR_ARRAY_T(context.new_data)->size);
|
||||
void* CARR_ring_buffer_realloc(CARR_ring_buffer_t* buf, size_t elem_size, size_t new_capacity) {
|
||||
if (buf != NULL && buf->capacity == new_capacity) {
|
||||
return buf->data;
|
||||
}
|
||||
CARR_ring_buffer_t* new_buf =
|
||||
(CARR_ring_buffer_t*) malloc(elem_size * new_capacity + offsetof(CARR_ring_buffer_t, data));
|
||||
if (new_buf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
new_buf->head = new_buf->tail = 0;
|
||||
new_buf->capacity = new_capacity;
|
||||
if (buf != NULL) {
|
||||
if (buf->tail > buf->head) {
|
||||
new_buf->tail = buf->tail - buf->head;
|
||||
memcpy(new_buf->data, buf->data + buf->head*elem_size, new_buf->tail*elem_size);
|
||||
} else if (buf->tail < buf->head) {
|
||||
new_buf->tail = buf->capacity + buf->tail - buf->head;
|
||||
memcpy(new_buf->data, buf->data + buf->head*elem_size, (buf->capacity-buf->head)*elem_size);
|
||||
memcpy(new_buf->data + (new_buf->tail-buf->tail)*elem_size, buf->data, buf->tail*elem_size);
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
CARR_context_free(&context, old_data);
|
||||
*handle = context.new_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
// === Ring buffers ===
|
||||
|
||||
bool CARR_ring_buffer_realloc(void** handle, size_t element_alignment, size_t element_size, size_t new_capacity) {
|
||||
void* old_data = *handle;
|
||||
if (old_data != NULL) {
|
||||
CARR_ring_buffer_t* old_buf = CARR_RING_BUFFER_T(old_data);
|
||||
if (old_buf->capacity == new_capacity) return true;
|
||||
// Shrinking is not supported.
|
||||
if ((old_buf->capacity + old_buf->tail - old_buf->head) % old_buf->capacity > new_capacity) return false;
|
||||
}
|
||||
CARR_context_t context =
|
||||
CARR_context_init(alignof(CARR_ring_buffer_t), sizeof(CARR_ring_buffer_t), element_alignment);
|
||||
if (new_capacity != 0) {
|
||||
if (!CARR_context_alloc(&context, element_size * new_capacity)) return false;
|
||||
CARR_ring_buffer_t* new_buf = CARR_RING_BUFFER_T(context.new_data);
|
||||
new_buf->capacity = new_capacity;
|
||||
new_buf->head = new_buf->tail = 0;
|
||||
if (old_data != NULL) {
|
||||
CARR_ring_buffer_t* old_buf = CARR_RING_BUFFER_T(old_data);
|
||||
if (old_buf->tail > old_buf->head) {
|
||||
new_buf->tail = old_buf->tail - old_buf->head;
|
||||
memcpy(context.new_data, (char*)old_data + old_buf->head*element_size, new_buf->tail*element_size);
|
||||
} else if (old_buf->tail < old_buf->head) {
|
||||
new_buf->tail = old_buf->capacity + old_buf->tail - old_buf->head;
|
||||
memcpy(context.new_data, (char*)old_data + old_buf->head*element_size,
|
||||
(old_buf->capacity-old_buf->head)*element_size);
|
||||
memcpy((char*)context.new_data + (new_buf->tail-old_buf->tail)*element_size, old_data,
|
||||
old_buf->tail*element_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
CARR_context_free(&context, old_data);
|
||||
*handle = context.new_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
// === Maps ===
|
||||
|
||||
static const size_t CARR_hash_map_primes[] = { 11U, 23U, 47U, 97U, 193U, 389U, 769U, 1543U, 3079U, 6151U,
|
||||
12289U, 24593U, 49157U, 98317U, 196613U, 393241U, 786433U,
|
||||
1572869U, 3145739U, 6291469U, 12582917U, 25165843U, 50331653U,
|
||||
100663319U, 201326611U, 402653189U, 805306457U, 1610612741U };
|
||||
static size_t CARR_hash_map_find_size(const size_t* table, unsigned int table_length, size_t min) {
|
||||
for (unsigned int i = 0; i < table_length; ++i) if (table[i] >= min) return table[i];
|
||||
return 0; // Do not return min, as this may break addressing variants which rely on specific numeric properties.
|
||||
}
|
||||
#define HASH_MAP_FIND_SIZE(TABLE, SIZE) CARR_hash_map_find_size(TABLE, SARRAY_COUNT_OF(TABLE), SIZE)
|
||||
|
||||
// Check whether memory chunk is non-zero.
|
||||
static bool CARR_check_range(const void* p, size_t alignment, size_t size) {
|
||||
switch (alignment) {
|
||||
case sizeof(uint8_t):
|
||||
case sizeof(uint16_t):{
|
||||
const uint8_t* data = p;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (data[i] != (uint8_t) 0) return true;
|
||||
}
|
||||
}break;
|
||||
case sizeof(uint32_t):{
|
||||
size >>= 2;
|
||||
const uint32_t* data = p;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (data[i] != (uint32_t) 0) return true;
|
||||
}
|
||||
}break;
|
||||
default:{
|
||||
size >>= 3;
|
||||
const uint64_t* data = p;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (data[i] != (uint64_t) 0) return true;
|
||||
}
|
||||
}break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool CARR_map_insert_all(CARR_MAP_LAYOUT_ARGS, void* src, void* dst) {
|
||||
if (src == NULL) return true;
|
||||
const CARR_map_dispatch_t* src_dispatch = ((const CARR_map_dispatch_t**)src)[-1];
|
||||
const CARR_map_dispatch_t* dst_dispatch = ((const CARR_map_dispatch_t**)dst)[-1];
|
||||
for (const void* key = NULL; (key = src_dispatch->next_key(CARR_MAP_LAYOUT_PASS, src, key)) != NULL;) {
|
||||
const void* value = src_dispatch->find(CARR_MAP_LAYOUT_PASS, src, key, NULL, false);
|
||||
void* new_value = dst_dispatch->find(CARR_MAP_LAYOUT_PASS, dst, key, NULL, true);
|
||||
if (new_value == NULL) return false; // Cannot insert.
|
||||
memcpy(new_value, value, value_size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// === Open addressing (probing) hash maps ===
|
||||
// Probing hash maps keep keys and values separately in two continuous aligned memory blocks.
|
||||
// This class is the most memory-efficient with no overhead other than fixed size header.
|
||||
// It provides O(1) lookup even when full, except for the cases of deletion and missing
|
||||
// key, which degrade down to O(N). This makes it a good choice for caches, which
|
||||
// only do "find or insert" and never delete elements.
|
||||
static const uint32_t CARR_hash_map_probing_rehash_bit = 0x80000000;
|
||||
static const uint32_t CARR_hash_map_probing_limit_mask = 0x7fffffff;
|
||||
typedef struct {
|
||||
size_t capacity;
|
||||
size_t size;
|
||||
uint32_t probing_limit;
|
||||
float load_factor;
|
||||
void* null_key_slot;
|
||||
CARR_equals_fp equals;
|
||||
CARR_hash_fp hash;
|
||||
void* dispatch_placeholder;
|
||||
} CARR_hash_map_probing_t;
|
||||
|
||||
static inline void* CARR_hash_map_probing_value_for(CARR_MAP_LAYOUT_ARGS, const void* data, const void* key_slot) {
|
||||
if (key_slot == NULL) return NULL;
|
||||
CARR_hash_map_probing_t* map = (CARR_hash_map_probing_t*) data - 1;
|
||||
size_t value_block_offset = CARR_align_size(value_alignment, key_size * map->capacity);
|
||||
return (char*)data + value_block_offset + ((const char*)key_slot - (char*)data) / key_size * value_size;
|
||||
}
|
||||
|
||||
static size_t CARR_hash_map_probing_check_extra_capacity(CARR_hash_map_probing_t* map, size_t count) {
|
||||
// Run length is a local metric, which directly correlate with lookup performance,
|
||||
// but can suffer from clustering, bad hash function, or bad luck.
|
||||
// Load factor is a global metric, which reflects "fullness",
|
||||
// but doesn't capture local effects, like clustering,
|
||||
// and is over-conservative for good distributions.
|
||||
// Therefore, we only rehash when both load factor and probing limit are exceeded.
|
||||
size_t new_capacity = map->size + count;
|
||||
if (new_capacity <= map->capacity) {
|
||||
if (!(map->probing_limit & CARR_hash_map_probing_rehash_bit)) { // Rehashing not requested.
|
||||
new_capacity = 0;
|
||||
} else if (map->size < (size_t)(map->load_factor * (float)map->capacity)) {
|
||||
map->probing_limit &= CARR_hash_map_probing_limit_mask; // Load factor too low, reset rehash flag.
|
||||
new_capacity = 0;
|
||||
} else new_capacity = map->capacity + 1;
|
||||
}
|
||||
return new_capacity;
|
||||
}
|
||||
|
||||
static const void* CARR_hash_map_probing_next_key(CARR_MAP_LAYOUT_ARGS, const void* data, const void* key_slot) {
|
||||
CARR_hash_map_probing_t* map = (CARR_hash_map_probing_t*) data - 1;
|
||||
char* slot;
|
||||
if (key_slot == NULL) slot = (char*)data;
|
||||
else if (key_slot < data) return NULL;
|
||||
else slot = (char*)key_slot + key_size;
|
||||
char* limit = (char*)data + key_size * (map->capacity - 1);
|
||||
for (; slot <= limit; slot += key_size) {
|
||||
if (CARR_check_range(slot, key_alignment, key_size) || slot == map->null_key_slot) return slot;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void CARR_hash_map_probing_clear(CARR_MAP_LAYOUT_ARGS, void* data) {
|
||||
CARR_hash_map_probing_t* map = (CARR_hash_map_probing_t*) data - 1;
|
||||
memset(data, 0, key_size * map->capacity);
|
||||
map->probing_limit &= CARR_hash_map_probing_limit_mask;
|
||||
map->null_key_slot = NULL;
|
||||
map->size = 0;
|
||||
}
|
||||
|
||||
static void CARR_hash_map_probing_free(CARR_MAP_LAYOUT_ARGS, void* data) {
|
||||
if (data == NULL) return;
|
||||
CARR_context_t context = CARR_context_init(alignof(CARR_hash_map_probing_t), sizeof(CARR_hash_map_probing_t),
|
||||
CARR_MAX(key_alignment, value_alignment));
|
||||
CARR_context_free(&context, data);
|
||||
}
|
||||
|
||||
// === Linear probing hash map ===
|
||||
static inline void CARR_hash_map_linear_probing_check_run(CARR_MAP_LAYOUT_ARGS, CARR_hash_map_probing_t* map,
|
||||
const char* from, const char* to) {
|
||||
if (map->probing_limit & CARR_hash_map_probing_rehash_bit) return; // Rehashing already requested.
|
||||
if (map->size < (size_t)(map->load_factor * (float)map->capacity)) return; // Load factor too low.
|
||||
ptrdiff_t offset = to - from;
|
||||
if (to < from) offset += (ptrdiff_t)(map->capacity * key_size);
|
||||
size_t run = (size_t)offset / key_size;
|
||||
// Set rehash bit if our probing length exceeded the limit.
|
||||
if (run > (size_t)map->probing_limit) map->probing_limit |= CARR_hash_map_probing_rehash_bit;
|
||||
}
|
||||
|
||||
static void* CARR_hash_map_linear_probing_find(CARR_MAP_LAYOUT_ARGS,
|
||||
void* data, const void* key, const void** resolved_key, bool insert) {
|
||||
CARR_hash_map_probing_t* map = (CARR_hash_map_probing_t*) data - 1;
|
||||
char* wrap = (char*)data + key_size * map->capacity;
|
||||
if (key >= data && key < (void*) wrap && ((const char*)key - (char*)data) % key_size == 0) {
|
||||
// Try fast access for resolved key.
|
||||
if (key == map->null_key_slot || CARR_check_range(key, key_alignment, key_size)) {
|
||||
if (resolved_key != NULL) *resolved_key = key;
|
||||
return CARR_hash_map_probing_value_for(CARR_MAP_LAYOUT_PASS, data, key);
|
||||
}
|
||||
}
|
||||
size_t hash = map->hash(key);
|
||||
char* start = (char*)data + key_size * (hash % map->capacity);
|
||||
char* slot = start;
|
||||
for (;;) {
|
||||
bool is_null = !CARR_check_range(slot, key_alignment, key_size);
|
||||
if (map->equals(key, slot)) {
|
||||
// Special case to distinguish null key from missing one.
|
||||
if (is_null) {
|
||||
if (map->null_key_slot == NULL && insert) {
|
||||
map->null_key_slot = slot;
|
||||
break; // Insert.
|
||||
}
|
||||
slot = map->null_key_slot;
|
||||
}
|
||||
if (resolved_key != NULL) *resolved_key = slot;
|
||||
return CARR_hash_map_probing_value_for(CARR_MAP_LAYOUT_PASS, data, slot);
|
||||
}
|
||||
if (is_null && slot != map->null_key_slot) { // Key not found.
|
||||
if (insert) break; // Insert.
|
||||
return resolved_key != NULL ? (void*)(*resolved_key = NULL) : NULL;
|
||||
}
|
||||
slot += key_size;
|
||||
if (slot == wrap) slot = (char*)data;
|
||||
if (slot == start) {
|
||||
return resolved_key != NULL ? (void*)(*resolved_key = NULL) : NULL; // We traversed the whole map.
|
||||
}
|
||||
}
|
||||
// Insert.
|
||||
void* value = CARR_hash_map_probing_value_for(CARR_MAP_LAYOUT_PASS, data, slot);
|
||||
memcpy(slot, key, key_size); // Copy key into slot.
|
||||
memset(value, 0, value_size); // Clear value.
|
||||
map->size++;
|
||||
if (resolved_key != NULL) {
|
||||
*resolved_key = slot;
|
||||
value = NULL; // Indicate that value was just inserted.
|
||||
}
|
||||
CARR_hash_map_linear_probing_check_run(CARR_MAP_LAYOUT_PASS, map, start, slot);
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool CARR_hash_map_linear_probing_remove(CARR_MAP_LAYOUT_ARGS, void* data, const void* key) {
|
||||
char* key_slot;
|
||||
CARR_hash_map_linear_probing_find(CARR_MAP_LAYOUT_PASS, data, key, (const void**) &key_slot, false);
|
||||
if (key_slot == NULL) return false;
|
||||
char* start = key_slot;
|
||||
CARR_hash_map_probing_t* map = (CARR_hash_map_probing_t*) data - 1;
|
||||
char* wrap = (char*)data + key_size * map->capacity;
|
||||
for (;;) {
|
||||
if (map->null_key_slot == key_slot) map->null_key_slot = NULL;
|
||||
char* slot = key_slot;
|
||||
for (;;) {
|
||||
slot += key_size;
|
||||
if (slot == wrap) slot = (char*)data;
|
||||
if (slot == start || (!CARR_check_range(slot, key_alignment, key_size) && slot != map->null_key_slot)) {
|
||||
memset(key_slot, 0, key_size); // Clear key slot.
|
||||
CARR_hash_map_linear_probing_check_run(CARR_MAP_LAYOUT_PASS, map, start, slot);
|
||||
return true;
|
||||
}
|
||||
size_t hash = map->hash(slot);
|
||||
char* expected_slot = (char*)data + key_size * (hash % map->capacity);
|
||||
if (slot >= expected_slot) {
|
||||
if (key_slot >= expected_slot && key_slot <= slot) break;
|
||||
} else {
|
||||
if (key_slot >= expected_slot || key_slot <= slot) break;
|
||||
}
|
||||
}
|
||||
// Move another entry into the gap.
|
||||
if (map->null_key_slot == slot) map->null_key_slot = key_slot;
|
||||
memcpy(key_slot, slot, key_size);
|
||||
memcpy(CARR_hash_map_probing_value_for(CARR_MAP_LAYOUT_PASS, data, key_slot),
|
||||
CARR_hash_map_probing_value_for(CARR_MAP_LAYOUT_PASS, data, slot), value_size);
|
||||
key_slot = slot; // Repeat with the new entry.
|
||||
}
|
||||
}
|
||||
|
||||
static bool CARR_hash_map_linear_probing_ensure_extra_capacity(CARR_MAP_LAYOUT_ARGS, void** handle, size_t count) {
|
||||
void* data = *handle;
|
||||
CARR_hash_map_probing_t* map = (CARR_hash_map_probing_t*) data - 1;
|
||||
size_t new_capacity = CARR_hash_map_probing_check_extra_capacity(map, count);
|
||||
if (new_capacity == 0) return true;
|
||||
return CARR_hash_map_linear_probing_rehash(CARR_MAP_LAYOUT_PASS, handle, map->equals, map->hash, new_capacity,
|
||||
map->probing_limit & CARR_hash_map_probing_limit_mask, map->load_factor);
|
||||
}
|
||||
|
||||
bool CARR_hash_map_linear_probing_rehash(CARR_MAP_LAYOUT_ARGS, void** handle, CARR_equals_fp equals, CARR_hash_fp hash,
|
||||
size_t new_capacity, uint32_t probing_limit, float load_factor) {
|
||||
size_t table_capacity = HASH_MAP_FIND_SIZE(CARR_hash_map_primes, new_capacity);
|
||||
if (table_capacity != 0) new_capacity = table_capacity;
|
||||
|
||||
CARR_context_t context = CARR_context_init(alignof(CARR_hash_map_probing_t), sizeof(CARR_hash_map_probing_t),
|
||||
CARR_MAX(key_alignment, value_alignment));
|
||||
size_t value_block_offset = CARR_align_size(value_alignment, key_size * new_capacity);
|
||||
if (!CARR_context_alloc(&context, value_block_offset + value_size * new_capacity)) return false;
|
||||
|
||||
CARR_hash_map_probing_t* map = (CARR_hash_map_probing_t*) context.new_data - 1;
|
||||
*map = (CARR_hash_map_probing_t) {
|
||||
.capacity = new_capacity,
|
||||
.size = 0,
|
||||
.probing_limit = CARR_MIN(probing_limit, CARR_hash_map_probing_limit_mask),
|
||||
.load_factor = load_factor,
|
||||
.null_key_slot = NULL,
|
||||
.equals = equals,
|
||||
.hash = hash
|
||||
};
|
||||
static const CARR_map_dispatch_t dispatch = {
|
||||
&CARR_hash_map_probing_next_key,
|
||||
&CARR_hash_map_linear_probing_find,
|
||||
&CARR_hash_map_linear_probing_remove,
|
||||
&CARR_hash_map_linear_probing_ensure_extra_capacity,
|
||||
&CARR_hash_map_probing_clear,
|
||||
&CARR_hash_map_probing_free,
|
||||
};
|
||||
((const CARR_map_dispatch_t**)context.new_data)[-1] = &dispatch;
|
||||
|
||||
CARR_hash_map_probing_clear(CARR_MAP_LAYOUT_PASS, context.new_data);
|
||||
if (!CARR_map_insert_all(CARR_MAP_LAYOUT_PASS, *handle, context.new_data)) {
|
||||
CARR_context_free(&context, context.new_data);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*handle != NULL) ((const CARR_map_dispatch_t**)*handle)[-1]->free(CARR_MAP_LAYOUT_PASS, *handle);
|
||||
*handle = context.new_data;
|
||||
return true;
|
||||
return new_buf->data;
|
||||
}
|
||||
|
||||
@@ -2,124 +2,72 @@
|
||||
#define C_ARRAY_UTIL_H
|
||||
|
||||
#include <malloc.h>
|
||||
#include <stdalign.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// C_ARRAY_UTIL_ALLOCATION_FAILED is called when allocation fails.
|
||||
// Default implementation calls abort().
|
||||
// Functions that can call C_ARRAY_UTIL_ALLOCATION_FAILED explicitly state
|
||||
// this in the documentation. Functions with *_TRY_* keep the data structure unchanged.
|
||||
// this in the documentation. Functions with *_TRY_* return NULL on failure.
|
||||
#ifndef C_ARRAY_UTIL_ALLOCATION_FAILED
|
||||
#include <stdlib.h>
|
||||
#define C_ARRAY_UTIL_ALLOCATION_FAILED() abort()
|
||||
#endif
|
||||
|
||||
// === Allocation helpers ===
|
||||
|
||||
#define CARR_MIN(a,b) (((a)<(b))?(a):(b))
|
||||
#define CARR_MAX(a,b) (((a)>(b))?(a):(b))
|
||||
|
||||
static inline bool CARR_handle_alloc(bool CARR_result, bool CARR_force) {
|
||||
if (CARR_result || !CARR_force) return CARR_result;
|
||||
C_ARRAY_UTIL_ALLOCATION_FAILED();
|
||||
return false;
|
||||
}
|
||||
static inline void consume(const void* value) {}
|
||||
|
||||
// === Arrays ===
|
||||
|
||||
#ifndef ARRAY_CAPACITY_GROW
|
||||
#define ARRAY_CAPACITY_GROW(C) (((C) * 3 + 1) / 2) // 1.5 multiplier
|
||||
#endif
|
||||
#ifndef ARRAY_DEFAULT_CAPACITY
|
||||
#define ARRAY_DEFAULT_CAPACITY 10
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
char data[];
|
||||
} CARR_array_t;
|
||||
|
||||
bool CARR_array_realloc(void** handle, size_t element_alignment, size_t element_size, size_t new_capacity);
|
||||
void* CARR_array_alloc(size_t elem_size, size_t capacity);
|
||||
void* CARR_array_realloc(CARR_array_t* vec, size_t elem_size, size_t new_capacity);
|
||||
|
||||
#define CARR_ARRAY_T(P) ((CARR_array_t*)(P) - 1) // NULL unsafe!
|
||||
typedef struct {
|
||||
size_t head;
|
||||
size_t tail;
|
||||
size_t capacity;
|
||||
char data[];
|
||||
} CARR_ring_buffer_t;
|
||||
|
||||
static inline void* CARR_array_alloc(size_t element_alignment, size_t element_size, size_t new_capacity) {
|
||||
void* data = NULL;
|
||||
CARR_array_realloc(&data, element_alignment, element_size, new_capacity);
|
||||
return data;
|
||||
}
|
||||
|
||||
static inline bool CARR_array_ensure_capacity(void** handle, size_t alignment, size_t size,
|
||||
size_t new_capacity, bool force) {
|
||||
void* data = *handle;
|
||||
if (new_capacity > (data == NULL ? 0 : CARR_ARRAY_T(data)->capacity)) {
|
||||
return CARR_handle_alloc(CARR_array_realloc(handle, alignment, size, new_capacity), force);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool CARR_array_resize(void** handle, size_t alignment, size_t size, size_t new_size, bool force) {
|
||||
if (CARR_array_ensure_capacity(handle, alignment, size, new_size, force)) {
|
||||
void* data = *handle;
|
||||
if (data != NULL) CARR_ARRAY_T(data)->size = new_size;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void CARR_array_push_back(void** handle, size_t alignment, size_t size) {
|
||||
void* data = *handle;
|
||||
if (data == NULL || CARR_ARRAY_T(data)->size >= CARR_ARRAY_T(data)->capacity) {
|
||||
size_t new_capacity = data == NULL ? ARRAY_DEFAULT_CAPACITY : ARRAY_CAPACITY_GROW(CARR_ARRAY_T(data)->size);
|
||||
if (!CARR_handle_alloc(CARR_array_realloc(handle, alignment, size, new_capacity), true)) return;
|
||||
data = *handle; // assert data != NULL
|
||||
}
|
||||
CARR_ARRAY_T(data)->size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic array declaration, e.g. ARRAY(int) my_array = NULL;
|
||||
* @param TYPE type of the array element.
|
||||
*/
|
||||
#define ARRAY(TYPE) TYPE*
|
||||
void* CARR_ring_buffer_realloc(CARR_ring_buffer_t* buf, size_t elem_size, size_t new_capacity);
|
||||
|
||||
/**
|
||||
* Allocate array. Returns NULL on allocation failure.
|
||||
* @param T type of elements
|
||||
* @param CAPACITY capacity of the array
|
||||
* @return pointer to the allocated array, or NULL
|
||||
*/
|
||||
#define ARRAY_ALLOC(T, CAPACITY) ((T*)CARR_array_alloc(alignof(T), sizeof(T), CAPACITY))
|
||||
#define ARRAY_ALLOC(T, CAPACITY) (T*)CARR_array_alloc(sizeof(T), CAPACITY)
|
||||
|
||||
#define ARRAY_T(P) ((CARR_array_t *)((P) == NULL ? NULL : (char*)(P) - offsetof(CARR_array_t, data)))
|
||||
|
||||
/**
|
||||
* @param P array
|
||||
* @param P pointer to the first data element of the array
|
||||
* @return size of the array
|
||||
*/
|
||||
#define ARRAY_SIZE(P) ((P) == NULL ? (size_t) 0 : (CARR_ARRAY_T(P))->size)
|
||||
#define ARRAY_SIZE(P) ((P) == NULL ? (size_t) 0 : (ARRAY_T(P))->size)
|
||||
|
||||
/**
|
||||
* @param P array
|
||||
* @param P pointer to the first data element of the array
|
||||
* @return capacity of the array
|
||||
*/
|
||||
#define ARRAY_CAPACITY(P) ((P) == NULL ? (size_t) 0 : (CARR_ARRAY_T(P))->capacity)
|
||||
#define ARRAY_CAPACITY(P) ((P) == NULL ? (size_t) 0 : (ARRAY_T(P))->capacity)
|
||||
|
||||
/**
|
||||
* @param P array
|
||||
* @return dereferenced pointer to the last element in the array
|
||||
* @param P pointer to the first data element of the array
|
||||
* @return last element in the array
|
||||
*/
|
||||
#define ARRAY_LAST(P) ((P)[ARRAY_SIZE(P) - 1])
|
||||
|
||||
/**
|
||||
* Deallocate the vector
|
||||
* @param P array
|
||||
* @param P pointer to the first data element of the array
|
||||
*/
|
||||
#define ARRAY_FREE(P) ((void)CARR_array_realloc((void**)&(P), alignof(*(P)), sizeof(*(P)), 0))
|
||||
#define ARRAY_FREE(P) free(ARRAY_T(P))
|
||||
|
||||
/**
|
||||
* Apply function to the array elements
|
||||
* @param P array
|
||||
* @param P pointer to the first data element of the array
|
||||
* @param F function to apply
|
||||
*/
|
||||
#define ARRAY_APPLY(P, F) do { \
|
||||
@@ -128,7 +76,7 @@ static inline void CARR_array_push_back(void** handle, size_t alignment, size_t
|
||||
|
||||
/**
|
||||
* Apply function to the array elements, passing pointer to an element as first parameter
|
||||
* @param P array
|
||||
* @param P pointer to the first data element of the array
|
||||
* @param F function to apply
|
||||
*/
|
||||
#define ARRAY_APPLY_LEADING(P, F, ...) do { \
|
||||
@@ -137,7 +85,7 @@ static inline void CARR_array_push_back(void** handle, size_t alignment, size_t
|
||||
|
||||
/**
|
||||
* Apply function to the array elements, passing pointer to an element as last parameter
|
||||
* @param P array
|
||||
* @param P pointer to the first data element of the array
|
||||
* @param F function to apply
|
||||
*/
|
||||
#define ARRAY_APPLY_TRAILING(P, F, ...) do { \
|
||||
@@ -145,367 +93,157 @@ static inline void CARR_array_push_back(void** handle, size_t alignment, size_t
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Ensure array capacity. Array is implicitly initialized when necessary.
|
||||
* On allocation failure, array is left unchanged.
|
||||
* @param P array
|
||||
* Ensure array capacity. Implicitly initializes when array is NULL.
|
||||
* On allocation failure, array is unchanged.
|
||||
* @param P pointer to the first data element of the array
|
||||
* @param CAPACITY required capacity of the array
|
||||
* @return true if the operation succeeded
|
||||
*/
|
||||
#define ARRAY_TRY_ENSURE_CAPACITY(P, CAPACITY) \
|
||||
CARR_array_ensure_capacity((void**)&(P), alignof(*(P)), sizeof(*(P)), (CAPACITY), false)
|
||||
#define ARRAY_TRY_ENSURE_CAPACITY(P, CAPACITY) do { \
|
||||
if ((P) == NULL) { \
|
||||
if ((CAPACITY) > 0) (P) = CARR_array_alloc(sizeof((P)[0]), CAPACITY); \
|
||||
} else if (ARRAY_CAPACITY(P) < (CAPACITY)) { \
|
||||
(P) = CARR_array_realloc(ARRAY_T(P), sizeof((P)[0]), CAPACITY); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Ensure array capacity. Array is implicitly initialized when necessary.
|
||||
* Ensure array capacity. Implicitly initializes when array is NULL.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P array
|
||||
* @param P pointer to the first data element of the array
|
||||
* @param CAPACITY required capacity of the array
|
||||
*/
|
||||
#define ARRAY_ENSURE_CAPACITY(P, CAPACITY) \
|
||||
((void)CARR_array_ensure_capacity((void**)&(P), alignof(*(P)), sizeof(*(P)), (CAPACITY), true))
|
||||
#define ARRAY_ENSURE_CAPACITY(P, CAPACITY) do { \
|
||||
ARRAY_TRY_ENSURE_CAPACITY(P, CAPACITY); \
|
||||
if (ARRAY_CAPACITY(P) < (CAPACITY)) C_ARRAY_UTIL_ALLOCATION_FAILED(); \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Shrink capacity of the array to its size.
|
||||
* On allocation failure, array is left unchanged.
|
||||
* @param P array
|
||||
* @return the array
|
||||
* @return true if the operation succeeded
|
||||
* On allocation failure, array is unchanged.
|
||||
* @param P pointer to the first data element of the array
|
||||
*/
|
||||
#define ARRAY_SHRINK_TO_FIT(P) CARR_array_realloc((void**)&(P), alignof(*(P)), sizeof(*(P)), ARRAY_SIZE(P))
|
||||
#define ARRAY_SHRINK_TO_FIT(P) do { \
|
||||
if ((P) != NULL) { \
|
||||
(P) = CARR_array_realloc(ARRAY_T(P), sizeof((P)[0]), ARRAY_SIZE(P)); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ARRAY_RESIZE_IMPL(P, SIZE, ...) do { \
|
||||
if ((P) != NULL || (SIZE) > 0) { \
|
||||
ARRAY_ENSURE_CAPACITY(P, SIZE); \
|
||||
if ((P) != NULL && (ARRAY_T(P))->capacity >= (SIZE)) { \
|
||||
(ARRAY_T(P))->size = (SIZE); \
|
||||
} __VA_ARGS__ \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Resize an array. Array is implicitly initialized when necessary.
|
||||
* On allocation failure, array is left unchanged.
|
||||
* @param P array
|
||||
* @param SIZE required size of the array
|
||||
* @return true if the operation succeeded
|
||||
*/
|
||||
#define ARRAY_TRY_RESIZE(P, SIZE) \
|
||||
CARR_array_resize((void**)&(P), alignof(*(P)), sizeof(*(P)), (SIZE), false)
|
||||
|
||||
/**
|
||||
* Resize an array. Array is implicitly initialized when necessary.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P array
|
||||
* Resize an array. Implicitly initializes when array is NULL.
|
||||
* On allocation failure, array is unchanged.
|
||||
* @param P pointer to the first data element of the array
|
||||
* @param SIZE required size of the array
|
||||
*/
|
||||
#define ARRAY_RESIZE(P, SIZE) \
|
||||
((void)CARR_array_resize((void**)&(P), alignof(*(P)), sizeof(*(P)), (SIZE), true))
|
||||
#define ARRAY_TRY_RESIZE(P, SIZE) ARRAY_RESIZE_IMPL(P, SIZE, )
|
||||
|
||||
/**
|
||||
* Add element to the end of the array. Array is implicitly initialized when necessary.
|
||||
* Resize an array. Implicitly initializes when array is NULL.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P array
|
||||
* @return dereferenced pointer to the inserted element
|
||||
* @param P pointer to the first data element of the array
|
||||
* @param SIZE required size of the array
|
||||
*/
|
||||
#define ARRAY_PUSH_BACK(P) \
|
||||
(*(CARR_array_push_back((void**)&(P), alignof(*(P)), sizeof(*(P))), (P) + ARRAY_SIZE(P) - 1))
|
||||
#define ARRAY_RESIZE(P, SIZE) ARRAY_RESIZE_IMPL(P, SIZE, else if ((SIZE) > 0) C_ARRAY_UTIL_ALLOCATION_FAILED();)
|
||||
|
||||
/**
|
||||
* Compile-time length of the static array.
|
||||
* Add element to the end of the array. Implicitly initializes when array is NULL.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P pointer to the first data element of the array
|
||||
*/
|
||||
#define ARRAY_PUSH_BACK(P, ...) do { \
|
||||
if ((P) == NULL) { \
|
||||
(P) = CARR_array_alloc(sizeof((P)[0]), ARRAY_DEFAULT_CAPACITY); \
|
||||
} else if (ARRAY_SIZE(P) >= ARRAY_CAPACITY(P)) { \
|
||||
(P) = CARR_array_realloc(ARRAY_T(P), sizeof((P)[0]), ARRAY_CAPACITY_GROW(ARRAY_SIZE(P))); \
|
||||
} \
|
||||
if (ARRAY_SIZE(P) >= ARRAY_CAPACITY(P)) C_ARRAY_UTIL_ALLOCATION_FAILED(); \
|
||||
*((P) + ARRAY_SIZE(P)) = (__VA_ARGS__); \
|
||||
(ARRAY_T(P))->size++; \
|
||||
} while(0)
|
||||
|
||||
#define SARRAY_COUNT_OF(STATIC_ARRAY) (sizeof(STATIC_ARRAY)/sizeof((STATIC_ARRAY)[0]))
|
||||
|
||||
// === Ring buffers ===
|
||||
|
||||
typedef struct {
|
||||
size_t head;
|
||||
size_t tail;
|
||||
size_t capacity;
|
||||
} CARR_ring_buffer_t;
|
||||
|
||||
bool CARR_ring_buffer_realloc(void** handle, size_t element_alignment, size_t element_size, size_t new_capacity);
|
||||
|
||||
#define CARR_RING_BUFFER_T(P) ((CARR_ring_buffer_t*)(P) - 1) // NULL / type unsafe!
|
||||
#define CARR_RING_BUFFER_IS_NULL(P) (&(P)->CARR_elem == NULL) // Guard against wrong pointer types.
|
||||
#define CARR_RING_BUFFER_GUARD(P, ...) (consume(&(P)->CARR_elem), __VA_ARGS__) // Guard against wrong pointer types.
|
||||
|
||||
static inline size_t CARR_ring_buffer_size(void* data) {
|
||||
CARR_ring_buffer_t* buffer = CARR_RING_BUFFER_T(data);
|
||||
return (buffer->capacity + buffer->tail - buffer->head) % buffer->capacity;
|
||||
}
|
||||
|
||||
static inline bool CARR_ring_buffer_ensure_can_push(void** handle, size_t alignment, size_t size, bool force) {
|
||||
void* data = *handle;
|
||||
if (data == NULL || CARR_ring_buffer_size(data) + 1 >= CARR_RING_BUFFER_T(data)->capacity) {
|
||||
size_t new_capacity = data == NULL ?
|
||||
ARRAY_DEFAULT_CAPACITY : ARRAY_CAPACITY_GROW(CARR_RING_BUFFER_T(data)->capacity);
|
||||
return CARR_handle_alloc(CARR_ring_buffer_realloc(handle, alignment, size, new_capacity), force);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline size_t CARR_ring_buffer_push_front(void* data) {
|
||||
if (data == NULL) return 0;
|
||||
CARR_ring_buffer_t* buffer = CARR_RING_BUFFER_T(data);
|
||||
return buffer->head = (buffer->head + buffer->capacity - 1) % buffer->capacity;
|
||||
}
|
||||
|
||||
static inline size_t CARR_ring_buffer_push_back(void* data) {
|
||||
if (data == NULL) return 0;
|
||||
CARR_ring_buffer_t* buffer = CARR_RING_BUFFER_T(data);
|
||||
size_t i = buffer->tail;
|
||||
buffer->tail = (buffer->tail + 1) % buffer->capacity;
|
||||
return i;
|
||||
}
|
||||
#define RING_BUFFER_T(P) ((CARR_ring_buffer_t *)((P) == NULL ? NULL : (char*)(P) - offsetof(CARR_ring_buffer_t, data)))
|
||||
|
||||
/**
|
||||
* Ring buffer declaration, e.g. RING_BUFFER(int) my_ring = NULL;
|
||||
* @param TYPE type of the ring buffer element.
|
||||
*/
|
||||
#define RING_BUFFER(TYPE) struct { TYPE CARR_elem; }*
|
||||
|
||||
/**
|
||||
* @param P ring buffer
|
||||
* @param P pointer to the first data element of the ring buffer
|
||||
* @return size of the ring buffer
|
||||
*/
|
||||
#define RING_BUFFER_SIZE(P) (CARR_RING_BUFFER_IS_NULL(P) ? (size_t) 0 : CARR_ring_buffer_size(P))
|
||||
#define RING_BUFFER_SIZE(P) ((P) == NULL ? (size_t) 0 : \
|
||||
(RING_BUFFER_T(P)->capacity + RING_BUFFER_T(P)->tail - RING_BUFFER_T(P)->head) % RING_BUFFER_T(P)->capacity)
|
||||
|
||||
/**
|
||||
* @param P ring buffer
|
||||
* @param P pointer to the first data element of the ring buffer
|
||||
* @return capacity of the ring buffer
|
||||
*/
|
||||
#define RING_BUFFER_CAPACITY(P) (CARR_RING_BUFFER_IS_NULL(P) ? (size_t) 0 : CARR_RING_BUFFER_T(P)->capacity)
|
||||
|
||||
/**
|
||||
* Ensure enough capacity to push an element into ring buffer. Implicitly initializes when buffer is NULL.
|
||||
* On allocation failure, buffer is left unchanged.
|
||||
* @param P ring buffer
|
||||
* @return true if the operation succeeded
|
||||
*/
|
||||
#define RING_BUFFER_TRY_ENSURE_CAN_PUSH(P) CARR_RING_BUFFER_GUARD((P), \
|
||||
CARR_ring_buffer_ensure_can_push((void**)&(P), alignof(*(P)), sizeof(*(P)), false))
|
||||
|
||||
/**
|
||||
* Ensure enough capacity to push an element into ring buffer. Implicitly initializes when buffer is NULL.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P ring buffer
|
||||
*/
|
||||
#define RING_BUFFER_ENSURE_CAN_PUSH(P) CARR_RING_BUFFER_GUARD((P), \
|
||||
(void)CARR_ring_buffer_ensure_can_push((void**)&(P), alignof(*(P)), sizeof(*(P)), true))
|
||||
#define RING_BUFFER_CAPACITY(P) ((P) == NULL ? (size_t) 0 : RING_BUFFER_T(P)->capacity)
|
||||
|
||||
/**
|
||||
* Add element to the beginning of the ring buffer. Implicitly initializes when buffer is NULL.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P ring buffer
|
||||
* @return dereferenced pointer to the inserted element
|
||||
* @param P pointer to the first data element of the buffer
|
||||
*/
|
||||
#define RING_BUFFER_PUSH_FRONT(P) \
|
||||
((RING_BUFFER_ENSURE_CAN_PUSH(P), (P) + CARR_ring_buffer_push_front(P))->CARR_elem)
|
||||
#define RING_BUFFER_PUSH_FRONT(P, ...) do { \
|
||||
if ((P) == NULL) (P) = CARR_ring_buffer_realloc(NULL, sizeof((P)[0]), ARRAY_DEFAULT_CAPACITY); \
|
||||
else if ((RING_BUFFER_T(P)->head + RING_BUFFER_T(P)->capacity - 1) % RING_BUFFER_T(P)->capacity == RING_BUFFER_T(P)->tail) \
|
||||
(P) = CARR_ring_buffer_realloc(RING_BUFFER_T(P), sizeof((P)[0]), ARRAY_CAPACITY_GROW(RING_BUFFER_T(P)->capacity)); \
|
||||
if ((P) == NULL) C_ARRAY_UTIL_ALLOCATION_FAILED(); \
|
||||
RING_BUFFER_T(P)->head = (RING_BUFFER_T(P)->head + RING_BUFFER_T(P)->capacity - 1) % RING_BUFFER_T(P)->capacity; \
|
||||
(P)[RING_BUFFER_T(P)->head] = (__VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Add element to the end of the ring buffer. Implicitly initializes when buffer is NULL.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P ring buffer
|
||||
* @return dereferenced pointer to the inserted element
|
||||
* @param P pointer to the first data element of the buffer
|
||||
*/
|
||||
#define RING_BUFFER_PUSH_BACK(P) \
|
||||
((RING_BUFFER_ENSURE_CAN_PUSH(P), (P) + CARR_ring_buffer_push_back(P))->CARR_elem)
|
||||
#define RING_BUFFER_PUSH_BACK(P, ...) do { \
|
||||
if ((P) == NULL) (P) = CARR_ring_buffer_realloc(NULL, sizeof((P)[0]), ARRAY_DEFAULT_CAPACITY); \
|
||||
else if ((RING_BUFFER_T(P)->tail + 1) % RING_BUFFER_T(P)->capacity == RING_BUFFER_T(P)->head) \
|
||||
(P) = CARR_ring_buffer_realloc(RING_BUFFER_T(P), sizeof((P)[0]), ARRAY_CAPACITY_GROW(RING_BUFFER_T(P)->capacity)); \
|
||||
if ((P) == NULL) C_ARRAY_UTIL_ALLOCATION_FAILED(); \
|
||||
(P)[RING_BUFFER_T(P)->tail] = (__VA_ARGS__); \
|
||||
RING_BUFFER_T(P)->tail = (RING_BUFFER_T(P)->tail + 1) % RING_BUFFER_T(P)->capacity; \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Get pointer to the first element of the ring buffer.
|
||||
* @param P ring buffer
|
||||
* @return pointer to the first element of the ring buffer, or NULL
|
||||
* @param P pointer to the first data element of the buffer
|
||||
*/
|
||||
#define RING_BUFFER_FRONT(P) (CARR_RING_BUFFER_IS_NULL(P) || \
|
||||
CARR_RING_BUFFER_T(P)->head == CARR_RING_BUFFER_T(P)->tail ? NULL : &(P)[CARR_RING_BUFFER_T(P)->head].CARR_elem)
|
||||
#define RING_BUFFER_FRONT(P) ((P) == NULL || RING_BUFFER_T(P)->head == RING_BUFFER_T(P)->tail ? NULL : &(P)[RING_BUFFER_T(P)->head])
|
||||
|
||||
/**
|
||||
* Get pointer to the last element of the ring buffer.
|
||||
* @param P ring buffer
|
||||
* @return pointer to the last element of the ring buffer, or NULL
|
||||
* @param P pointer to the first data element of the buffer
|
||||
*/
|
||||
#define RING_BUFFER_BACK(P) (CARR_RING_BUFFER_IS_NULL(P) || \
|
||||
CARR_RING_BUFFER_T(P)->head == CARR_RING_BUFFER_T(P)->tail ? NULL : \
|
||||
&(P)[(CARR_RING_BUFFER_T(P)->tail+CARR_RING_BUFFER_T(P)->capacity-1) % CARR_RING_BUFFER_T(P)->capacity].CARR_elem)
|
||||
#define RING_BUFFER_BACK(P) ((P) == NULL || RING_BUFFER_T(P)->head == RING_BUFFER_T(P)->tail ? NULL : \
|
||||
&(P)[(RING_BUFFER_T(P)->tail + RING_BUFFER_T(P)->capacity - 1) % RING_BUFFER_T(P)->capacity])
|
||||
|
||||
/**
|
||||
* Move beginning of the ring buffer forward (remove first element).
|
||||
* @param P ring buffer
|
||||
* @param P pointer to the first data element of the buffer
|
||||
*/
|
||||
#define RING_BUFFER_POP_FRONT(P) CARR_RING_BUFFER_GUARD((P), (void)(CARR_RING_BUFFER_T(P)->head = \
|
||||
(CARR_RING_BUFFER_T(P)->head + 1) % CARR_RING_BUFFER_T(P)->capacity))
|
||||
#define RING_BUFFER_POP_FRONT(P) RING_BUFFER_T(P)->head = (RING_BUFFER_T(P)->head + 1) % RING_BUFFER_T(P)->capacity
|
||||
|
||||
/**
|
||||
* Move end of the ring buffer backward (remove last element).
|
||||
* @param P ring buffer
|
||||
* @param P pointer to the first data element of the buffer
|
||||
*/
|
||||
#define RING_BUFFER_POP_BACK(P) CARR_RING_BUFFER_GUARD((P), (void)(CARR_RING_BUFFER_T(P)->tail = \
|
||||
(CARR_RING_BUFFER_T(P)->tail + CARR_RING_BUFFER_T(P)->capacity - 1) % CARR_RING_BUFFER_T(P)->capacity))
|
||||
#define RING_BUFFER_POP_BACK(P) \
|
||||
RING_BUFFER_T(P)->tail = (RING_BUFFER_T(P)->tail + RING_BUFFER_T(P)->capacity - 1) % RING_BUFFER_T(P)->capacity
|
||||
|
||||
/**
|
||||
* Deallocate the ring buffer
|
||||
* @param P ring buffer
|
||||
* @param P pointer to the first data element of the buffer
|
||||
*/
|
||||
#define RING_BUFFER_FREE(P) CARR_RING_BUFFER_GUARD((P), \
|
||||
(void)CARR_ring_buffer_realloc((void**)&(P), alignof(*(P)), sizeof(*(P)), 0))
|
||||
#define RING_BUFFER_FREE(P) free(RING_BUFFER_T(P))
|
||||
|
||||
// === Maps ===
|
||||
|
||||
typedef bool (*CARR_equals_fp)(const void* a, const void* b);
|
||||
typedef size_t (*CARR_hash_fp)(const void* data);
|
||||
|
||||
#define CARR_MAP_LAYOUT_ARGS size_t key_alignment, size_t key_size, size_t value_alignment, size_t value_size
|
||||
#define CARR_MAP_LAYOUT_PASS key_alignment, key_size, value_alignment, value_size
|
||||
#define CARR_MAP_LAYOUT(P) \
|
||||
alignof((P)->CARR_keys[0].CARR_key[0]), sizeof((P)->CARR_keys[0].CARR_key[0]), \
|
||||
alignof((P)->CARR_values[0].CARR_value[0]), sizeof((P)->CARR_values[0].CARR_value[0])
|
||||
|
||||
typedef const void* (*CARR_map_dispatch_next_key_fp)(CARR_MAP_LAYOUT_ARGS, const void* data, const void* key_slot);
|
||||
typedef void* (*CARR_map_dispatch_find_fp)(CARR_MAP_LAYOUT_ARGS,
|
||||
void* data, const void* key, const void** resolved_key, bool insert);
|
||||
typedef bool (*CARR_map_dispatch_remove_fp)(CARR_MAP_LAYOUT_ARGS, void* data, const void* key);
|
||||
typedef bool (*CARR_map_dispatch_ensure_extra_capacity_fp)(CARR_MAP_LAYOUT_ARGS, void** handle, size_t count);
|
||||
typedef void (*CARR_map_dispatch_clear_fp)(CARR_MAP_LAYOUT_ARGS, void* data);
|
||||
typedef void (*CARR_map_dispatch_free_fp)(CARR_MAP_LAYOUT_ARGS, void* data);
|
||||
|
||||
typedef struct {
|
||||
CARR_map_dispatch_next_key_fp next_key;
|
||||
CARR_map_dispatch_find_fp find;
|
||||
CARR_map_dispatch_remove_fp remove;
|
||||
CARR_map_dispatch_ensure_extra_capacity_fp ensure_extra_capacity;
|
||||
CARR_map_dispatch_clear_fp clear;
|
||||
CARR_map_dispatch_free_fp free;
|
||||
} CARR_map_dispatch_t;
|
||||
|
||||
#define CARR_MAP_KEY_PTR(P, ...) \
|
||||
(&((true ? NULL : (P))->CARR_keys[((uintptr_t)(__VA_ARGS__) / sizeof((P)->CARR_keys[0]) - 1)].CARR_key[0]))
|
||||
#define CARR_MAP_VALUE_PTR(P, ...) \
|
||||
(&((true ? NULL : (P))->CARR_values[((uintptr_t)(__VA_ARGS__) / sizeof((P)->CARR_values[0]) - 1)].CARR_value[0]))
|
||||
#define CARR_MAP_KEY_GUARD(P, ...) \
|
||||
(true ? (__VA_ARGS__) : &(P)->CARR_keys[0].CARR_key[0]) // Guard against wrong key types.
|
||||
#define CARR_MAP_DISPATCH(P, NAME, ...) \
|
||||
(((const CARR_map_dispatch_t**)(P))[-1]->NAME(CARR_MAP_LAYOUT(P), __VA_ARGS__))
|
||||
|
||||
bool CARR_hash_map_linear_probing_rehash(CARR_MAP_LAYOUT_ARGS, void** handle, CARR_equals_fp equals, CARR_hash_fp hash,
|
||||
size_t new_capacity, uint32_t probing_limit, float load_factor);
|
||||
|
||||
/**
|
||||
* Map declaration, e.g. MAP(int, int) my_map = NULL;
|
||||
* Map must be explicitly initialized before usage, e.g. via HASH_MAP_REHASH.
|
||||
* @param KEY_TYPE type of the map key.
|
||||
* @param VALUE_TYPE type of the map value.
|
||||
*/
|
||||
#define MAP(KEY_TYPE, VALUE_TYPE) union { \
|
||||
struct { char CARR_dummy; const KEY_TYPE CARR_key[]; } CARR_keys[1]; \
|
||||
struct { char CARR_dummy; VALUE_TYPE CARR_value[]; } CARR_values[1]; \
|
||||
}*
|
||||
|
||||
/**
|
||||
* Rehash a hash map with given strategy. It will be initialized if NULL.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* List of available strategies with accepted parameters and sensible defaults:
|
||||
* 1. linear_probing (
|
||||
* CARR_equals_fp equals, // Key comparison function.
|
||||
* CARR_hash_fp hash, // Key hash calculation function.
|
||||
* size_t new_capacity, // New (min) capacity, must not be less than current number of items. Can be 0.
|
||||
* uint32_t probing_limit, // Search length, triggering rehash. Must not be too low, around 10 should be fine?
|
||||
* float load_factor // Min load factor needed to allow rehash triggered by probing_limit. 0.75 is fine.
|
||||
* )
|
||||
* @param P map
|
||||
* @param STRATEGY strategy to use
|
||||
* @param ... parameters for the rehash strategy
|
||||
*/
|
||||
#define HASH_MAP_REHASH(P, STRATEGY, ...) \
|
||||
((void)CARR_handle_alloc(CARR_hash_map_##STRATEGY##_rehash(CARR_MAP_LAYOUT(P), (void**)&(P), __VA_ARGS__), true))
|
||||
|
||||
/**
|
||||
* Rehash a hash map with given strategy. It will be initialized if NULL.
|
||||
* On allocation failure, map is left unchanged.
|
||||
* For list of available strategies see HASH_MAP_REHASH.
|
||||
* @param P map
|
||||
* @param STRATEGY strategy to use
|
||||
* @return true if the operation succeeded
|
||||
*/
|
||||
#define HASH_MAP_TRY_REHASH(P, STRATEGY, ...) \
|
||||
(CARR_hash_map_##STRATEGY##_rehash(CARR_MAP_LAYOUT(P), (void**)&(P), __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* Find the next resolved key present in the map, or NULL.
|
||||
* Enumeration order is implementation-defined.
|
||||
* @param P map
|
||||
* @param KEY_PTR pointer to the current resolved key, or NULL
|
||||
* @return pointer to the next resolved key
|
||||
*/
|
||||
#define MAP_NEXT_KEY(P, KEY_PTR) \
|
||||
CARR_MAP_KEY_PTR((P), CARR_MAP_DISPATCH((P), next_key, (P), CARR_MAP_KEY_GUARD((P), (KEY_PTR))))
|
||||
|
||||
/**
|
||||
* Find a value for the provided key.
|
||||
* @param P map
|
||||
* @param KEY key to find, can be a compound literal, like (int){0}
|
||||
* @return pointer to the found value, or NULL
|
||||
*/
|
||||
#define MAP_FIND(P, KEY) \
|
||||
CARR_MAP_VALUE_PTR((P), CARR_MAP_DISPATCH((P), find, (P), CARR_MAP_KEY_GUARD((P), &(KEY)), NULL, false))
|
||||
|
||||
/**
|
||||
* Find a value for the provided key, or insert a new one.
|
||||
* Value is zeroed for newly inserted items.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P map
|
||||
* @param KEY key to find, can be a compound literal, like (int){0}
|
||||
* @return dereferenced pointer to the found value
|
||||
*/
|
||||
#define MAP_AT(P, KEY) (*(MAP_ENSURE_EXTRA_CAPACITY((P), 1), \
|
||||
CARR_MAP_VALUE_PTR((P), CARR_MAP_DISPATCH((P), find, (P), CARR_MAP_KEY_GUARD((P), &(KEY)), NULL, true))))
|
||||
|
||||
/**
|
||||
* Resolve provided key and find corresponding value.
|
||||
* Using resolved key addresses speeds up subsequent map operations.
|
||||
* @param P map
|
||||
* @param KEY_PTR pointer to the key to find, replaced with resolved key address, or NULL
|
||||
* @return pointer to the found value, or NULL
|
||||
*/
|
||||
#define MAP_RESOLVE(P, KEY_PTR) CARR_MAP_VALUE_PTR((P), \
|
||||
CARR_MAP_DISPATCH((P), find, (P), CARR_MAP_KEY_GUARD((P), (KEY_PTR)), (const void**) &(KEY_PTR), false))
|
||||
|
||||
/**
|
||||
* Resolve provided key and find corresponding value, or insert a new one.
|
||||
* Using resolved key addresses speeds up subsequent map operations.
|
||||
* Returned value pointer may be NULL, indicating that the entry was just inserted, use MAP_FIND or MAP_AT to access it.
|
||||
* On allocation failure, map is left unchanged.
|
||||
* @param P map
|
||||
* @param KEY_PTR pointer to the key to find, replaced with resolved key address
|
||||
* @return pointer to the found value, or NULL
|
||||
*/
|
||||
#define MAP_RESOLVE_OR_INSERT(P, KEY_PTR) (MAP_TRY_ENSURE_EXTRA_CAPACITY((P), 1), CARR_MAP_VALUE_PTR((P), \
|
||||
CARR_MAP_DISPATCH((P), find, (P), CARR_MAP_KEY_GUARD((P), (KEY_PTR)), (const void**) &(KEY_PTR), true)))
|
||||
|
||||
/**
|
||||
* Remove the provided key, if one exists.
|
||||
* @param P map
|
||||
* @param KEY key to remove, can be a compound literal, like (int){0}
|
||||
* @return true if the key was removed
|
||||
*/
|
||||
#define MAP_REMOVE(P, KEY) CARR_MAP_DISPATCH((P), remove, (P), CARR_MAP_KEY_GUARD((P), &(KEY)))
|
||||
|
||||
/**
|
||||
* Ensure that map has enough capacity to insert COUNT more items without reallocation.
|
||||
* On allocation failure, C_ARRAY_UTIL_ALLOCATION_FAILED is called.
|
||||
* @param P map
|
||||
* @param COUNT number of new items
|
||||
*/
|
||||
#define MAP_ENSURE_EXTRA_CAPACITY(P, COUNT) ((void)CARR_handle_alloc(MAP_TRY_ENSURE_EXTRA_CAPACITY((P), (COUNT)), true))
|
||||
|
||||
/**
|
||||
* Ensure that map has enough capacity to insert COUNT more items without reallocation.
|
||||
* On allocation failure, map is left unchanged.
|
||||
* @param P map
|
||||
* @param COUNT number of new items
|
||||
* @return true if the operation succeeded
|
||||
*/
|
||||
#define MAP_TRY_ENSURE_EXTRA_CAPACITY(P, COUNT) CARR_MAP_DISPATCH((P), ensure_extra_capacity, (void**)&(P), (COUNT))
|
||||
|
||||
/**
|
||||
* Clear the map.
|
||||
* @param P map
|
||||
*/
|
||||
#define MAP_CLEAR(P) CARR_MAP_DISPATCH((P), clear, (P))
|
||||
|
||||
/**
|
||||
* Free the map.
|
||||
* @param P map
|
||||
*/
|
||||
#define MAP_FREE(P) ((P) == NULL ? 0 : CARR_MAP_DISPATCH((P), free, (P)), (void)((P) = NULL))
|
||||
|
||||
#endif // C_ARRAY_UTIL_H
|
||||
#endif // CARRAYUTILS_H
|
||||
|
||||
@@ -87,12 +87,12 @@ typedef union MemoryHandle {
|
||||
#define MAX_SHARED_PAGE_SIZE ((1ULL << MAX_BLOCK_LEVEL) * BLOCK_SIZE)
|
||||
|
||||
typedef struct {
|
||||
ARRAY(BlockPair) blockPairs;
|
||||
void* mappedData;
|
||||
uint32_t freeLevelIndices[MAX_BLOCK_LEVEL+1]; // Indices start from 1
|
||||
uint32_t freeBlockPairIndex; // Indices start from 1
|
||||
uint32_t nextPageIndex;
|
||||
uint32_t memoryType;
|
||||
BlockPair* blockPairs;
|
||||
void* mappedData;
|
||||
uint32_t freeLevelIndices[MAX_BLOCK_LEVEL+1]; // Indices start from 1
|
||||
uint32_t freeBlockPairIndex; // Indices start from 1
|
||||
uint32_t nextPageIndex;
|
||||
uint32_t memoryType;
|
||||
} SharedPageData;
|
||||
|
||||
typedef struct {
|
||||
@@ -120,9 +120,9 @@ struct VKAllocator {
|
||||
VKDevice* device;
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
|
||||
ARRAY(Page) pages;
|
||||
uint32_t freePageIndex;
|
||||
Pool pools[VK_MAX_MEMORY_TYPES];
|
||||
Page* pages;
|
||||
uint32_t freePageIndex;
|
||||
Pool pools[VK_MAX_MEMORY_TYPES];
|
||||
};
|
||||
|
||||
#define NO_PAGE_INDEX (~0U)
|
||||
@@ -222,7 +222,7 @@ static uint32_t VKAllocator_AllocatePage(VKAllocator* alloc, uint32_t memoryType
|
||||
} else {
|
||||
index = ARRAY_SIZE(alloc->pages);
|
||||
VK_RUNTIME_ASSERT(index < MAX_PAGES);
|
||||
ARRAY_PUSH_BACK(alloc->pages) = (Page) {};
|
||||
ARRAY_PUSH_BACK(alloc->pages, (Page) {});
|
||||
page = &ARRAY_LAST(alloc->pages);
|
||||
}
|
||||
assert(page->memory == VK_NULL_HANDLE);
|
||||
@@ -275,7 +275,7 @@ static uint32_t VKAllocator_PopFreeBlockPair(SharedPageData* data, uint32_t leve
|
||||
pair = &data->blockPairs[pairIndex-1];
|
||||
data->freeBlockPairIndex = pair->nextFree;
|
||||
} else {
|
||||
ARRAY_PUSH_BACK(data->blockPairs) = (BlockPair) {};
|
||||
ARRAY_PUSH_BACK(data->blockPairs, (BlockPair) {});
|
||||
pairIndex = ARRAY_SIZE(data->blockPairs);
|
||||
pair = &data->blockPairs[pairIndex-1];
|
||||
}
|
||||
@@ -369,7 +369,7 @@ static AllocationResult VKAllocator_AllocateForResource(VKMemoryRequirements* re
|
||||
// Adjust level to ensure proper alignment. Not very optimal, but this is a very rare case.
|
||||
while (blockSize % alignment != 0) { level++; blockSize <<= 1; }
|
||||
|
||||
J2dRlsTraceLn6(J2D_TRACE_VERBOSE2,
|
||||
J2dRlsTraceLn6(J2D_TRACE_VERBOSE,
|
||||
"VKAllocator_Allocate: level=%d, blockSize=%d, size=%d, alignment=%d, memoryType=%d, dedicated=%d",
|
||||
level, blockSize, size, alignment, memoryType, dedicated);
|
||||
|
||||
@@ -401,13 +401,13 @@ static AllocationResult VKAllocator_AllocateForResource(VKMemoryRequirements* re
|
||||
data = page->sharedPageData = (SharedPageData*) calloc(1, sizeof(SharedPageData));
|
||||
VK_RUNTIME_ASSERT(page->sharedPageData);
|
||||
data->memoryType = memoryType;
|
||||
ARRAY_PUSH_BACK(data->blockPairs) = (BlockPair) {
|
||||
.offset = 0,
|
||||
.parent = 0,
|
||||
.nextFree = 0,
|
||||
.firstFree = 1,
|
||||
.secondFree = 0,
|
||||
};
|
||||
ARRAY_PUSH_BACK(data->blockPairs, (BlockPair) {
|
||||
.offset = 0,
|
||||
.parent = 0,
|
||||
.nextFree = 0,
|
||||
.firstFree = 1,
|
||||
.secondFree = 0,
|
||||
});
|
||||
data->freeLevelIndices[pageLevel] = 1;
|
||||
data->nextPageIndex = pool->sharedPagesIndex;
|
||||
pool->sharedPagesIndex = pageIndex;
|
||||
|
||||
@@ -83,8 +83,6 @@ static void vulkanLibClose() {
|
||||
}
|
||||
#endif
|
||||
|
||||
VKComposites_Destroy(geInstance->composites);
|
||||
|
||||
if (geInstance->vkDestroyInstance != NULL) {
|
||||
geInstance->vkDestroyInstance(geInstance->vkInstance, NULL);
|
||||
}
|
||||
@@ -207,13 +205,13 @@ static jboolean VK_InitGraphicsEnvironment(PFN_vkGetInstanceProcAddr vkGetInstan
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, " %s", (char *) extensions[i].extensionName)
|
||||
}
|
||||
|
||||
ARRAY(pchar) enabledLayers = NULL;
|
||||
ARRAY(pchar) enabledExtensions = NULL;
|
||||
pchar* enabledLayers = NULL;
|
||||
pchar* enabledExtensions = NULL;
|
||||
void *pNext = NULL;
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
ARRAY_PUSH_BACK(enabledExtensions) = VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME;
|
||||
ARRAY_PUSH_BACK(enabledExtensions, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
||||
#endif
|
||||
ARRAY_PUSH_BACK(enabledExtensions) = VK_KHR_SURFACE_EXTENSION_NAME;
|
||||
ARRAY_PUSH_BACK(enabledExtensions, VK_KHR_SURFACE_EXTENSION_NAME);
|
||||
|
||||
// Check required layers & extensions.
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(enabledExtensions); i++) {
|
||||
@@ -266,8 +264,8 @@ static jboolean VK_InitGraphicsEnvironment(PFN_vkGetInstanceProcAddr vkGetInstan
|
||||
}
|
||||
|
||||
if (foundDebugLayer && foundDebugExt) {
|
||||
ARRAY_PUSH_BACK(enabledLayers) = VALIDATION_LAYER_NAME;
|
||||
ARRAY_PUSH_BACK(enabledExtensions) = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
|
||||
ARRAY_PUSH_BACK(enabledLayers, VALIDATION_LAYER_NAME);
|
||||
ARRAY_PUSH_BACK(enabledExtensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
pNext = &features;
|
||||
} else {
|
||||
J2dRlsTraceLn2(J2D_TRACE_WARNING, "Vulkan: %s and %s are not supported",
|
||||
@@ -304,8 +302,6 @@ static jboolean VK_InitGraphicsEnvironment(PFN_vkGetInstanceProcAddr vkGetInstan
|
||||
ARRAY_FREE(enabledLayers);
|
||||
ARRAY_FREE(enabledExtensions);
|
||||
|
||||
geInstance->composites = VKComposites_Create();
|
||||
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
INSTANCE_PROC(vkGetPhysicalDeviceWaylandPresentationSupportKHR);
|
||||
INSTANCE_PROC(vkCreateWaylandSurfaceKHR);
|
||||
@@ -327,26 +323,25 @@ static jboolean VK_InitGraphicsEnvironment(PFN_vkGetInstanceProcAddr vkGetInstan
|
||||
|
||||
// Create debug messenger
|
||||
#if defined(DEBUG)
|
||||
if (foundDebugLayer && foundDebugExt) {
|
||||
INSTANCE_PROC(vkCreateDebugUtilsMessengerEXT);
|
||||
INSTANCE_PROC(vkDestroyDebugUtilsMessengerEXT);
|
||||
if (pNext) {
|
||||
VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||||
.flags = 0,
|
||||
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT,
|
||||
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
||||
.pfnUserCallback = &debugCallback
|
||||
};
|
||||
VK_IF_ERROR(geInstance->vkCreateDebugUtilsMessengerEXT(geInstance->vkInstance, &debugUtilsMessengerCreateInfo,
|
||||
NULL, &geInstance->debugMessenger)) {}
|
||||
}
|
||||
INSTANCE_PROC(vkCreateDebugUtilsMessengerEXT);
|
||||
INSTANCE_PROC(vkDestroyDebugUtilsMessengerEXT);
|
||||
if (pNext) {
|
||||
VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||||
.flags = 0,
|
||||
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT,
|
||||
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
|
||||
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
||||
.pfnUserCallback = &debugCallback
|
||||
};
|
||||
VK_IF_ERROR(geInstance->vkCreateDebugUtilsMessengerEXT(geInstance->vkInstance, &debugUtilsMessengerCreateInfo,
|
||||
NULL, &geInstance->debugMessenger)) {}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return JNI_TRUE;
|
||||
@@ -478,9 +473,9 @@ static jboolean VK_FindDevices() {
|
||||
continue;
|
||||
}
|
||||
|
||||
ARRAY(pchar) deviceEnabledLayers = NULL;
|
||||
ARRAY(pchar) deviceEnabledExtensions = NULL;
|
||||
ARRAY_PUSH_BACK(deviceEnabledExtensions) = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
|
||||
pchar* deviceEnabledLayers = NULL;
|
||||
pchar* deviceEnabledExtensions = NULL;
|
||||
ARRAY_PUSH_BACK(deviceEnabledExtensions, VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
|
||||
// Validation layer
|
||||
#ifdef DEBUG
|
||||
@@ -488,7 +483,7 @@ static jboolean VK_FindDevices() {
|
||||
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;
|
||||
ARRAY_PUSH_BACK(deviceEnabledLayers, VALIDATION_LAYER_NAME);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -502,14 +497,15 @@ static jboolean VK_FindDevices() {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
ARRAY_PUSH_BACK(geInstance->devices) = (VKDevice) {
|
||||
.name = deviceName,
|
||||
.handle = VK_NULL_HANDLE,
|
||||
.physicalDevice = geInstance->physicalDevices[i],
|
||||
.queueFamily = queueFamily,
|
||||
.enabledLayers = deviceEnabledLayers,
|
||||
.enabledExtensions = deviceEnabledExtensions
|
||||
};
|
||||
ARRAY_PUSH_BACK(geInstance->devices,
|
||||
((VKDevice) {
|
||||
.name = deviceName,
|
||||
.handle = VK_NULL_HANDLE,
|
||||
.physicalDevice = geInstance->physicalDevices[i],
|
||||
.queueFamily = queueFamily,
|
||||
.enabledLayers = deviceEnabledLayers,
|
||||
.enabledExtensions = deviceEnabledExtensions
|
||||
}));
|
||||
}
|
||||
if (ARRAY_SIZE(geInstance->devices) == 0) {
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, "Vulkan: No compatible device found")
|
||||
@@ -618,14 +614,10 @@ static jboolean VK_InitDevice(VKDevice* device) {
|
||||
DEVICE_PROC(vkDestroyDescriptorSetLayout);
|
||||
DEVICE_PROC(vkUpdateDescriptorSets);
|
||||
DEVICE_PROC(vkCreateDescriptorPool);
|
||||
DEVICE_PROC(vkDestroyDescriptorPool);
|
||||
DEVICE_PROC(vkAllocateDescriptorSets);
|
||||
DEVICE_PROC(vkCmdBindDescriptorSets);
|
||||
DEVICE_PROC(vkGetImageMemoryRequirements2);
|
||||
DEVICE_PROC(vkCreateBuffer);
|
||||
DEVICE_PROC(vkDestroyBuffer);
|
||||
DEVICE_PROC(vkCreateBufferView);
|
||||
DEVICE_PROC(vkDestroyBufferView);
|
||||
DEVICE_PROC(vkGetBufferMemoryRequirements2);
|
||||
DEVICE_PROC(vkBindBufferMemory);
|
||||
DEVICE_PROC(vkMapMemory);
|
||||
@@ -633,6 +625,7 @@ static jboolean VK_InitDevice(VKDevice* device) {
|
||||
DEVICE_PROC(vkCmdBindVertexBuffers);
|
||||
DEVICE_PROC(vkCreateRenderPass);
|
||||
DEVICE_PROC(vkDestroyRenderPass);
|
||||
DEVICE_PROC(vkDestroyBuffer);
|
||||
DEVICE_PROC(vkFreeMemory);
|
||||
DEVICE_PROC(vkDestroyImageView);
|
||||
DEVICE_PROC(vkDestroyImage);
|
||||
|
||||
@@ -27,18 +27,16 @@
|
||||
#ifndef VKBase_h_Included
|
||||
#define VKBase_h_Included
|
||||
#include "VKTypes.h"
|
||||
#include "VKComposites.h"
|
||||
#include "VKTexturePool.h"
|
||||
#include "VKRenderState.h"
|
||||
#include "VKUtil.h"
|
||||
|
||||
struct VKDevice {
|
||||
VkDevice handle;
|
||||
VkPhysicalDevice physicalDevice;
|
||||
char* name;
|
||||
uint32_t queueFamily;
|
||||
ARRAY(pchar) enabledLayers;
|
||||
ARRAY(pchar) enabledExtensions;
|
||||
pchar* enabledLayers;
|
||||
pchar* enabledExtensions;
|
||||
VkQueue queue;
|
||||
|
||||
VKAllocator* allocator;
|
||||
@@ -94,14 +92,10 @@ struct VKDevice {
|
||||
PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
|
||||
PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
|
||||
PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
|
||||
PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
|
||||
PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
|
||||
PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
|
||||
PFN_vkGetImageMemoryRequirements2 vkGetImageMemoryRequirements2;
|
||||
PFN_vkCreateBuffer vkCreateBuffer;
|
||||
PFN_vkDestroyBuffer vkDestroyBuffer;
|
||||
PFN_vkCreateBufferView vkCreateBufferView;
|
||||
PFN_vkDestroyBufferView vkDestroyBufferView;
|
||||
PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2;
|
||||
PFN_vkBindBufferMemory vkBindBufferMemory;
|
||||
PFN_vkMapMemory vkMapMemory;
|
||||
@@ -109,6 +103,7 @@ struct VKDevice {
|
||||
PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
|
||||
PFN_vkCreateRenderPass vkCreateRenderPass;
|
||||
PFN_vkDestroyRenderPass vkDestroyRenderPass;
|
||||
PFN_vkDestroyBuffer vkDestroyBuffer;
|
||||
PFN_vkFreeMemory vkFreeMemory;
|
||||
PFN_vkDestroyImageView vkDestroyImageView;
|
||||
PFN_vkDestroyImage vkDestroyImage;
|
||||
@@ -121,12 +116,10 @@ struct VKDevice {
|
||||
};
|
||||
|
||||
struct VKGraphicsEnvironment {
|
||||
VkInstance vkInstance;
|
||||
ARRAY(VkPhysicalDevice) physicalDevices;
|
||||
ARRAY(VKDevice) devices;
|
||||
VKDevice* currentDevice;
|
||||
|
||||
VKComposites composites;
|
||||
VkInstance vkInstance;
|
||||
VkPhysicalDevice* physicalDevices;
|
||||
VKDevice* devices;
|
||||
VKDevice* currentDevice;
|
||||
|
||||
#if defined(DEBUG)
|
||||
VkDebugUtilsMessengerEXT debugMessenger;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include "Trace.h"
|
||||
#include "VKImage.h"
|
||||
#include "VKBuffer.h"
|
||||
#include "VKUtil.h"
|
||||
#include "CArrayUtil.h"
|
||||
|
||||
static void VKBlitSwToTextureViaPooledTexture(VKRenderingContext* context, VKImage* dest, const SurfaceDataRasInfo *srcInfo,
|
||||
int dx1, int dy1, int dx2, int dy2) {
|
||||
@@ -54,7 +54,7 @@ static void VKBlitSwToTextureViaPooledTexture(VKRenderingContext* context, VKIma
|
||||
return;
|
||||
}
|
||||
|
||||
ARRAY(VKTxVertex) vertices = ARRAY_ALLOC(VKTxVertex, 4);
|
||||
VKTxVertex* vertices = ARRAY_ALLOC(VKTxVertex, 4);
|
||||
/*
|
||||
* (p1)---------(p2)
|
||||
* | |
|
||||
@@ -67,10 +67,10 @@ static void VKBlitSwToTextureViaPooledTexture(VKRenderingContext* context, VKIma
|
||||
double u = (double)sw / VKTexturePoolHandle_GetActualWidth(hnd);
|
||||
double v = (double)sh / VKTexturePoolHandle_GetActualHeight(hnd);
|
||||
|
||||
ARRAY_PUSH_BACK(vertices) = (VKTxVertex) {dx1, dy1, 0.0f, 0.0f};
|
||||
ARRAY_PUSH_BACK(vertices) = (VKTxVertex) {dx2, dy1, u, 0.0f};
|
||||
ARRAY_PUSH_BACK(vertices) = (VKTxVertex) {dx1, dy2, 0.0f, v};
|
||||
ARRAY_PUSH_BACK(vertices) = (VKTxVertex) {dx2, dy2, u, v};
|
||||
ARRAY_PUSH_BACK(vertices, ((VKTxVertex) {dx1, dy1, 0.0f, 0.0f}));
|
||||
ARRAY_PUSH_BACK(vertices, ((VKTxVertex) {dx2, dy1, u, 0.0f}));
|
||||
ARRAY_PUSH_BACK(vertices, ((VKTxVertex) {dx1, dy2, 0.0f, v}));
|
||||
ARRAY_PUSH_BACK(vertices, ((VKTxVertex) {dx2, dy2, u, v}));
|
||||
|
||||
VKBuffer* renderVertexBuffer = ARRAY_TO_VERTEX_BUF(device, vertices);
|
||||
ARRAY_FREE(vertices);
|
||||
@@ -137,128 +137,6 @@ static void VKBlitSwToTextureViaPooledTexture(VKRenderingContext* context, VKIma
|
||||
// VKBuffer_Destroy(device, renderVertexBuffer);
|
||||
}
|
||||
|
||||
static void VKBlitTextureToTexture(VKRenderingContext* context, VKImage* src, VKImage* dest,
|
||||
int sx1, int sy1, int sx2, int sy2,
|
||||
double dx1, double dy1, double dx2, double dy2)
|
||||
{
|
||||
VKSDOps* surface = context->surface;
|
||||
|
||||
VKRenderer_FlushRenderPass(surface);
|
||||
|
||||
VKDevice* device = surface->device;
|
||||
|
||||
VKTxVertex* vertices = ARRAY_ALLOC(VKTxVertex, 4);
|
||||
/*
|
||||
* (p1)---------(p2)
|
||||
* | |
|
||||
* | |
|
||||
* | |
|
||||
* (p4)---------(p3)
|
||||
*/
|
||||
|
||||
double u1 = (double)sx1 / src->extent.width;
|
||||
double v1 = (double)sy1 / src->extent.height;
|
||||
double u2 = (double)sx2 / src->extent.width;
|
||||
double v2 = (double)sy2 / src->extent.height;
|
||||
|
||||
ARRAY_PUSH_BACK(vertices) = (VKTxVertex) {dx1, dy1, u1, v1};
|
||||
ARRAY_PUSH_BACK(vertices) = (VKTxVertex) {dx2, dy1, u2, v1};
|
||||
ARRAY_PUSH_BACK(vertices) = (VKTxVertex) {dx1, dy2, u1, v2};
|
||||
ARRAY_PUSH_BACK(vertices) = (VKTxVertex) {dx2, dy2, u2, v2};
|
||||
|
||||
VKBuffer* renderVertexBuffer = ARRAY_TO_VERTEX_BUF(device, vertices);
|
||||
ARRAY_FREE(vertices);
|
||||
|
||||
VkCommandBuffer cb = VKRenderer_Record(device->renderer);
|
||||
{
|
||||
VkImageMemoryBarrier barrier;
|
||||
VKBarrierBatch barrierBatch = {};
|
||||
VKRenderer_AddImageBarrier(&barrier, &barrierBatch, src,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
if (barrierBatch.barrierCount > 0) {
|
||||
device->vkCmdPipelineBarrier(cb, barrierBatch.srcStages, barrierBatch.dstStages,
|
||||
0, 0, NULL,
|
||||
0, NULL,
|
||||
barrierBatch.barrierCount, &barrier);
|
||||
}
|
||||
}
|
||||
|
||||
VKRenderer_TextureRender(context, dest, src,
|
||||
renderVertexBuffer->handle, 4);
|
||||
|
||||
// TODO: Not optimal but required for releasing raster buffer. Such Buffers should also be managed by special pools
|
||||
// TODO: Also, consider using VKRenderer_FlushRenderPass here to process pending command
|
||||
VKRenderer_Flush(device->renderer);
|
||||
VKRenderer_Sync(device->renderer);
|
||||
// TODO: Add proper sync for renderVertexBuffer
|
||||
// VKBuffer_Destroy(device, renderVertexBuffer);
|
||||
}
|
||||
|
||||
static jboolean clipDestCoords(
|
||||
VKRenderingContext* context,
|
||||
jdouble *dx1, jdouble *dy1, jdouble *dx2, jdouble *dy2,
|
||||
jint *sx1, jint *sy1, jint *sx2, jint *sy2,
|
||||
jint destW, jint destH) {
|
||||
// Trim destination rect by clip-rect (or dest.bounds)
|
||||
const jint sw = *sx2 - *sx1;
|
||||
const jint sh = *sy2 - *sy1;
|
||||
const jdouble dw = *dx2 - *dx1;
|
||||
const jdouble dh = *dy2 - *dy1;
|
||||
VkRect2D* clipRect = &context->clipRect;
|
||||
jdouble dcx1 = 0;
|
||||
jdouble dcx2 = destW;
|
||||
jdouble dcy1 = 0;
|
||||
jdouble dcy2 = destH;
|
||||
if (clipRect->offset.x > dcx1)
|
||||
dcx1 = clipRect->offset.x;
|
||||
const int maxX = clipRect->offset.x + clipRect->extent.width;
|
||||
if (dcx2 > maxX)
|
||||
dcx2 = maxX;
|
||||
if (clipRect->offset.y > dcy1)
|
||||
dcy1 = clipRect->offset.y;
|
||||
const int maxY = clipRect->offset.y + clipRect->extent.height;
|
||||
if (dcy2 > maxY)
|
||||
dcy2 = maxY;
|
||||
|
||||
if (dcx1 >= dcx2) {
|
||||
J2dTraceLn2(J2D_TRACE_ERROR, "\tclipDestCoords: dcx1=%1.2f, dcx2=%1.2f", dcx1, dcx2);
|
||||
dcx1 = dcx2;
|
||||
}
|
||||
if (dcy1 >= dcy2) {
|
||||
J2dTraceLn2(J2D_TRACE_ERROR, "\tclipDestCoords: dcy1=%1.2f, dcy2=%1.2f", dcy1, dcy2);
|
||||
dcy1 = dcy2;
|
||||
}
|
||||
if (*dx2 <= dcx1 || *dx1 >= dcx2 || *dy2 <= dcy1 || *dy1 >= dcy2) {
|
||||
J2dTraceLn(J2D_TRACE_INFO, "\tclipDestCoords: dest rect doesn't intersect clip area");
|
||||
J2dTraceLn4(J2D_TRACE_INFO, "\tdx2=%1.4f <= dcx1=%1.4f || *dx1=%1.4f >= dcx2=%1.4f", *dx2, dcx1, *dx1, dcx2);
|
||||
J2dTraceLn4(J2D_TRACE_INFO, "\t*dy2=%1.4f <= dcy1=%1.4f || *dy1=%1.4f >= dcy2=%1.4f", *dy2, dcy1, *dy1, dcy2);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (*dx1 < dcx1) {
|
||||
J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdx1=%1.2f, will be clipped to %1.2f | sx1+=%d", *dx1, dcx1, (jint)((dcx1 - *dx1) * (sw/dw)));
|
||||
*sx1 += (jint)((dcx1 - *dx1) * (sw/dw));
|
||||
*dx1 = dcx1;
|
||||
}
|
||||
if (*dx2 > dcx2) {
|
||||
J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdx2=%1.2f, will be clipped to %1.2f | sx2-=%d", *dx2, dcx2, (jint)((*dx2 - dcx2) * (sw/dw)));
|
||||
*sx2 -= (jint)((*dx2 - dcx2) * (sw/dw));
|
||||
*dx2 = dcx2;
|
||||
}
|
||||
if (*dy1 < dcy1) {
|
||||
J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdy1=%1.2f, will be clipped to %1.2f | sy1+=%d", *dy1, dcy1, (jint)((dcy1 - *dy1) * (sh/dh)));
|
||||
*sy1 += (jint)((dcy1 - *dy1) * (sh/dh));
|
||||
*dy1 = dcy1;
|
||||
}
|
||||
if (*dy2 > dcy2) {
|
||||
J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdy2=%1.2f, will be clipped to %1.2f | sy2-=%d", *dy2, dcy2, (jint)((*dy2 - dcy2) * (sh/dh)));
|
||||
*sy2 -= (jint)((*dy2 - dcy2) * (sh/dh));
|
||||
*dy2 = dcy2;
|
||||
}
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
void VKBlitLoops_IsoBlit(JNIEnv *env,
|
||||
VKRenderingContext* context, jlong pSrcOps,
|
||||
@@ -269,79 +147,73 @@ void VKBlitLoops_IsoBlit(JNIEnv *env,
|
||||
jdouble dx1, jdouble dy1,
|
||||
jdouble dx2, jdouble dy2)
|
||||
{
|
||||
J2dRlsTraceLn8(J2D_TRACE_VERBOSE, "VKBlitLoops_IsoBlit: (%d %d %d %d) -> (%f %f %f %f) ",
|
||||
J2dRlsTraceLn8(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: BLIT_IsoBlit (%d %d %d %d) -> (%f %f %f %f) ",
|
||||
sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "VKBlitLoops_IsoBlit: texture=%d xform=%d",
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: BLIT_IsoBlit texture=%d xform=%d",
|
||||
texture, xform)
|
||||
|
||||
VKSDOps *srcOps = (VKSDOps *)jlong_to_ptr(pSrcOps);
|
||||
|
||||
if (context == NULL || srcOps == NULL) {
|
||||
J2dRlsTraceLn2(J2D_TRACE_ERROR, "VKBlitLoops_IsoBlit: context(%p) or srcOps(%p) is null",
|
||||
context, srcOps)
|
||||
return;
|
||||
}
|
||||
|
||||
if (srcOps->image == NULL) {
|
||||
J2dRlsTraceLn(J2D_TRACE_WARNING, "VKBlitLoops_IsoBlit: srcOps->image is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VKRenderer_Validate(context, SHADER_BLIT, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)) {
|
||||
J2dTraceLn(J2D_TRACE_INFO, "VKBlitLoops_IsoBlit: VKRenderer_Validate cannot validate renderer");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: check if srctype is supported
|
||||
|
||||
SurfaceDataRasInfo srcInfo;
|
||||
jint sw = sx2 - sx1;
|
||||
jint sh = sy2 - sy1;
|
||||
jdouble dw = dx2 - dx1;
|
||||
jdouble dh = dy2 - dy1;
|
||||
|
||||
if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) {
|
||||
J2dTraceLn(J2D_TRACE_WARNING,
|
||||
"VKBlitLoops_IsoBlit: invalid dimensions");
|
||||
return;
|
||||
}
|
||||
|
||||
srcInfo.bounds.x1 = sx1;
|
||||
srcInfo.bounds.y1 = sy1;
|
||||
srcInfo.bounds.x2 = sx2;
|
||||
srcInfo.bounds.y2 = sy2;
|
||||
|
||||
SurfaceData_IntersectBoundsXYXY(&srcInfo.bounds,
|
||||
0, 0,
|
||||
srcOps->image->extent.width,
|
||||
srcOps->image->extent.height);
|
||||
|
||||
if (srcInfo.bounds.x2 > srcInfo.bounds.x1 &&
|
||||
srcInfo.bounds.y2 > srcInfo.bounds.y1) {
|
||||
if (srcInfo.bounds.x1 != sx1) {
|
||||
dx1 += (srcInfo.bounds.x1 - sx1) * (dw / sw);
|
||||
sx1 = srcInfo.bounds.x1;
|
||||
}
|
||||
if (srcInfo.bounds.y1 != sy1) {
|
||||
dy1 += (srcInfo.bounds.y1 - sy1) * (dh / sh);
|
||||
sy1 = srcInfo.bounds.y1;
|
||||
}
|
||||
if (srcInfo.bounds.x2 != sx2) {
|
||||
dx2 += (srcInfo.bounds.x2 - sx2) * (dw / sw);
|
||||
sx2 = srcInfo.bounds.x2;
|
||||
}
|
||||
if (srcInfo.bounds.y2 != sy2) {
|
||||
dy2 += (srcInfo.bounds.y2 - sy2) * (dh / sh);
|
||||
sy2 = srcInfo.bounds.y2;
|
||||
}
|
||||
|
||||
if (sx2 > sx1 && sy2 > sy1) {
|
||||
VKBlitTextureToTexture(context, srcOps->image, context->surface->image,
|
||||
sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
|
||||
}
|
||||
}
|
||||
}
|
||||
static jboolean clipDestCoords(
|
||||
VKRenderingContext* context,
|
||||
jdouble *dx1, jdouble *dy1, jdouble *dx2, jdouble *dy2,
|
||||
jint *sx1, jint *sy1, jint *sx2, jint *sy2,
|
||||
jint destW, jint destH) {
|
||||
// Trim destination rect by clip-rect (or dest.bounds)
|
||||
const jint sw = *sx2 - *sx1;
|
||||
const jint sh = *sy2 - *sy1;
|
||||
const jdouble dw = *dx2 - *dx1;
|
||||
const jdouble dh = *dy2 - *dy1;
|
||||
VkRect2D* clipRect = &context->clipRect;
|
||||
jdouble dcx1 = 0;
|
||||
jdouble dcx2 = destW;
|
||||
jdouble dcy1 = 0;
|
||||
jdouble dcy2 = destH;
|
||||
if (clipRect->offset.x > dcx1)
|
||||
dcx1 = clipRect->offset.x;
|
||||
const int maxX = clipRect->offset.x + clipRect->extent.width;
|
||||
if (dcx2 > maxX)
|
||||
dcx2 = maxX;
|
||||
if (clipRect->offset.y > dcy1)
|
||||
dcy1 = clipRect->offset.y;
|
||||
const int maxY = clipRect->offset.y + clipRect->extent.height;
|
||||
if (dcy2 > maxY)
|
||||
dcy2 = maxY;
|
||||
|
||||
if (dcx1 >= dcx2) {
|
||||
J2dTraceLn2(J2D_TRACE_ERROR, "\tclipDestCoords: dcx1=%1.2f, dcx2=%1.2f", dcx1, dcx2);
|
||||
dcx1 = dcx2;
|
||||
}
|
||||
if (dcy1 >= dcy2) {
|
||||
J2dTraceLn2(J2D_TRACE_ERROR, "\tclipDestCoords: dcy1=%1.2f, dcy2=%1.2f", dcy1, dcy2);
|
||||
dcy1 = dcy2;
|
||||
}
|
||||
if (*dx2 <= dcx1 || *dx1 >= dcx2 || *dy2 <= dcy1 || *dy1 >= dcy2) {
|
||||
J2dTraceLn(J2D_TRACE_INFO, "\tclipDestCoords: dest rect doesn't intersect clip area");
|
||||
J2dTraceLn4(J2D_TRACE_INFO, "\tdx2=%1.4f <= dcx1=%1.4f || *dx1=%1.4f >= dcx2=%1.4f", *dx2, dcx1, *dx1, dcx2);
|
||||
J2dTraceLn4(J2D_TRACE_INFO, "\t*dy2=%1.4f <= dcy1=%1.4f || *dy1=%1.4f >= dcy2=%1.4f", *dy2, dcy1, *dy1, dcy2);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (*dx1 < dcx1) {
|
||||
J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdx1=%1.2f, will be clipped to %1.2f | sx1+=%d", *dx1, dcx1, (jint)((dcx1 - *dx1) * (sw/dw)));
|
||||
*sx1 += (jint)((dcx1 - *dx1) * (sw/dw));
|
||||
*dx1 = dcx1;
|
||||
}
|
||||
if (*dx2 > dcx2) {
|
||||
J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdx2=%1.2f, will be clipped to %1.2f | sx2-=%d", *dx2, dcx2, (jint)((*dx2 - dcx2) * (sw/dw)));
|
||||
*sx2 -= (jint)((*dx2 - dcx2) * (sw/dw));
|
||||
*dx2 = dcx2;
|
||||
}
|
||||
if (*dy1 < dcy1) {
|
||||
J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdy1=%1.2f, will be clipped to %1.2f | sy1+=%d", *dy1, dcy1, (jint)((dcy1 - *dy1) * (sh/dh)));
|
||||
*sy1 += (jint)((dcy1 - *dy1) * (sh/dh));
|
||||
*dy1 = dcy1;
|
||||
}
|
||||
if (*dy2 > dcy2) {
|
||||
J2dTraceLn3(J2D_TRACE_VERBOSE, "\t\tdy2=%1.2f, will be clipped to %1.2f | sy2-=%d", *dy2, dcy2, (jint)((*dy2 - dcy2) * (sh/dh)));
|
||||
*sy2 -= (jint)((*dy2 - dcy2) * (sh/dh));
|
||||
*dy2 = dcy2;
|
||||
}
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
|
||||
void VKBlitLoops_Blit(JNIEnv *env,
|
||||
@@ -368,7 +240,7 @@ void VKBlitLoops_Blit(JNIEnv *env,
|
||||
}
|
||||
|
||||
|
||||
if (!VKRenderer_Validate(context, SHADER_BLIT, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)) {
|
||||
if (!VKRenderer_Validate(context, PIPELINE_BLIT)) {
|
||||
J2dTraceLn(J2D_TRACE_INFO, "VKBlitLoops_Blit: VKRenderer_Validate cannot validate renderer");
|
||||
return;
|
||||
}
|
||||
@@ -461,8 +333,8 @@ VKBlitLoops_SurfaceToSwBlit(JNIEnv *env, VKRenderingContext* context,
|
||||
SurfaceDataOps *dstOps = (SurfaceDataOps *)jlong_to_ptr(pDstOps);
|
||||
SurfaceDataRasInfo srcInfo, dstInfo;
|
||||
|
||||
J2dTraceLn8(J2D_TRACE_INFO, "VKBlitLoops_SurfaceToSwBlit: (%p) (%d %d %d %d) -> (%p) (%d %d)",
|
||||
srcOps, srcx, srcy, width, height, dstOps, dstx, dsty);
|
||||
J2dTraceLn6(J2D_TRACE_INFO, "VKBlitLoops_SurfaceToSwBlit: (%d %d %d %d) -> (%d %d)",
|
||||
srcx, srcy, width, height, dstx, dsty);
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
J2dTraceLn(J2D_TRACE_WARNING,
|
||||
|
||||
@@ -106,77 +106,6 @@ VKMemory VKBuffer_CreateBuffers(VKDevice* device, VkBufferUsageFlags usageFlags,
|
||||
return page;
|
||||
}
|
||||
|
||||
static VkDescriptorPool VKBuffer_DestroyTexelBuffersOnFailure(VKDevice* device, VkDescriptorPool pool, uint32_t bufferCount, VKTexelBuffer* texelBuffers) {
|
||||
assert(device != NULL);
|
||||
for (uint32_t i = 0; i < bufferCount; i++) {
|
||||
device->vkDestroyBufferView(device->handle, texelBuffers[i].view, NULL);
|
||||
}
|
||||
device->vkDestroyDescriptorPool(device->handle, pool, NULL);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkDescriptorPool VKBuffer_CreateTexelBuffers(VKDevice* device, VkFormat format,
|
||||
VkDescriptorType descriptorType, VkDescriptorSetLayout descriptorSetLayout,
|
||||
uint32_t bufferCount, VKBuffer* buffers, VKTexelBuffer* texelBuffers) {
|
||||
assert(device != NULL);
|
||||
|
||||
// Create descriptor pool.
|
||||
VkDescriptorPoolSize poolSize = { .type = descriptorType, .descriptorCount = bufferCount };
|
||||
VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.flags = 0,
|
||||
.maxSets = bufferCount,
|
||||
.poolSizeCount = 1,
|
||||
.pPoolSizes = &poolSize
|
||||
};
|
||||
VkDescriptorPool pool;
|
||||
VK_IF_ERROR(device->vkCreateDescriptorPool(device->handle, &descriptorPoolCreateInfo, NULL, &pool)) return VK_NULL_HANDLE;
|
||||
|
||||
// Allocate descriptor sets.
|
||||
VkDescriptorSetLayout layouts[bufferCount];
|
||||
for (uint32_t i = 0; i < bufferCount; i++) layouts[i] = descriptorSetLayout;
|
||||
VkDescriptorSetAllocateInfo allocateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.descriptorPool = pool,
|
||||
.descriptorSetCount = bufferCount,
|
||||
.pSetLayouts = layouts
|
||||
};
|
||||
VkDescriptorSet descriptorSets[bufferCount];
|
||||
VK_IF_ERROR(device->vkAllocateDescriptorSets(device->handle, &allocateInfo, descriptorSets)) {
|
||||
return VKBuffer_DestroyTexelBuffersOnFailure(device, pool, 0, texelBuffers);
|
||||
}
|
||||
|
||||
// Create buffer views and record them into descriptors.
|
||||
VkBufferViewCreateInfo bufferViewCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
|
||||
.format = format,
|
||||
.offset = 0,
|
||||
.range = VK_WHOLE_SIZE
|
||||
};
|
||||
VkWriteDescriptorSet writeDescriptorSets[bufferCount];
|
||||
for (uint32_t i = 0; i < bufferCount; i++) {
|
||||
texelBuffers[i] = (VKTexelBuffer) {
|
||||
.buffer = buffers[i],
|
||||
.descriptorSet = descriptorSets[i]
|
||||
};
|
||||
bufferViewCreateInfo.buffer = buffers[i].handle;
|
||||
VK_IF_ERROR(device->vkCreateBufferView(device->handle, &bufferViewCreateInfo, NULL, &texelBuffers[i].view)) {
|
||||
return VKBuffer_DestroyTexelBuffersOnFailure(device, pool, i, texelBuffers);
|
||||
}
|
||||
writeDescriptorSets[i] = (VkWriteDescriptorSet) {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = descriptorSets[i],
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = descriptorType,
|
||||
.pTexelBufferView = &texelBuffers[i].view
|
||||
};
|
||||
}
|
||||
device->vkUpdateDescriptorSets(device->handle, bufferCount, writeDescriptorSets, 0, NULL);
|
||||
return pool;
|
||||
}
|
||||
|
||||
VKBuffer* VKBuffer_Create(VKDevice* device, VkDeviceSize size,
|
||||
VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
|
||||
{
|
||||
|
||||
@@ -42,12 +42,6 @@ struct VKBuffer {
|
||||
void* data;
|
||||
};
|
||||
|
||||
struct VKTexelBuffer {
|
||||
VKBuffer buffer;
|
||||
VkBufferView view;
|
||||
VkDescriptorSet descriptorSet;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create buffers, allocate a memory page and bind them together.
|
||||
* 'pageSize' can be 0, meaning that page size is calculated based on buffer memory requirements.
|
||||
@@ -60,16 +54,6 @@ VKMemory VKBuffer_CreateBuffers(VKDevice* device, VkBufferUsageFlags usageFlags,
|
||||
VkDeviceSize bufferSize, VkDeviceSize pageSize,
|
||||
uint32_t* bufferCount, VKBuffer* buffers);
|
||||
|
||||
/**
|
||||
* Create texel buffers from existing array of buffers.
|
||||
* It returns created descriptor pool, or VK_NULL_HANDLE on failure.
|
||||
* Created texel buffers are written in 'texelBuffers',
|
||||
* original buffers are taken from 'buffers'.
|
||||
*/
|
||||
VkDescriptorPool VKBuffer_CreateTexelBuffers(VKDevice* device, VkFormat format,
|
||||
VkDescriptorType descriptorType, VkDescriptorSetLayout descriptorSetLayout,
|
||||
uint32_t bufferCount, VKBuffer* buffers, VKTexelBuffer* texelBuffers);
|
||||
|
||||
// TODO usage of this function is suboptimal, we need to avoid creating individual buffers.
|
||||
VKBuffer* VKBuffer_Create(VKDevice* device, VkDeviceSize size,
|
||||
VkBufferUsageFlags usage, VkMemoryPropertyFlags properties);
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright 2025 JetBrains s.r.o.
|
||||
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
//
|
||||
// This code is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License version 2 only, as
|
||||
// published by the Free Software Foundation. Oracle designates this
|
||||
// particular file as subject to the "Classpath" exception as provided
|
||||
// by Oracle in the LICENSE file that accompanied this code.
|
||||
//
|
||||
// This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
// version 2 for more details (a copy is included in the LICENSE file that
|
||||
// accompanied this code).
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License version
|
||||
// 2 along with this work; if not, write to the Free Software Foundation,
|
||||
// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
// Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
// or visit www.oracle.com if you need additional information or have any
|
||||
// questions.
|
||||
|
||||
#include "VKComposites.h"
|
||||
|
||||
#define ALPHA_BLEND(NAME, SRC_COLOR, DST_COLOR, SRC_ALPHA, DST_ALPHA) \
|
||||
VKComposites_AddState(&map, ALPHA_COMPOSITE_ ## NAME, (VKCompositeState) \
|
||||
{{ .blendEnable = VK_TRUE, \
|
||||
.srcColorBlendFactor = VK_BLEND_FACTOR_ ## SRC_COLOR, \
|
||||
.dstColorBlendFactor = VK_BLEND_FACTOR_ ## DST_COLOR, \
|
||||
.colorBlendOp = VK_BLEND_OP_ADD, \
|
||||
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ ## SRC_ALPHA, \
|
||||
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ ## DST_ALPHA, \
|
||||
.alphaBlendOp = VK_BLEND_OP_ADD, \
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | \
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT }, \
|
||||
{ .logicOpEnable = VK_FALSE }})
|
||||
|
||||
static size_t hash(const void* ptr) {
|
||||
return (size_t) *(VKCompositeMode*)ptr;
|
||||
}
|
||||
|
||||
static bool equals(const void* ap, const void* bp) {
|
||||
return *(VKCompositeMode*)ap == *(VKCompositeMode*)bp;
|
||||
}
|
||||
|
||||
VKComposites VKComposites_Create() {
|
||||
VKComposites map = NULL;
|
||||
HASH_MAP_REHASH(map, linear_probing, &equals, &hash, ALPHA_COMPOSITE_GROUP + 2, 10, 0.75);
|
||||
|
||||
VKComposites_AddState(&map, LOGIC_COMPOSITE_XOR, (VKCompositeState) {
|
||||
{ .blendEnable = VK_FALSE,
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT },
|
||||
{ .logicOpEnable = VK_TRUE,
|
||||
.logicOp = VK_LOGIC_OP_XOR }});
|
||||
|
||||
// NAME | SRC_COLOR | DST_COLOR | SRC_ALPHA | DST_ALPHA ||
|
||||
ALPHA_BLEND( CLEAR , ZERO , ZERO , ZERO , ZERO );
|
||||
ALPHA_BLEND( SRC , ONE , ZERO , ONE , ZERO );
|
||||
ALPHA_BLEND( SRC_OVER , ONE , ONE_MINUS_SRC_ALPHA , ONE , ONE_MINUS_SRC_ALPHA );
|
||||
ALPHA_BLEND( DST_OVER , ONE_MINUS_DST_ALPHA , ONE , ONE_MINUS_DST_ALPHA , ONE );
|
||||
ALPHA_BLEND( SRC_IN , DST_ALPHA , ZERO , DST_ALPHA , ZERO );
|
||||
ALPHA_BLEND( DST_IN , ZERO , SRC_ALPHA , ZERO , SRC_ALPHA );
|
||||
ALPHA_BLEND( SRC_OUT , ONE_MINUS_DST_ALPHA , ZERO , ONE_MINUS_DST_ALPHA , ZERO );
|
||||
ALPHA_BLEND( DST_OUT , ZERO , ONE_MINUS_SRC_ALPHA , ZERO , ONE_MINUS_SRC_ALPHA );
|
||||
ALPHA_BLEND( DST , ZERO , ONE , ZERO , ONE );
|
||||
ALPHA_BLEND( SRC_ATOP , DST_ALPHA , ONE_MINUS_SRC_ALPHA , ZERO , ONE );
|
||||
ALPHA_BLEND( DST_ATOP , ONE_MINUS_DST_ALPHA , SRC_ALPHA , ONE , ZERO );
|
||||
ALPHA_BLEND( XOR , ONE_MINUS_DST_ALPHA , ONE_MINUS_SRC_ALPHA , ONE_MINUS_DST_ALPHA , ONE_MINUS_SRC_ALPHA );
|
||||
|
||||
VKComposites_AddState(&map, NO_COMPOSITE, (VKCompositeState) {
|
||||
{ .blendEnable = VK_FALSE,
|
||||
.colorWriteMask = 0 }, // For stencil-only operations.
|
||||
{ .logicOpEnable = VK_FALSE }});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
void VKComposites_Destroy(VKComposites composites) {
|
||||
MAP_FREE(composites);
|
||||
}
|
||||
|
||||
void VKComposites_AddState(VKComposites* composites, VKCompositeMode mode, VKCompositeState state) {
|
||||
state.blendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
state.blendState.pNext = NULL;
|
||||
state.blendState.attachmentCount = 1;
|
||||
MAP_AT(*composites, mode) = state;
|
||||
}
|
||||
|
||||
const VKCompositeState* VKComposites_GetState(VKComposites* composites, VKCompositeMode mode) {
|
||||
VKCompositeState* state = MAP_FIND(*composites, mode);
|
||||
state->blendState.pAttachments = &state->attachmentState;
|
||||
return state;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright 2025 JetBrains s.r.o.
|
||||
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
//
|
||||
// This code is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License version 2 only, as
|
||||
// published by the Free Software Foundation. Oracle designates this
|
||||
// particular file as subject to the "Classpath" exception as provided
|
||||
// by Oracle in the LICENSE file that accompanied this code.
|
||||
//
|
||||
// This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
// version 2 for more details (a copy is included in the LICENSE file that
|
||||
// accompanied this code).
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License version
|
||||
// 2 along with this work; if not, write to the Free Software Foundation,
|
||||
// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
// Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
// or visit www.oracle.com if you need additional information or have any
|
||||
// questions.
|
||||
|
||||
#ifndef VKComposites_h_Included
|
||||
#define VKComposites_h_Included
|
||||
|
||||
#include "java_awt_AlphaComposite.h"
|
||||
#include "VKUtil.h"
|
||||
|
||||
/**
|
||||
* There are two groups of composite modes:
|
||||
* - Logic composite - using logicOp.
|
||||
* - Alpha compisite - using blending.
|
||||
*/
|
||||
typedef enum {
|
||||
LOGIC_COMPOSITE_XOR = 0,
|
||||
LOGIC_COMPOSITE_GROUP = LOGIC_COMPOSITE_XOR,
|
||||
ALPHA_COMPOSITE_CLEAR = java_awt_AlphaComposite_CLEAR,
|
||||
ALPHA_COMPOSITE_SRC = java_awt_AlphaComposite_SRC,
|
||||
ALPHA_COMPOSITE_DST = java_awt_AlphaComposite_DST,
|
||||
ALPHA_COMPOSITE_SRC_OVER = java_awt_AlphaComposite_SRC_OVER,
|
||||
ALPHA_COMPOSITE_DST_OVER = java_awt_AlphaComposite_DST_OVER,
|
||||
ALPHA_COMPOSITE_SRC_IN = java_awt_AlphaComposite_SRC_IN,
|
||||
ALPHA_COMPOSITE_DST_IN = java_awt_AlphaComposite_DST_IN,
|
||||
ALPHA_COMPOSITE_SRC_OUT = java_awt_AlphaComposite_SRC_OUT,
|
||||
ALPHA_COMPOSITE_DST_OUT = java_awt_AlphaComposite_DST_OUT,
|
||||
ALPHA_COMPOSITE_SRC_ATOP = java_awt_AlphaComposite_SRC_ATOP,
|
||||
ALPHA_COMPOSITE_DST_ATOP = java_awt_AlphaComposite_DST_ATOP,
|
||||
ALPHA_COMPOSITE_XOR = java_awt_AlphaComposite_XOR,
|
||||
ALPHA_COMPOSITE_GROUP = ALPHA_COMPOSITE_XOR,
|
||||
NO_COMPOSITE = 0x7FFFFFFF
|
||||
} VKCompositeMode;
|
||||
#define COMPOSITE_GROUP(COMPOSITE) ( \
|
||||
(COMPOSITE) <= LOGIC_COMPOSITE_GROUP ? LOGIC_COMPOSITE_GROUP : \
|
||||
(COMPOSITE) <= ALPHA_COMPOSITE_GROUP ? ALPHA_COMPOSITE_GROUP : \
|
||||
NO_COMPOSITE )
|
||||
|
||||
typedef struct {
|
||||
VkPipelineColorBlendAttachmentState attachmentState;
|
||||
VkPipelineColorBlendStateCreateInfo blendState;
|
||||
} VKCompositeState;
|
||||
|
||||
typedef MAP(VKCompositeMode, VKCompositeState) VKComposites;
|
||||
|
||||
VKComposites VKComposites_Create();
|
||||
void VKComposites_Destroy(VKComposites composites);
|
||||
void VKComposites_AddState(VKComposites* composites, VKCompositeMode mode, VKCompositeState state);
|
||||
const VKCompositeState* VKComposites_GetState(VKComposites* composites, VKCompositeMode mode);
|
||||
|
||||
#endif //VKComposites_h_Included
|
||||
@@ -40,7 +40,7 @@ static VkBool32 VKImage_CreateView(VKDevice* device, VKImage* image) {
|
||||
.image = image->handle,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = image->format,
|
||||
.subresourceRange.aspectMask = VKImage_GetAspect(image),
|
||||
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.subresourceRange.baseMipLevel = 0,
|
||||
.subresourceRange.levelCount = 1,
|
||||
.subresourceRange.baseArrayLayer = 0,
|
||||
@@ -53,11 +53,6 @@ static VkBool32 VKImage_CreateView(VKDevice* device, VKImage* image) {
|
||||
return VK_TRUE;
|
||||
}
|
||||
|
||||
VkImageAspectFlagBits VKImage_GetAspect(VKImage* image) {
|
||||
return VKUtil_GetFormatGroup(image->format).bytes == 0 ? // Unknown format group means stencil.
|
||||
VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
}
|
||||
|
||||
VKImage* VKImage_Create(VKDevice* device, uint32_t width, uint32_t height,
|
||||
VkImageCreateFlags flags, VkFormat format,
|
||||
VkImageTiling tiling, VkImageUsageFlags usage, VkSampleCountFlagBits samples,
|
||||
|
||||
@@ -42,8 +42,6 @@ struct VKImage {
|
||||
VkAccessFlagBits lastAccess;
|
||||
};
|
||||
|
||||
VkImageAspectFlagBits VKImage_GetAspect(VKImage* image);
|
||||
|
||||
VKImage* VKImage_Create(VKDevice* device, uint32_t width, uint32_t height,
|
||||
VkImageCreateFlags flags, VkFormat format,
|
||||
VkImageTiling tiling, VkImageUsageFlags usage, VkSampleCountFlagBits samples,
|
||||
|
||||
@@ -33,29 +33,9 @@
|
||||
#undef SHADER_ENTRY
|
||||
#undef BYTECODE_END
|
||||
|
||||
inline void hash(uint32_t* result, int i) { // Good for hashing enums.
|
||||
uint32_t x = (uint32_t) i;
|
||||
x = ((x >> 16U) ^ x) * 0x45d9f3bU;
|
||||
x = ((x >> 16U) ^ x) * 0x45d9f3bU;
|
||||
x = (x >> 16U) ^ x;
|
||||
*result ^= x + 0x9e3779b9U + (*result << 6U) + (*result >> 2U);
|
||||
}
|
||||
static size_t pipelineDescriptorHash(const void* ptr) {
|
||||
const VKPipelineDescriptor* d = ptr;
|
||||
uint32_t h = 0U;
|
||||
hash(&h, d->stencilMode);
|
||||
hash(&h, d->composite);
|
||||
hash(&h, d->shader);
|
||||
hash(&h, d->topology);
|
||||
return (size_t) h;
|
||||
}
|
||||
static bool pipelineDescriptorEquals(const void* ap, const void* bp) {
|
||||
const VKPipelineDescriptor *a = ap, *b = bp;
|
||||
return a->stencilMode == b->stencilMode &&
|
||||
a->composite == b->composite &&
|
||||
a->shader == b->shader &&
|
||||
a->topology == b->topology;
|
||||
}
|
||||
typedef struct VKPipelineSet {
|
||||
VkPipeline pipelines[PIPELINE_COUNT];
|
||||
} VKPipelineSet;
|
||||
|
||||
typedef struct VKShaders {
|
||||
# define SHADER_ENTRY(NAME, TYPE) VkPipelineShaderStageCreateInfo NAME ## _ ## TYPE;
|
||||
@@ -99,183 +79,218 @@ static VKShaders* VKPipelines_CreateShaders(VKDevice* device) {
|
||||
return shaders;
|
||||
}
|
||||
|
||||
#define MAKE_INPUT_STATE(NAME, TYPE, ...) \
|
||||
const VkFormat INPUT_STATE_ATTRIBUTE_FORMATS_##NAME[] = { __VA_ARGS__ }; \
|
||||
VkVertexInputAttributeDescription \
|
||||
INPUT_STATE_ATTRIBUTES_##NAME[SARRAY_COUNT_OF(INPUT_STATE_ATTRIBUTE_FORMATS_##NAME)]; \
|
||||
const VkVertexInputBindingDescription INPUT_STATE_BINDING_##NAME = { \
|
||||
.binding = 0, \
|
||||
.stride = sizeof(TYPE), \
|
||||
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX \
|
||||
}; \
|
||||
const VkPipelineVertexInputStateCreateInfo INPUT_STATE_##NAME = { \
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, \
|
||||
.vertexBindingDescriptionCount = 1, \
|
||||
.pVertexBindingDescriptions = &INPUT_STATE_BINDING_##NAME, \
|
||||
.vertexAttributeDescriptionCount = SARRAY_COUNT_OF(INPUT_STATE_ATTRIBUTES_##NAME), \
|
||||
.pVertexAttributeDescriptions = INPUT_STATE_ATTRIBUTES_##NAME \
|
||||
}; \
|
||||
uint32_t INPUT_STATE_BINDING_SIZE_##NAME = 0; \
|
||||
for (uint32_t i = 0; i < SARRAY_COUNT_OF(INPUT_STATE_ATTRIBUTES_##NAME); i++) { \
|
||||
INPUT_STATE_ATTRIBUTES_##NAME[i] = (VkVertexInputAttributeDescription) { \
|
||||
i, 0, INPUT_STATE_ATTRIBUTE_FORMATS_##NAME[i], INPUT_STATE_BINDING_SIZE_##NAME}; \
|
||||
INPUT_STATE_BINDING_SIZE_##NAME += \
|
||||
VKUtil_GetFormatGroup(INPUT_STATE_ATTRIBUTE_FORMATS_##NAME[i]).bytes; \
|
||||
} if (sizeof(TYPE) != INPUT_STATE_BINDING_SIZE_##NAME) VK_FATAL_ERROR("Vertex size mismatch for input state " #NAME)
|
||||
#define MAKE_INPUT_STATE(NAME, TYPE, ...) \
|
||||
static const VkVertexInputAttributeDescription INPUT_STATE_ATTRIBUTES_##NAME[] = { __VA_ARGS__ }; \
|
||||
static const VkVertexInputBindingDescription INPUT_STATE_BINDING_##NAME = { \
|
||||
.binding = 0, \
|
||||
.stride = sizeof(TYPE), \
|
||||
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX \
|
||||
}; \
|
||||
static const VkPipelineVertexInputStateCreateInfo INPUT_STATE_##NAME = { \
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, \
|
||||
.vertexBindingDescriptionCount = 1, \
|
||||
.pVertexBindingDescriptions = &INPUT_STATE_BINDING_##NAME, \
|
||||
.vertexAttributeDescriptionCount = SARRAY_COUNT_OF(INPUT_STATE_ATTRIBUTES_##NAME), \
|
||||
.pVertexAttributeDescriptions = INPUT_STATE_ATTRIBUTES_##NAME \
|
||||
}
|
||||
|
||||
static VkPipeline VKPipelines_CreatePipelines(VKRenderPassContext* renderPassContext, uint32_t count,
|
||||
const VKPipelineDescriptor* descriptors) {
|
||||
assert(renderPassContext != NULL && renderPassContext->pipelineContext != NULL);
|
||||
assert(count > 0 && descriptors != NULL);
|
||||
VKPipelineContext* pipelineContext = renderPassContext->pipelineContext;
|
||||
VKDevice* device = pipelineContext->device;
|
||||
VKShaders* shaders = pipelineContext->shaders;
|
||||
VKComposites* composites = &VKGE_graphics_environment()->composites;
|
||||
typedef struct {
|
||||
VkGraphicsPipelineCreateInfo createInfo;
|
||||
VkPipelineMultisampleStateCreateInfo multisampleState;
|
||||
VkPipelineColorBlendStateCreateInfo colorBlendState;
|
||||
VkPipelineDynamicStateCreateInfo dynamicState;
|
||||
VkDynamicState dynamicStates[2];
|
||||
} PipelineCreateState;
|
||||
|
||||
// Setup pipeline creation structs.
|
||||
static const uint32_t MAX_DYNAMIC_STATES = 2;
|
||||
typedef struct {
|
||||
VkPipelineShaderStageCreateInfo createInfos[2]; // vert + frag
|
||||
} ShaderStages;
|
||||
ShaderStages stages[count];
|
||||
VkPipelineInputAssemblyStateCreateInfo inputAssemblyStates[count];
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencilStates[count];
|
||||
VkPipelineDynamicStateCreateInfo dynamicStates[count];
|
||||
VkDynamicState dynamicStateValues[count][MAX_DYNAMIC_STATES];
|
||||
VkGraphicsPipelineCreateInfo createInfos[count];
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
// Init default pipeline state. Some members are left uninitialized:
|
||||
// - pStages (but stageCount is set to 2)
|
||||
// - pVertexInputState
|
||||
// - createInfo.layout
|
||||
inputAssemblyStates[i] = (VkPipelineInputAssemblyStateCreateInfo) {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.topology = descriptors[i].topology
|
||||
};
|
||||
static const VkViewport viewport = {};
|
||||
static const VkRect2D scissor = {};
|
||||
static const VkPipelineViewportStateCreateInfo viewportState = {
|
||||
typedef struct {
|
||||
VkPipelineShaderStageCreateInfo createInfos[2]; // vert + frag
|
||||
} ShaderStages;
|
||||
|
||||
/**
|
||||
* Init default pipeline state. Some members are left uninitialized:
|
||||
* - pStages (but stageCount is set to 2)
|
||||
* - pVertexInputState
|
||||
* - pInputAssemblyState
|
||||
* - colorBlendState.pAttachments (but attachmentCount is set to 1)
|
||||
* - createInfo.layout
|
||||
* - createInfo.renderPass
|
||||
* - renderingCreateInfo.pColorAttachmentFormats (but colorAttachmentCount is set to 1)
|
||||
*/
|
||||
static void VKPipelines_InitPipelineCreateState(PipelineCreateState* state) {
|
||||
static const VkViewport viewport = {};
|
||||
static const VkRect2D scissor = {};
|
||||
static const VkPipelineViewportStateCreateInfo viewportState = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.viewportCount = 1,
|
||||
.pViewports = &viewport,
|
||||
.scissorCount = 1,
|
||||
.pScissors = &scissor
|
||||
};
|
||||
static const VkPipelineRasterizationStateCreateInfo rasterizationState = {
|
||||
};
|
||||
static const VkPipelineRasterizationStateCreateInfo rasterizationState = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.polygonMode = VK_POLYGON_MODE_FILL,
|
||||
.cullMode = VK_CULL_MODE_NONE,
|
||||
.lineWidth = 1.0f
|
||||
};
|
||||
static const VkPipelineMultisampleStateCreateInfo multisampleState = {
|
||||
};
|
||||
state->multisampleState = (VkPipelineMultisampleStateCreateInfo) {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
|
||||
};
|
||||
static const VkStencilOpState stencilOpState = {
|
||||
.failOp = VK_STENCIL_OP_KEEP,
|
||||
.passOp = VK_STENCIL_OP_KEEP,
|
||||
.compareOp = VK_COMPARE_OP_NOT_EQUAL,
|
||||
.compareMask = 0xFFFFFFFFU,
|
||||
.writeMask = 0U,
|
||||
.reference = CLIP_STENCIL_EXCLUDE_VALUE
|
||||
};
|
||||
depthStencilStates[i] = (VkPipelineDepthStencilStateCreateInfo) {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
.stencilTestEnable = descriptors[i].stencilMode == STENCIL_MODE_ON,
|
||||
.front = stencilOpState,
|
||||
.back = stencilOpState
|
||||
};
|
||||
dynamicStates[i] = (VkPipelineDynamicStateCreateInfo) {
|
||||
};
|
||||
state->colorBlendState = (VkPipelineColorBlendStateCreateInfo) {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||
.logicOpEnable = VK_FALSE,
|
||||
.logicOp = VK_LOGIC_OP_XOR,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = NULL,
|
||||
};
|
||||
state->dynamicState = (VkPipelineDynamicStateCreateInfo) {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.dynamicStateCount = 2,
|
||||
.pDynamicStates = dynamicStateValues[i]
|
||||
};
|
||||
dynamicStateValues[i][0] = VK_DYNAMIC_STATE_VIEWPORT;
|
||||
dynamicStateValues[i][1] = VK_DYNAMIC_STATE_SCISSOR;
|
||||
createInfos[i] = (VkGraphicsPipelineCreateInfo) {
|
||||
.pDynamicStates = state->dynamicStates
|
||||
};
|
||||
state->dynamicStates[0] = VK_DYNAMIC_STATE_VIEWPORT;
|
||||
state->dynamicStates[1] = VK_DYNAMIC_STATE_SCISSOR;
|
||||
state->createInfo = (VkGraphicsPipelineCreateInfo) {
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.stageCount = 2,
|
||||
.pStages = stages[i].createInfos,
|
||||
.pInputAssemblyState = &inputAssemblyStates[i],
|
||||
.pViewportState = &viewportState,
|
||||
.pRasterizationState = &rasterizationState,
|
||||
.pMultisampleState = &multisampleState,
|
||||
.pDepthStencilState = &depthStencilStates[i],
|
||||
.pColorBlendState = &VKComposites_GetState(composites, descriptors[i].composite)->blendState,
|
||||
.pDynamicState = &dynamicStates[i],
|
||||
.renderPass = renderPassContext->renderPass[descriptors[i].stencilMode != STENCIL_MODE_NONE],
|
||||
.pMultisampleState = &state->multisampleState,
|
||||
.pColorBlendState = &state->colorBlendState,
|
||||
.pDynamicState = &state->dynamicState,
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = -1
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
static const VkPipelineInputAssemblyStateCreateInfo INPUT_ASSEMBLY_STATE_TRIANGLE_STRIP = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
|
||||
};
|
||||
static const VkPipelineInputAssemblyStateCreateInfo INPUT_ASSEMBLY_STATE_TRIANGLE_LIST = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
|
||||
};
|
||||
static const VkPipelineInputAssemblyStateCreateInfo INPUT_ASSEMBLY_STATE_LINE_LIST = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST
|
||||
};
|
||||
|
||||
// Blend states are hard-coded, but can also be loaded dynamically to implement custom composites.
|
||||
#define DEF_BLEND(NAME, SRC_COLOR, DST_COLOR, SRC_ALPHA, DST_ALPHA) \
|
||||
{ .blendEnable = VK_TRUE, \
|
||||
.srcColorBlendFactor = VK_BLEND_FACTOR_ ## SRC_COLOR, \
|
||||
.dstColorBlendFactor = VK_BLEND_FACTOR_ ## DST_COLOR, \
|
||||
.colorBlendOp = VK_BLEND_OP_ADD, \
|
||||
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ ## SRC_ALPHA, \
|
||||
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ ## DST_ALPHA, \
|
||||
.alphaBlendOp = VK_BLEND_OP_ADD, \
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | \
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT }
|
||||
|
||||
|
||||
const VkPipelineColorBlendAttachmentState COMPOSITE_BLEND_STATES[COMPOSITE_COUNT] = {
|
||||
{ .blendEnable = VK_FALSE, // LOGIC_XOR
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT },
|
||||
// NAME || SRC_COLOR | DST_COLOR | SRC_ALPHA | DST_ALPHA ||
|
||||
DEF_BLEND(| CLEAR |, ZERO , ZERO , ZERO , ZERO ),
|
||||
DEF_BLEND(| SRC |, ONE , ZERO , ONE , ZERO ),
|
||||
DEF_BLEND(| SRC_OVER |, ONE , ONE_MINUS_SRC_ALPHA , ONE , ONE_MINUS_SRC_ALPHA ),
|
||||
DEF_BLEND(| DST_OVER |, ONE_MINUS_DST_ALPHA , ONE , ONE_MINUS_DST_ALPHA , ONE ),
|
||||
DEF_BLEND(| SRC_IN |, DST_ALPHA , ZERO , DST_ALPHA , ZERO ),
|
||||
DEF_BLEND(| DST_IN |, ZERO , SRC_ALPHA , ZERO , SRC_ALPHA ),
|
||||
DEF_BLEND(| SRC_OUT |, ONE_MINUS_DST_ALPHA , ZERO , ONE_MINUS_DST_ALPHA , ZERO ),
|
||||
DEF_BLEND(| DST_OUT |, ZERO , ONE_MINUS_SRC_ALPHA , ZERO , ONE_MINUS_SRC_ALPHA ),
|
||||
DEF_BLEND(| DST |, ZERO , ONE , ZERO , ONE ),
|
||||
DEF_BLEND(| SRC_ATOP |, DST_ALPHA , ONE_MINUS_SRC_ALPHA , ZERO , ONE ),
|
||||
DEF_BLEND(| DST_ATOP |, ONE_MINUS_DST_ALPHA , SRC_ALPHA , ONE , ZERO ),
|
||||
DEF_BLEND(| XOR |, ONE_MINUS_DST_ALPHA , ONE_MINUS_SRC_ALPHA , ONE_MINUS_DST_ALPHA , ONE_MINUS_SRC_ALPHA ),
|
||||
};
|
||||
|
||||
static const VkVertexInputAttributeDescription INPUT_ATTRIBUTE_POSITION = {
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32_SFLOAT,
|
||||
.offset = 0
|
||||
};
|
||||
static const VkVertexInputAttributeDescription INPUT_ATTRIBUTE_COLOR = {
|
||||
.location = 1,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
.offset = sizeof(float) * 2
|
||||
};
|
||||
static const VkVertexInputAttributeDescription INPUT_ATTRIBUTE_TEXCOORD = {
|
||||
.location = 1,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32_SFLOAT,
|
||||
.offset = sizeof(float) * 2
|
||||
};
|
||||
|
||||
MAKE_INPUT_STATE(COLOR_VERTEX, VKColorVertex, INPUT_ATTRIBUTE_POSITION, INPUT_ATTRIBUTE_COLOR);
|
||||
MAKE_INPUT_STATE(TEXCOORD_VERTEX, VKTxVertex, INPUT_ATTRIBUTE_POSITION, INPUT_ATTRIBUTE_TEXCOORD);
|
||||
|
||||
static void VKPipelines_DestroyPipelineSet(VKDevice* device, VKPipelineSet* set) {
|
||||
assert(device != NULL);
|
||||
if (set == NULL) return;
|
||||
for (uint32_t i = 0; i < PIPELINE_COUNT; i++) {
|
||||
device->vkDestroyPipeline(device->handle, set->pipelines[i], NULL);
|
||||
}
|
||||
free(set);
|
||||
}
|
||||
|
||||
static VKPipelineSet* VKPipelines_CreatePipelineSet(VKRenderPassContext* renderPassContext, VKCompositeMode composite) {
|
||||
assert(renderPassContext != NULL && renderPassContext->pipelineContext != NULL);
|
||||
assert(composite < COMPOSITE_COUNT);
|
||||
VKPipelineContext* pipelineContext = renderPassContext->pipelineContext;
|
||||
|
||||
VKPipelineSet* set = calloc(1, sizeof(VKPipelineSet));
|
||||
VK_RUNTIME_ASSERT(set);
|
||||
VKDevice* device = pipelineContext->device;
|
||||
VKShaders* shaders = pipelineContext->shaders;
|
||||
|
||||
// Setup default pipeline parameters.
|
||||
PipelineCreateState base;
|
||||
VKPipelines_InitPipelineCreateState(&base);
|
||||
base.createInfo.layout = pipelineContext->pipelineLayout;
|
||||
base.createInfo.renderPass = renderPassContext->renderPass;
|
||||
base.colorBlendState.pAttachments = &COMPOSITE_BLEND_STATES[composite];
|
||||
if (COMPOSITE_GROUP(composite) == LOGIC_COMPOSITE_GROUP) base.colorBlendState.logicOpEnable = VK_TRUE;
|
||||
assert(base.dynamicState.dynamicStateCount <= SARRAY_COUNT_OF(base.dynamicStates));
|
||||
|
||||
ShaderStages stages[PIPELINE_COUNT];
|
||||
VkGraphicsPipelineCreateInfo createInfos[PIPELINE_COUNT];
|
||||
for (uint32_t i = 0; i < PIPELINE_COUNT; i++) {
|
||||
createInfos[i] = base.createInfo;
|
||||
createInfos[i].pStages = stages[i].createInfos;
|
||||
}
|
||||
|
||||
// Setup input states.
|
||||
MAKE_INPUT_STATE(COLOR, VKColorVertex, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT);
|
||||
MAKE_INPUT_STATE(MASK_FILL_COLOR, VKMaskFillColorVertex, VK_FORMAT_R32G32B32A32_SINT, VK_FORMAT_R32G32B32A32_SFLOAT);
|
||||
MAKE_INPUT_STATE(BLIT, VKTxVertex, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32_SFLOAT);
|
||||
MAKE_INPUT_STATE(CLIP, VKIntVertex, VK_FORMAT_R32G32_SINT);
|
||||
{ // Setup plain color pipelines.
|
||||
createInfos[PIPELINE_DRAW_COLOR].pVertexInputState = createInfos[PIPELINE_FILL_COLOR].pVertexInputState = &INPUT_STATE_COLOR_VERTEX;
|
||||
createInfos[PIPELINE_FILL_COLOR].pInputAssemblyState = &INPUT_ASSEMBLY_STATE_TRIANGLE_LIST;
|
||||
createInfos[PIPELINE_DRAW_COLOR].pInputAssemblyState = &INPUT_ASSEMBLY_STATE_LINE_LIST;
|
||||
stages[PIPELINE_DRAW_COLOR] = stages[PIPELINE_FILL_COLOR] = (ShaderStages) {{ shaders->color_vert, shaders->color_frag }};
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
// Setup shader-specific pipeline parameters.
|
||||
switch (descriptors[i].shader) {
|
||||
case SHADER_COLOR:
|
||||
createInfos[i].pVertexInputState = &INPUT_STATE_COLOR;
|
||||
createInfos[i].layout = pipelineContext->colorPipelineLayout;
|
||||
stages[i] = (ShaderStages) {{ shaders->color_vert, shaders->color_frag }};
|
||||
break;
|
||||
case SHADER_MASK_FILL_COLOR:
|
||||
createInfos[i].pVertexInputState = &INPUT_STATE_MASK_FILL_COLOR;
|
||||
createInfos[i].layout = pipelineContext->maskFillPipelineLayout;
|
||||
stages[i] = (ShaderStages) {{ shaders->mask_fill_color_vert, shaders->mask_fill_color_frag }};
|
||||
break;
|
||||
case SHADER_BLIT:
|
||||
createInfos[i].pVertexInputState = &INPUT_STATE_BLIT;
|
||||
createInfos[i].layout = pipelineContext->texturePipelineLayout;
|
||||
stages[i] = (ShaderStages) {{ shaders->blit_vert, shaders->blit_frag }};
|
||||
break;
|
||||
case SHADER_CLIP:
|
||||
createInfos[i].pVertexInputState = &INPUT_STATE_CLIP;
|
||||
static const VkStencilOpState CLIP_STENCIL_OP = {
|
||||
.failOp = VK_STENCIL_OP_REPLACE,
|
||||
.passOp = VK_STENCIL_OP_REPLACE,
|
||||
.compareOp = VK_COMPARE_OP_NEVER,
|
||||
.compareMask = 0U,
|
||||
.writeMask = 0xFFFFFFFFU,
|
||||
.reference = CLIP_STENCIL_INCLUDE_VALUE
|
||||
};
|
||||
static const VkPipelineDepthStencilStateCreateInfo CLIP_STENCIL_STATE = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
.stencilTestEnable = VK_TRUE,
|
||||
.front = CLIP_STENCIL_OP,
|
||||
.back = CLIP_STENCIL_OP
|
||||
};
|
||||
createInfos[i].pDepthStencilState = &CLIP_STENCIL_STATE;
|
||||
createInfos[i].layout = pipelineContext->texturePipelineLayout;
|
||||
createInfos[i].stageCount = 1;
|
||||
stages[i] = (ShaderStages) {{ shaders->clip_vert }};
|
||||
break;
|
||||
default:
|
||||
VK_FATAL_ERROR("Cannot create pipeline, unknown shader requested!");
|
||||
}
|
||||
assert(createInfos[i].pDynamicState->dynamicStateCount <= MAX_DYNAMIC_STATES);
|
||||
J2dRlsTraceLn4(J2D_TRACE_INFO, "VKPipelines_CreatePipelines: stencilMode=%d, composite=%d, shader=%d, topology=%d",
|
||||
descriptors[i].stencilMode, descriptors[i].composite, descriptors[i].shader, descriptors[i].topology);
|
||||
{ // Setup texture pipeline.
|
||||
createInfos[PIPELINE_BLIT].pVertexInputState = &INPUT_STATE_TEXCOORD_VERTEX;
|
||||
createInfos[PIPELINE_BLIT].pInputAssemblyState = &INPUT_ASSEMBLY_STATE_TRIANGLE_STRIP;
|
||||
createInfos[PIPELINE_BLIT].layout = pipelineContext->texturePipelineLayout;
|
||||
stages[PIPELINE_BLIT] = (ShaderStages) {{ shaders->blit_vert, shaders->blit_frag }};
|
||||
}
|
||||
|
||||
// Create pipelines.
|
||||
// TODO pipeline cache
|
||||
VkPipeline pipelines[count];
|
||||
VK_IF_ERROR(device->vkCreateGraphicsPipelines(device->handle, VK_NULL_HANDLE, count,
|
||||
createInfos, NULL, pipelines)) VK_UNHANDLED_ERROR();
|
||||
J2dRlsTraceLn1(J2D_TRACE_INFO, "VKPipelines_CreatePipelines: created %d pipelines", count);
|
||||
for (uint32_t i = 0; i < count; ++i) MAP_AT(renderPassContext->pipelines, descriptors[i]) = pipelines[i];
|
||||
return pipelines[0];
|
||||
VK_IF_ERROR(device->vkCreateGraphicsPipelines(device->handle, VK_NULL_HANDLE, PIPELINE_COUNT,
|
||||
createInfos, NULL, set->pipelines)) VK_UNHANDLED_ERROR();
|
||||
J2dRlsTraceLn1(J2D_TRACE_INFO, "VKPipelines_CreatePipelineSet: composite=%d", composite);
|
||||
return set;
|
||||
}
|
||||
|
||||
static VkResult VKPipelines_InitRenderPasses(VKDevice* device, VKRenderPassContext* renderPassContext) {
|
||||
static VkResult VKPipelines_InitRenderPass(VKDevice* device, VKRenderPassContext* renderPassContext) {
|
||||
assert(device != NULL && renderPassContext != NULL);
|
||||
VkAttachmentDescription attachments[] = {{
|
||||
VkAttachmentDescription colorAttachment = {
|
||||
.format = renderPassContext->format,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
@@ -284,61 +299,47 @@ static VkResult VKPipelines_InitRenderPasses(VKDevice* device, VKRenderPassConte
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
|
||||
}, {
|
||||
.format = VK_FORMAT_S8_UINT,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
|
||||
}};
|
||||
};
|
||||
VkAttachmentReference colorReference = {
|
||||
.attachment = 0,
|
||||
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
|
||||
};
|
||||
VkAttachmentReference stencilReference = {
|
||||
.attachment = 1,
|
||||
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
|
||||
};
|
||||
VkSubpassDescription subpassDescription = {
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.colorAttachmentCount = 1,
|
||||
.pColorAttachments = &colorReference
|
||||
};
|
||||
// TODO this is probably not needed?
|
||||
// // Subpass dependencies for layout transitions
|
||||
// VkSubpassDependency dependency = {
|
||||
// .srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
// .dstSubpass = 0,
|
||||
// .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
// .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
// .srcAccessMask = 0,
|
||||
// .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
|
||||
// };
|
||||
VkRenderPassCreateInfo createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = attachments,
|
||||
.pAttachments = &colorAttachment,
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpassDescription,
|
||||
.dependencyCount = 0,
|
||||
.pDependencies = NULL
|
||||
};
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
if (i == 1) {
|
||||
createInfo.attachmentCount = 2;
|
||||
subpassDescription.pDepthStencilAttachment = &stencilReference;
|
||||
}
|
||||
VkResult result = device->vkCreateRenderPass(device->handle, &createInfo, NULL, &renderPassContext->renderPass[i]);
|
||||
VK_IF_ERROR(result) return result;
|
||||
}
|
||||
return VK_SUCCESS;
|
||||
return device->vkCreateRenderPass(device->handle, &createInfo, NULL, &renderPassContext->renderPass);
|
||||
}
|
||||
|
||||
static void VKPipelines_DestroyRenderPassContext(VKRenderPassContext* renderPassContext) {
|
||||
if (renderPassContext == NULL) return;
|
||||
VKDevice* device = renderPassContext->pipelineContext->device;
|
||||
assert(device != NULL);
|
||||
for (const VKPipelineDescriptor* k = NULL; (k = MAP_NEXT_KEY(renderPassContext->pipelines, k)) != NULL;) {
|
||||
VkPipeline pipeline = *MAP_FIND(renderPassContext->pipelines, *k);
|
||||
device->vkDestroyPipeline(device->handle, pipeline, NULL);
|
||||
}
|
||||
MAP_FREE(renderPassContext->pipelines);
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
device->vkDestroyRenderPass(device->handle, renderPassContext->renderPass[i], NULL);
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(renderPassContext->pipelineSets); i++) {
|
||||
VKPipelines_DestroyPipelineSet(device, renderPassContext->pipelineSets[i]);
|
||||
}
|
||||
ARRAY_FREE(renderPassContext->pipelineSets);
|
||||
device->vkDestroyRenderPass(device->handle, renderPassContext->renderPass, NULL);
|
||||
J2dRlsTraceLn2(J2D_TRACE_INFO, "VKPipelines_DestroyRenderPassContext(%p): format=%d",
|
||||
renderPassContext, renderPassContext->format);
|
||||
free(renderPassContext);
|
||||
@@ -348,18 +349,14 @@ static VKRenderPassContext* VKPipelines_CreateRenderPassContext(VKPipelineContex
|
||||
assert(pipelineContext != NULL && pipelineContext->device != NULL);
|
||||
VKRenderPassContext* renderPassContext = calloc(1, sizeof(VKRenderPassContext));
|
||||
VK_RUNTIME_ASSERT(renderPassContext);
|
||||
HASH_MAP_REHASH(renderPassContext->pipelines, linear_probing,
|
||||
&pipelineDescriptorEquals, &pipelineDescriptorHash, 0, 10, 0.75);
|
||||
renderPassContext->pipelineContext = pipelineContext;
|
||||
renderPassContext->format = format;
|
||||
|
||||
VK_IF_ERROR(VKPipelines_InitRenderPasses(pipelineContext->device, renderPassContext)) {
|
||||
VK_IF_ERROR(VKPipelines_InitRenderPass(pipelineContext->device, renderPassContext)) {
|
||||
VKPipelines_DestroyRenderPassContext(renderPassContext);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO create few common pipelines in advance? Like default shaders for SRC_OVER composite.
|
||||
|
||||
J2dRlsTraceLn2(J2D_TRACE_INFO, "VKPipelines_CreateRenderPassContext(%p): format=%d", renderPassContext, format);
|
||||
return renderPassContext;
|
||||
}
|
||||
@@ -380,7 +377,7 @@ static VkResult VKPipelines_InitPipelineLayouts(VKDevice* device, VKPipelineCont
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &pushConstantRange
|
||||
};
|
||||
result = device->vkCreatePipelineLayout(device->handle, &createInfo, NULL, &pipelines->colorPipelineLayout);
|
||||
result = device->vkCreatePipelineLayout(device->handle, &createInfo, NULL, &pipelines->pipelineLayout);
|
||||
VK_IF_ERROR(result) return result;
|
||||
|
||||
VkDescriptorSetLayoutBinding textureLayoutBinding = {
|
||||
@@ -403,26 +400,6 @@ static VkResult VKPipelines_InitPipelineLayouts(VKDevice* device, VKPipelineCont
|
||||
result = device->vkCreatePipelineLayout(device->handle, &createInfo, NULL, &pipelines->texturePipelineLayout);
|
||||
VK_IF_ERROR(result) return result;
|
||||
|
||||
VkDescriptorSetLayoutBinding maskBufferLayoutBinding = {
|
||||
.binding = 0,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.pImmutableSamplers = NULL
|
||||
};
|
||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.bindingCount = 1,
|
||||
.pBindings = &maskBufferLayoutBinding
|
||||
};
|
||||
result = device->vkCreateDescriptorSetLayout(device->handle, &descriptorSetLayoutCreateInfo, NULL, &pipelines->maskFillDescriptorSetLayout);
|
||||
VK_IF_ERROR(result) return result;
|
||||
|
||||
createInfo.setLayoutCount = 1;
|
||||
createInfo.pSetLayouts = &pipelines->maskFillDescriptorSetLayout;
|
||||
result = device->vkCreatePipelineLayout(device->handle, &createInfo, NULL, &pipelines->maskFillPipelineLayout);
|
||||
VK_IF_ERROR(result) return result;
|
||||
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -474,11 +451,9 @@ void VKPipelines_DestroyContext(VKPipelineContext* pipelineContext) {
|
||||
VKPipelines_DestroyShaders(device, pipelineContext->shaders);
|
||||
device->vkDestroySampler(device->handle, pipelineContext->linearRepeatSampler, NULL);
|
||||
|
||||
device->vkDestroyPipelineLayout(device->handle, pipelineContext->colorPipelineLayout, NULL);
|
||||
device->vkDestroyPipelineLayout(device->handle, pipelineContext->pipelineLayout, NULL);
|
||||
device->vkDestroyPipelineLayout(device->handle, pipelineContext->texturePipelineLayout, NULL);
|
||||
device->vkDestroyDescriptorSetLayout(device->handle, pipelineContext->textureDescriptorSetLayout, NULL);
|
||||
device->vkDestroyPipelineLayout(device->handle, pipelineContext->maskFillPipelineLayout, NULL);
|
||||
device->vkDestroyDescriptorSetLayout(device->handle, pipelineContext->maskFillDescriptorSetLayout, NULL);
|
||||
|
||||
J2dRlsTraceLn1(J2D_TRACE_INFO, "VKPipelines_DestroyContext(%p)", pipelineContext);
|
||||
free(pipelineContext);
|
||||
@@ -493,15 +468,20 @@ VKRenderPassContext* VKPipelines_GetRenderPassContext(VKPipelineContext* pipelin
|
||||
}
|
||||
// Not found, create.
|
||||
VKRenderPassContext* renderPassContext = VKPipelines_CreateRenderPassContext(pipelineContext, format);
|
||||
ARRAY_PUSH_BACK(pipelineContext->renderPassContexts) = renderPassContext;
|
||||
ARRAY_PUSH_BACK(pipelineContext->renderPassContexts, renderPassContext);
|
||||
return renderPassContext;
|
||||
}
|
||||
|
||||
VkPipeline VKPipelines_GetPipeline(VKRenderPassContext* renderPassContext, VKPipelineDescriptor descriptor) {
|
||||
VkPipeline VKPipelines_GetPipeline(VKRenderPassContext* renderPassContext, VKCompositeMode composite, VKPipeline pipeline) {
|
||||
assert(renderPassContext != NULL);
|
||||
VkPipeline pipeline = MAP_AT(renderPassContext->pipelines, descriptor);
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
pipeline = VKPipelines_CreatePipelines(renderPassContext, 1, &descriptor);
|
||||
assert(composite < COMPOSITE_COUNT); // We could append custom composites after that index.
|
||||
assert(pipeline < PIPELINE_COUNT); // We could append custom pipelines after that index.
|
||||
// Currently, our pipelines map to composite modes 1-to-1, but this may change in future when we'll add more states.
|
||||
uint32_t setIndex = (uint32_t) composite;
|
||||
|
||||
while (ARRAY_SIZE(renderPassContext->pipelineSets) <= setIndex) ARRAY_PUSH_BACK(renderPassContext->pipelineSets, NULL);
|
||||
if (renderPassContext->pipelineSets[setIndex] == NULL) {
|
||||
renderPassContext->pipelineSets[setIndex] = VKPipelines_CreatePipelineSet(renderPassContext, composite);
|
||||
}
|
||||
return pipeline;
|
||||
return renderPassContext->pipelineSets[setIndex]->pipelines[pipeline];
|
||||
}
|
||||
|
||||
@@ -24,72 +24,76 @@
|
||||
#ifndef VKPipelines_h_Included
|
||||
#define VKPipelines_h_Included
|
||||
|
||||
#include "VKComposites.h"
|
||||
#include "java_awt_AlphaComposite.h"
|
||||
#include "VKTypes.h"
|
||||
#include "VKUtil.h"
|
||||
|
||||
#define CLIP_STENCIL_INCLUDE_VALUE 0x80U
|
||||
#define CLIP_STENCIL_EXCLUDE_VALUE 0U
|
||||
|
||||
/**
|
||||
* Shader programs.
|
||||
* All pipeline types.
|
||||
*/
|
||||
typedef enum {
|
||||
SHADER_COLOR,
|
||||
SHADER_MASK_FILL_COLOR,
|
||||
SHADER_BLIT,
|
||||
SHADER_CLIP,
|
||||
NO_SHADER = 0x7FFFFFFF
|
||||
} VKShader;
|
||||
|
||||
typedef enum {
|
||||
STENCIL_MODE_NONE = 0, // No stencil attachment.
|
||||
STENCIL_MODE_OFF = 1, // Has stencil attachment, stencil test disabled.
|
||||
STENCIL_MODE_ON = 2 // Has stencil attachment, stencil test enabled.
|
||||
} VKStencilMode;
|
||||
PIPELINE_FILL_COLOR = 0,
|
||||
PIPELINE_DRAW_COLOR = 1,
|
||||
PIPELINE_BLIT = 2,
|
||||
PIPELINE_COUNT = 3,
|
||||
NO_PIPELINE = 0x7FFFFFFF
|
||||
} VKPipeline;
|
||||
|
||||
/**
|
||||
* All features describing a pipeline.
|
||||
* When adding new fields, update hash and comparison in VKPipelines.c.
|
||||
* There are two groups of composite modes:
|
||||
* - Logic composite - using logicOp.
|
||||
* - Alpha compisite - using blending.
|
||||
*/
|
||||
typedef struct {
|
||||
VKStencilMode stencilMode;
|
||||
VKCompositeMode composite;
|
||||
VKShader shader;
|
||||
VkPrimitiveTopology topology;
|
||||
} VKPipelineDescriptor;
|
||||
typedef enum {
|
||||
LOGIC_COMPOSITE_XOR = 0,
|
||||
LOGIC_COMPOSITE_GROUP = LOGIC_COMPOSITE_XOR,
|
||||
ALPHA_COMPOSITE_CLEAR = java_awt_AlphaComposite_CLEAR,
|
||||
ALPHA_COMPOSITE_SRC = java_awt_AlphaComposite_SRC,
|
||||
ALPHA_COMPOSITE_DST = java_awt_AlphaComposite_DST,
|
||||
ALPHA_COMPOSITE_SRC_OVER = java_awt_AlphaComposite_SRC_OVER,
|
||||
ALPHA_COMPOSITE_DST_OVER = java_awt_AlphaComposite_DST_OVER,
|
||||
ALPHA_COMPOSITE_SRC_IN = java_awt_AlphaComposite_SRC_IN,
|
||||
ALPHA_COMPOSITE_DST_IN = java_awt_AlphaComposite_DST_IN,
|
||||
ALPHA_COMPOSITE_SRC_OUT = java_awt_AlphaComposite_SRC_OUT,
|
||||
ALPHA_COMPOSITE_DST_OUT = java_awt_AlphaComposite_DST_OUT,
|
||||
ALPHA_COMPOSITE_SRC_ATOP = java_awt_AlphaComposite_SRC_ATOP,
|
||||
ALPHA_COMPOSITE_DST_ATOP = java_awt_AlphaComposite_DST_ATOP,
|
||||
ALPHA_COMPOSITE_XOR = java_awt_AlphaComposite_XOR,
|
||||
ALPHA_COMPOSITE_GROUP = ALPHA_COMPOSITE_XOR,
|
||||
COMPOSITE_COUNT = ALPHA_COMPOSITE_GROUP + 1,
|
||||
NO_COMPOSITE = 0x7FFFFFFF
|
||||
} VKCompositeMode;
|
||||
#define COMPOSITE_GROUP(COMPOSITE) ( \
|
||||
(COMPOSITE) <= LOGIC_COMPOSITE_GROUP ? LOGIC_COMPOSITE_GROUP : \
|
||||
(COMPOSITE) <= ALPHA_COMPOSITE_GROUP ? ALPHA_COMPOSITE_GROUP : \
|
||||
NO_COMPOSITE )
|
||||
|
||||
extern const VkPipelineColorBlendAttachmentState COMPOSITE_BLEND_STATES[COMPOSITE_COUNT];
|
||||
|
||||
/**
|
||||
* Global pipeline context.
|
||||
*/
|
||||
struct VKPipelineContext {
|
||||
VKDevice* device;
|
||||
VkPipelineLayout colorPipelineLayout;
|
||||
VkDescriptorSetLayout textureDescriptorSetLayout;
|
||||
VkPipelineLayout texturePipelineLayout;
|
||||
VkDescriptorSetLayout maskFillDescriptorSetLayout;
|
||||
VkPipelineLayout maskFillPipelineLayout;
|
||||
VKDevice* device;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VkPipelineLayout texturePipelineLayout;
|
||||
VkDescriptorSetLayout textureDescriptorSetLayout;
|
||||
|
||||
VkSampler linearRepeatSampler;
|
||||
VkSampler linearRepeatSampler;
|
||||
|
||||
struct VKShaders* shaders;
|
||||
ARRAY(VKRenderPassContext*) renderPassContexts;
|
||||
struct VKShaders* shaders;
|
||||
VKRenderPassContext** renderPassContexts;
|
||||
};
|
||||
|
||||
/**
|
||||
* Per-format context.
|
||||
*/
|
||||
struct VKRenderPassContext {
|
||||
VKPipelineContext* pipelineContext;
|
||||
VkFormat format;
|
||||
VkRenderPass renderPass[2]; // Color-only and color+stencil.
|
||||
MAP(VKPipelineDescriptor, VkPipeline) pipelines;
|
||||
VKPipelineContext* pipelineContext;
|
||||
VkFormat format;
|
||||
VkRenderPass renderPass;
|
||||
struct VKPipelineSet** pipelineSets; // TODO we will need a real hash map for this in the future.
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int x, y;
|
||||
} VKIntVertex;
|
||||
|
||||
typedef struct {
|
||||
float x, y;
|
||||
Color color;
|
||||
@@ -100,15 +104,10 @@ typedef struct {
|
||||
float u, v;
|
||||
} VKTxVertex;
|
||||
|
||||
typedef struct {
|
||||
int x, y, maskOffset, maskScanline;
|
||||
Color color;
|
||||
} VKMaskFillColorVertex;
|
||||
|
||||
VKPipelineContext* VKPipelines_CreateContext(VKDevice* device);
|
||||
void VKPipelines_DestroyContext(VKPipelineContext* pipelines);
|
||||
|
||||
VKRenderPassContext* VKPipelines_GetRenderPassContext(VKPipelineContext* pipelineContext, VkFormat format);
|
||||
VkPipeline VKPipelines_GetPipeline(VKRenderPassContext* renderPassContext, VKPipelineDescriptor descriptor);
|
||||
VkPipeline VKPipelines_GetPipeline(VKRenderPassContext* renderPassContext, VKCompositeMode composite, VKPipeline pipeline);
|
||||
|
||||
#endif //VKPipelines_h_Included
|
||||
|
||||
@@ -26,11 +26,9 @@
|
||||
|
||||
#ifndef HEADLESS
|
||||
|
||||
#include "sun_font_StrikeCache.h"
|
||||
#include "sun_java2d_pipe_BufferedOpCodes.h"
|
||||
#include "sun_java2d_pipe_BufferedRenderPipe.h"
|
||||
#include "sun_java2d_pipe_BufferedTextPipe.h"
|
||||
#include "fontscalerdefs.h"
|
||||
#include "Trace.h"
|
||||
#include "jlong.h"
|
||||
#include "VKBase.h"
|
||||
@@ -93,19 +91,15 @@
|
||||
#define OFFSET_XFORM sun_java2d_vulkan_VKBlitLoops_OFFSET_XFORM
|
||||
#define OFFSET_ISOBLIT sun_java2d_vulkan_VKBlitLoops_OFFSET_ISOBLIT
|
||||
|
||||
#define NO_CLIP ((VkRect2D) {{0, 0}, {0x7FFFFFFFU, 0x7FFFFFFFU}})
|
||||
|
||||
// Rendering context is only accessed from VKRenderQueue_flushBuffer,
|
||||
// which is only called from queue flusher thread, no need for synchronization.
|
||||
static VKRenderingContext context = {
|
||||
.surface = NULL,
|
||||
.transform = {1.0, 0.0, 0.0,0.0, 1.0, 0.0},
|
||||
.clipRect = {{0, 0},{INT_MAX, INT_MAX}},
|
||||
.color = {},
|
||||
.composite = ALPHA_COMPOSITE_SRC_OVER,
|
||||
.extraAlpha = 1.0f,
|
||||
.clipModCount = 1,
|
||||
.clipRect = NO_CLIP,
|
||||
.clipSpanVertices = NULL
|
||||
.extraAlpha = 1.0f
|
||||
};
|
||||
// We keep this color separately from context.color,
|
||||
// because we need consistent state when switching between XOR and alpha composite modes.
|
||||
@@ -162,7 +156,7 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
J2dRlsTraceLn4(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: DRAW_RECT(%d, %d, %d, %d)",
|
||||
x, y, w, h);
|
||||
VKRenderer_RenderRect(&context, VK_FALSE, x, y, w, h);
|
||||
VKRenderer_RenderRect(&context, PIPELINE_DRAW_COLOR, x, y, w, h);
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_DRAW_POLY:
|
||||
@@ -204,7 +198,7 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
J2dRlsTraceLn8(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: DRAW_PARALLELOGRAM(%f, %f, %f, %f, %f, %f, %f, %f)",
|
||||
x11, y11, dx21, dy21, dx12, dy12, lwr21, lwr12);
|
||||
VKRenderer_RenderParallelogram(&context, VK_FALSE, x11, y11, dx21, dy21, dx12, dy12);
|
||||
VKRenderer_RenderParallelogram(&context, PIPELINE_DRAW_COLOR, x11, y11, dx21, dy21, dx12, dy12);
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_DRAW_AAPARALLELOGRAM:
|
||||
@@ -232,7 +226,7 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
jint h = NEXT_INT(b);
|
||||
J2dRlsTraceLn4(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: FILL_RECT(%d, %d, %d, %d)", x, y, w, h);
|
||||
VKRenderer_RenderRect(&context, VK_TRUE, x, y, w, h);
|
||||
VKRenderer_RenderRect(&context, PIPELINE_FILL_COLOR, x, y, w, h);
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_FILL_SPANS:
|
||||
@@ -255,7 +249,7 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
J2dRlsTraceLn6(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: FILL_PARALLELOGRAM(%f, %f, %f, %f, %f, %f)",
|
||||
x11, y11, dx21, dy21, dx12, dy12);
|
||||
VKRenderer_RenderParallelogram(&context, VK_TRUE, x11, y11, dx21, dy21, dx12, dy12);
|
||||
VKRenderer_RenderParallelogram(&context, PIPELINE_FILL_COLOR, x11, y11, dx21, dy21, dx12, dy12);
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_FILL_AAPARALLELOGRAM:
|
||||
@@ -269,8 +263,6 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
J2dRlsTraceLn6(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: FILL_AAPARALLELOGRAM(%f, %f, %f, %f, %f, %f)",
|
||||
x11, y11, dx21, dy21, dx12, dy12);
|
||||
// TODO this is not AA!
|
||||
VKRenderer_RenderParallelogram(&context, VK_TRUE, x11, y11, dx21, dy21, dx12, dy12);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -300,25 +292,6 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
bytesPerGlyph = BYTES_PER_GLYPH_IMAGE;
|
||||
}
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: DRAW_GLYPH_LIST");
|
||||
// TODO this is a quick and dirty implementation of greyscale-AA text rendering over MASK_FILL. Need to do better.
|
||||
jfloat glyphx, glyphy;
|
||||
for (int i = 0; i < numGlyphs; i++) {
|
||||
GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images));
|
||||
if (usePositions) {
|
||||
jfloat posx = NEXT_FLOAT(positions);
|
||||
jfloat posy = NEXT_FLOAT(positions);
|
||||
glyphx = glyphListOrigX + posx + ginfo->topLeftX;
|
||||
glyphy = glyphListOrigY + posy + ginfo->topLeftY;
|
||||
} else {
|
||||
glyphx = glyphListOrigX + ginfo->topLeftX;
|
||||
glyphy = glyphListOrigY + ginfo->topLeftY;
|
||||
glyphListOrigX += ginfo->advanceX;
|
||||
glyphListOrigY += ginfo->advanceY;
|
||||
}
|
||||
if (ginfo->format != sun_font_StrikeCache_PIXEL_FORMAT_GREYSCALE) continue;
|
||||
if (ginfo->height*ginfo->rowBytes == 0) continue;
|
||||
VKRenderer_MaskFill(&context, (int) glyphx, (int) glyphy, ginfo->width, ginfo->height, 0, ginfo->rowBytes, ginfo->height*ginfo->rowBytes, ginfo->image);
|
||||
}
|
||||
SKIP_BYTES(b, numGlyphs * bytesPerGlyph);
|
||||
}
|
||||
break;
|
||||
@@ -375,7 +348,7 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
dx1, dy1, dx2, dy2);
|
||||
}
|
||||
context.surface = oldSurface;
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: BLIT %p -> %p ", pSrc, pDst)
|
||||
break;
|
||||
J2dRlsTraceLn8(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: BLIT (%d %d %d %d) -> (%f %f %f %f) ",
|
||||
sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2)
|
||||
J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: BLIT texture=%d rtt=%d xform=%d isoblit=%d",
|
||||
@@ -408,10 +381,7 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
jint maskscan = NEXT_INT(b);
|
||||
jint masklen = NEXT_INT(b);
|
||||
unsigned char *pMask = (masklen > 0) ? b : NULL;
|
||||
J2dRlsTraceLn7(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: MASK_FILL(%d, %d, %dx%d, maskoff=%d, maskscan=%d, masklen=%d)",
|
||||
x, y, w, h, maskoff, maskscan, masklen);
|
||||
VKRenderer_MaskFill(&context, x, y, w, h, maskoff, maskscan, masklen, pMask);
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: MASK_FILL");
|
||||
SKIP_BYTES(b, masklen);
|
||||
}
|
||||
break;
|
||||
@@ -434,21 +404,18 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
jint y1 = NEXT_INT(b);
|
||||
jint x2 = NEXT_INT(b);
|
||||
jint y2 = NEXT_INT(b);
|
||||
context.clipRect = (VkRect2D){
|
||||
{x1, y1},
|
||||
{x2-x1, y2 - y1}};
|
||||
J2dRlsTraceLn4(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: SET_RECT_CLIP(%d, %d, %d, %d)",
|
||||
x1, y1, x2, y2);
|
||||
ARRAY_RESIZE(context.clipSpanVertices, 0);
|
||||
jint width = x2 - x1, height = y2 - y1;
|
||||
context.clipRect = (VkRect2D) {{x1, y1}, {CARR_MAX(width, 0), CARR_MAX(height, 0)}};
|
||||
context.clipModCount++;
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_BEGIN_SHAPE_CLIP:
|
||||
{
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: BEGIN_SHAPE_CLIP");
|
||||
ARRAY_RESIZE(context.clipSpanVertices, 0);
|
||||
context.clipModCount++;
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_SET_SHAPE_CLIP_SPANS:
|
||||
@@ -456,38 +423,19 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
jint count = NEXT_INT(b);
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: SET_SHAPE_CLIP_SPANS");
|
||||
size_t offset = ARRAY_SIZE(context.clipSpanVertices);
|
||||
ARRAY_RESIZE(context.clipSpanVertices, offset + count * 6);
|
||||
for (jint i = 0; i < count; i++) {
|
||||
jint x1 = NEXT_INT(b);
|
||||
jint y1 = NEXT_INT(b);
|
||||
jint x2 = NEXT_INT(b);
|
||||
jint y2 = NEXT_INT(b);
|
||||
context.clipSpanVertices[offset + i * 6 + 0] = (VKIntVertex) {x1, y1};
|
||||
context.clipSpanVertices[offset + i * 6 + 1] = (VKIntVertex) {x2, y1};
|
||||
context.clipSpanVertices[offset + i * 6 + 2] = (VKIntVertex) {x2, y2};
|
||||
context.clipSpanVertices[offset + i * 6 + 3] = (VKIntVertex) {x2, y2};
|
||||
context.clipSpanVertices[offset + i * 6 + 4] = (VKIntVertex) {x1, y2};
|
||||
context.clipSpanVertices[offset + i * 6 + 5] = (VKIntVertex) {x1, y1};
|
||||
}
|
||||
context.clipModCount++;
|
||||
SKIP_BYTES(b, count * BYTES_PER_SPAN);
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_END_SHAPE_CLIP:
|
||||
{
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: END_SHAPE_CLIP");
|
||||
context.clipRect = NO_CLIP;
|
||||
context.clipModCount++;
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_RESET_CLIP:
|
||||
{
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: RESET_CLIP");
|
||||
ARRAY_RESIZE(context.clipSpanVertices, 0);
|
||||
context.clipRect = NO_CLIP;
|
||||
context.clipModCount++;
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_SET_ALPHA_COMPOSITE:
|
||||
@@ -556,16 +504,9 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
{
|
||||
VKSDOps* src = NEXT_SURFACE(b);
|
||||
VKSDOps* dst = NEXT_SURFACE(b);
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: SET_SURFACES src=%p dst=%p", src, dst);
|
||||
|
||||
if (context.surface != NULL && context.surface != dst) {
|
||||
// TODO Problematic surface flush on a context switch without explicit presentation request.
|
||||
// Its presence here should not make any difference, but for some reason does.
|
||||
// Related scenarios need an investigation, e.g. J2Demo.
|
||||
VKRenderer_FlushSurface(context.surface);
|
||||
}
|
||||
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: SET_SURFACES");
|
||||
context.surface = dst;
|
||||
}
|
||||
break;
|
||||
@@ -580,8 +521,8 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
case sun_java2d_pipe_BufferedOpCodes_FLUSH_SURFACE:
|
||||
{
|
||||
VKSDOps* surface = NEXT_SURFACE(b);
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: FLUSH_SURFACE (%p)", surface)
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: FLUSH_SURFACE");
|
||||
}
|
||||
break;
|
||||
case sun_java2d_pipe_BufferedOpCodes_DISPOSE_SURFACE:
|
||||
@@ -618,8 +559,8 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
VKSDOps* surface = NEXT_SURFACE(b);
|
||||
jint width = NEXT_INT(b);
|
||||
jint height = NEXT_INT(b);
|
||||
J2dRlsTraceLn3(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: CONFIGURE_SURFACE (%p) %dx%d", surface, width, height);
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: CONFIGURE_SURFACE %dx%d", width, height);
|
||||
VKRenderer_ConfigureSurface(surface, (VkExtent2D) {width, height});
|
||||
}
|
||||
break;
|
||||
@@ -636,8 +577,8 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
|
||||
case sun_java2d_pipe_BufferedOpCodes_FLUSH_BUFFER:
|
||||
{
|
||||
VKSDOps* surface = NEXT_SURFACE(b);
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: FLUSH_BUFFER (%p)", surface)
|
||||
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
|
||||
"VKRenderQueue_flushBuffer: FLUSH_BUFFER");
|
||||
|
||||
VKRenderer_FlushSurface(surface);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#ifndef HEADLESS
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "VKUtil.h"
|
||||
#include "VKBase.h"
|
||||
#include "VKAllocator.h"
|
||||
@@ -40,11 +39,11 @@
|
||||
* Pool of resources with associated timestamps, guarding their reuse.
|
||||
* The pool must only be manipulated via POOL_* macros.
|
||||
*/
|
||||
#define POOL(TYPE, NAME) \
|
||||
RING_BUFFER(struct PoolEntry_ ## NAME { \
|
||||
uint64_t timestamp; \
|
||||
TYPE value; \
|
||||
}) NAME
|
||||
#define POOL(TYPE, NAME) \
|
||||
struct PoolEntry_ ## NAME { \
|
||||
uint64_t timestamp; \
|
||||
TYPE value; \
|
||||
} *NAME
|
||||
|
||||
/**
|
||||
* Take an available item from the pool. VAR is left unchanged if there is no available item.
|
||||
@@ -59,14 +58,14 @@ RING_BUFFER(struct PoolEntry_ ## NAME { \
|
||||
* after the next submitted batch of work completes execution on GPU.
|
||||
*/
|
||||
// In debug mode resource reuse will be randomly delayed by 3 timestamps in ~20% cases.
|
||||
#define POOL_RETURN(RENDERER, NAME, VAR) (RING_BUFFER_PUSH_BACK((RENDERER)->NAME) = \
|
||||
#define POOL_RETURN(RENDERER, NAME, VAR) RING_BUFFER_PUSH_BACK((RENDERER)->NAME, \
|
||||
(struct PoolEntry_ ## NAME) { .timestamp = (RENDERER)->writeTimestamp + (VK_DEBUG_RANDOM(20)*3), .value = (VAR) })
|
||||
|
||||
/**
|
||||
* Insert an item into the pool. It is available for POOL_TAKE immediately.
|
||||
* This is usually used for bulk insertion of newly-created resources.
|
||||
*/
|
||||
#define POOL_INSERT(RENDERER, NAME, VAR) (RING_BUFFER_PUSH_FRONT((RENDERER)->NAME) = \
|
||||
#define POOL_INSERT(RENDERER, NAME, VAR) RING_BUFFER_PUSH_FRONT((RENDERER)->NAME, \
|
||||
(struct PoolEntry_ ## NAME) { .timestamp = 0ULL, .value = (VAR) })
|
||||
|
||||
/**
|
||||
@@ -91,14 +90,11 @@ struct VKRenderer {
|
||||
VKDevice* device;
|
||||
VKPipelineContext* pipelineContext;
|
||||
|
||||
POOL(VkCommandBuffer, commandBufferPool);
|
||||
POOL(VkCommandBuffer, secondaryCommandBufferPool);
|
||||
POOL(VkSemaphore, semaphorePool);
|
||||
POOL(VKBuffer, vertexBufferPool);
|
||||
POOL(VKTexelBuffer, maskFillBufferPool);
|
||||
POOL(VkFramebuffer, framebufferDestructionQueue);
|
||||
ARRAY(VKMemory) bufferMemoryPages;
|
||||
ARRAY(VkDescriptorPool) descriptorPools;
|
||||
POOL(VkCommandBuffer, commandBufferPool);
|
||||
POOL(VkCommandBuffer, secondaryCommandBufferPool);
|
||||
POOL(VkSemaphore, semaphorePool);
|
||||
POOL(VKBuffer, vertexBufferPool);
|
||||
VKMemory* vertexBufferMemoryPages;
|
||||
|
||||
/**
|
||||
* Last known timestamp hit by GPU execution. Resources with equal or less timestamp may be safely reused.
|
||||
@@ -114,14 +110,14 @@ struct VKRenderer {
|
||||
VkCommandBuffer commandBuffer;
|
||||
|
||||
struct Wait {
|
||||
ARRAY(VkSemaphore) semaphores;
|
||||
ARRAY(VkPipelineStageFlags) stages;
|
||||
VkSemaphore* semaphores;
|
||||
VkPipelineStageFlags* stages;
|
||||
} wait;
|
||||
|
||||
struct PendingPresentation {
|
||||
ARRAY(VkSwapchainKHR) swapchains;
|
||||
ARRAY(uint32_t) indices;
|
||||
ARRAY(VkResult) results;
|
||||
VkSwapchainKHR* swapchains;
|
||||
uint32_t* indices;
|
||||
VkResult* results;
|
||||
} pendingPresentation;
|
||||
};
|
||||
|
||||
@@ -138,23 +134,20 @@ typedef struct {
|
||||
*/
|
||||
struct VKRenderPass {
|
||||
VKRenderPassContext* context;
|
||||
ARRAY(VKBuffer) vertexBuffers;
|
||||
ARRAY(VKTexelBuffer) maskFillBuffers;
|
||||
VkRenderPass renderPass; // Non-owning.
|
||||
VKBuffer* vertexBuffers;
|
||||
VkFramebuffer framebuffer;
|
||||
VkCommandBuffer commandBuffer;
|
||||
|
||||
uint32_t firstVertex;
|
||||
uint32_t vertexCount;
|
||||
BufferWritingState vertexBufferWriting;
|
||||
BufferWritingState maskFillBufferWriting;
|
||||
|
||||
VKPipelineDescriptor state;
|
||||
uint64_t clipModCount; // Just a tag to detect when clip was changed.
|
||||
VkBool32 pendingFlush;
|
||||
VkBool32 pendingCommands;
|
||||
VkBool32 pendingClear;
|
||||
uint64_t lastTimestamp; // When was this surface last used?
|
||||
VKCompositeMode currentComposite;
|
||||
VKPipeline currentPipeline;
|
||||
VkBool32 pendingFlush;
|
||||
VkBool32 pendingCommands;
|
||||
VkBool32 pendingClear;
|
||||
uint64_t lastTimestamp; // When was this surface last used?
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -174,10 +167,7 @@ inline VkBool32 VKRenderer_CheckPoolEntryAvailable(VKRenderer* renderer, void* e
|
||||
*/
|
||||
static VkBool32 VKRenderer_CheckPoolDrain(void* pool, void* entry) {
|
||||
if (entry != NULL) return VK_TRUE;
|
||||
if (pool != NULL) {
|
||||
RING_BUFFER(char) ring_buffer = pool;
|
||||
RING_BUFFER_FREE(ring_buffer);
|
||||
}
|
||||
else if (pool != NULL) RING_BUFFER_FREE(pool);
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
@@ -198,39 +188,11 @@ static VKBuffer VKRenderer_GetVertexBuffer(VKRenderer* renderer) {
|
||||
VKRenderer_FindVertexBufferMemoryType,
|
||||
VERTEX_BUFFER_SIZE, VERTEX_BUFFER_PAGE_SIZE, &bufferCount, buffers);
|
||||
VK_RUNTIME_ASSERT(page);
|
||||
ARRAY_PUSH_BACK(renderer->bufferMemoryPages) = page;
|
||||
ARRAY_PUSH_BACK(renderer->vertexBufferMemoryPages, page);
|
||||
for (uint32_t i = 1; i < bufferCount; i++) POOL_INSERT(renderer, vertexBufferPool, buffers[i]);
|
||||
return buffers[0];
|
||||
}
|
||||
|
||||
#define MASK_FILL_BUFFER_SIZE (256 * 1024) // 256KiB = 256 typical MASK_FILL tiles
|
||||
#define MASK_FILL_BUFFER_PAGE_SIZE (4 * 1024 * 1024) // 4MiB - fits 16 buffers
|
||||
static void VKRenderer_FindMaskFillBufferMemoryType(VKMemoryRequirements* requirements) {
|
||||
VKAllocator_FindMemoryType(requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
VK_ALL_MEMORY_PROPERTIES);
|
||||
VKAllocator_FindMemoryType(requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_ALL_MEMORY_PROPERTIES);
|
||||
}
|
||||
static VKTexelBuffer VKRenderer_GetMaskFillBuffer(VKRenderer* renderer) {
|
||||
VKTexelBuffer buffer = { .buffer.handle = VK_NULL_HANDLE };
|
||||
POOL_TAKE(renderer, maskFillBufferPool, buffer);
|
||||
if (buffer.buffer.handle != VK_NULL_HANDLE) return buffer;
|
||||
uint32_t bufferCount = MASK_FILL_BUFFER_PAGE_SIZE / MASK_FILL_BUFFER_SIZE;
|
||||
VKBuffer buffers[bufferCount];
|
||||
VKMemory page = VKBuffer_CreateBuffers(renderer->device, VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT,
|
||||
VKRenderer_FindMaskFillBufferMemoryType,
|
||||
MASK_FILL_BUFFER_SIZE, MASK_FILL_BUFFER_PAGE_SIZE, &bufferCount, buffers);
|
||||
VK_RUNTIME_ASSERT(page);
|
||||
VKTexelBuffer texelBuffers[bufferCount];
|
||||
VkDescriptorPool descriptorPool = VKBuffer_CreateTexelBuffers(
|
||||
renderer->device, VK_FORMAT_R8_UNORM, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
|
||||
renderer->pipelineContext->maskFillDescriptorSetLayout, bufferCount, buffers, texelBuffers);
|
||||
VK_RUNTIME_ASSERT(descriptorPool);
|
||||
for (uint32_t i = 1; i < bufferCount; i++) POOL_INSERT(renderer, maskFillBufferPool, texelBuffers[i]);
|
||||
ARRAY_PUSH_BACK(renderer->bufferMemoryPages) = page;
|
||||
ARRAY_PUSH_BACK(renderer->descriptorPools) = descriptorPool;
|
||||
return texelBuffers[0];
|
||||
}
|
||||
|
||||
static VkSemaphore VKRenderer_AddPendingSemaphore(VKRenderer* renderer) {
|
||||
VKDevice* device = renderer->device;
|
||||
VkSemaphore semaphore = VK_NULL_HANDLE;
|
||||
@@ -332,26 +294,14 @@ void VKRenderer_Destroy(VKRenderer* renderer) {
|
||||
device->vkDestroySemaphore(device->handle, entry->value, NULL);
|
||||
}
|
||||
|
||||
// Destroy buffer pools.
|
||||
// Destroy vertex buffer pool.
|
||||
POOL_DRAIN_FOR(renderer, vertexBufferPool, entry) {
|
||||
device->vkDestroyBuffer(device->handle, entry->value.handle, NULL);
|
||||
}
|
||||
POOL_DRAIN_FOR(renderer, maskFillBufferPool, entry) {
|
||||
// No need to destroy descriptor sets one by one, we will destroy the pool anyway.
|
||||
device->vkDestroyBufferView(device->handle, entry->value.view, NULL);
|
||||
device->vkDestroyBuffer(device->handle, entry->value.buffer.handle, NULL);
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(renderer->vertexBufferMemoryPages); i++) {
|
||||
VKAllocator_Free(device->allocator, renderer->vertexBufferMemoryPages[i]);
|
||||
}
|
||||
POOL_DRAIN_FOR(renderer, framebufferDestructionQueue, entry) {
|
||||
device->vkDestroyFramebuffer(device->handle, entry->value, NULL);
|
||||
}
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(renderer->bufferMemoryPages); i++) {
|
||||
VKAllocator_Free(device->allocator, renderer->bufferMemoryPages[i]);
|
||||
}
|
||||
ARRAY_FREE(renderer->bufferMemoryPages);
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(renderer->descriptorPools); i++) {
|
||||
device->vkDestroyDescriptorPool(device->handle, renderer->descriptorPools[i], NULL);
|
||||
}
|
||||
ARRAY_FREE(renderer->descriptorPools);
|
||||
ARRAY_FREE(renderer->vertexBufferMemoryPages);
|
||||
|
||||
device->vkDestroySemaphore(device->handle, renderer->timelineSemaphore, NULL);
|
||||
device->vkDestroyCommandPool(device->handle, renderer->commandPool, NULL);
|
||||
@@ -364,17 +314,6 @@ void VKRenderer_Destroy(VKRenderer* renderer) {
|
||||
free(renderer);
|
||||
}
|
||||
|
||||
static void VKRenderer_CleanupPendingResources(VKRenderer* renderer) {
|
||||
VKDevice* device = renderer->device;
|
||||
for (;;) {
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
POOL_TAKE(renderer, framebufferDestructionQueue, framebuffer);
|
||||
if (framebuffer == VK_NULL_HANDLE) break;
|
||||
device->vkDestroyFramebuffer(device->handle, framebuffer, NULL);
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "VKRenderer_CleanupPendingResources(%p): framebuffer destroyed", renderer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record commands into primary command buffer (outside of a render pass).
|
||||
* Recorded commands will be sent for execution via VKRenderer_Flush.
|
||||
@@ -412,7 +351,6 @@ VkCommandBuffer VKRenderer_Record(VKRenderer* renderer) {
|
||||
|
||||
void VKRenderer_Flush(VKRenderer* renderer) {
|
||||
if (renderer == NULL) return;
|
||||
VKRenderer_CleanupPendingResources(renderer);
|
||||
VKDevice* device = renderer->device;
|
||||
size_t pendingPresentations = ARRAY_SIZE(renderer->pendingPresentation.swapchains);
|
||||
|
||||
@@ -498,7 +436,7 @@ void VKRenderer_AddImageBarrier(VkImageMemoryBarrier* barriers, VKBarrierBatch*
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image->handle,
|
||||
.subresourceRange = { VKImage_GetAspect(image), 0, 1, 0, 1 }
|
||||
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }
|
||||
};
|
||||
batch->barrierCount++;
|
||||
batch->srcStages |= image->lastStage;
|
||||
@@ -527,28 +465,20 @@ inline void VKRenderer_FlushDraw(VKSDOps* surface) {
|
||||
*/
|
||||
static void VKRenderer_ResetDrawing(VKSDOps* surface) {
|
||||
assert(surface != NULL && surface->renderPass != NULL);
|
||||
surface->renderPass->state.composite = NO_COMPOSITE;
|
||||
surface->renderPass->state.shader = NO_SHADER;
|
||||
surface->renderPass->currentComposite = NO_COMPOSITE;
|
||||
surface->renderPass->currentPipeline = NO_PIPELINE;
|
||||
surface->renderPass->firstVertex = 0;
|
||||
surface->renderPass->vertexCount = 0;
|
||||
surface->renderPass->vertexBufferWriting = (BufferWritingState) {NULL, 0, VK_FALSE};
|
||||
surface->renderPass->maskFillBufferWriting = (BufferWritingState) {NULL, 0, VK_FALSE};
|
||||
size_t vertexBufferCount = ARRAY_SIZE(surface->renderPass->vertexBuffers);
|
||||
size_t maskFillBufferCount = ARRAY_SIZE(surface->renderPass->maskFillBuffers);
|
||||
if (vertexBufferCount == 0 && maskFillBufferCount == 0) return;
|
||||
VkMappedMemoryRange memoryRanges[vertexBufferCount + maskFillBufferCount];
|
||||
if (vertexBufferCount == 0) return;
|
||||
VkMappedMemoryRange memoryRanges[vertexBufferCount];
|
||||
for (uint32_t i = 0; i < vertexBufferCount; i++) {
|
||||
memoryRanges[i] = surface->renderPass->vertexBuffers[i].range;
|
||||
POOL_RETURN(surface->device->renderer, vertexBufferPool, surface->renderPass->vertexBuffers[i]);
|
||||
}
|
||||
for (uint32_t i = 0; i < maskFillBufferCount; i++) {
|
||||
memoryRanges[vertexBufferCount + i] = surface->renderPass->maskFillBuffers[i].buffer.range;
|
||||
POOL_RETURN(surface->device->renderer, maskFillBufferPool, surface->renderPass->maskFillBuffers[i]);
|
||||
}
|
||||
ARRAY_RESIZE(surface->renderPass->vertexBuffers, 0);
|
||||
ARRAY_RESIZE(surface->renderPass->maskFillBuffers, 0);
|
||||
VK_IF_ERROR(surface->device->vkFlushMappedMemoryRanges(surface->device->handle,
|
||||
vertexBufferCount + maskFillBufferCount, memoryRanges)) {}
|
||||
VK_IF_ERROR(surface->device->vkFlushMappedMemoryRanges(surface->device->handle, vertexBufferCount, memoryRanges)) {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -572,7 +502,6 @@ void VKRenderer_DestroyRenderPass(VKSDOps* surface) {
|
||||
if (device != NULL && device->renderer != NULL) {
|
||||
// Wait while surface resources are being used by the device.
|
||||
VKRenderer_Wait(device->renderer, surface->renderPass->lastTimestamp);
|
||||
VKRenderer_CleanupPendingResources(device->renderer);
|
||||
VKRenderer_DiscardRenderPass(surface);
|
||||
// Release resources.
|
||||
device->vkDestroyFramebuffer(device->handle, surface->renderPass->framebuffer, NULL);
|
||||
@@ -580,7 +509,6 @@ void VKRenderer_DestroyRenderPass(VKSDOps* surface) {
|
||||
POOL_RETURN(device->renderer, secondaryCommandBufferPool, surface->renderPass->commandBuffer);
|
||||
}
|
||||
ARRAY_FREE(surface->renderPass->vertexBuffers);
|
||||
ARRAY_FREE(surface->renderPass->maskFillBuffers);
|
||||
}
|
||||
free(surface->renderPass);
|
||||
surface->renderPass = NULL;
|
||||
@@ -605,15 +533,10 @@ static VkBool32 VKRenderer_InitRenderPass(VKSDOps* surface) {
|
||||
VKRenderPass* renderPass = surface->renderPass = malloc(sizeof(VKRenderPass));
|
||||
VK_RUNTIME_ASSERT(renderPass);
|
||||
(*renderPass) = (VKRenderPass) {
|
||||
.state = {
|
||||
.stencilMode = STENCIL_MODE_NONE,
|
||||
.composite = NO_COMPOSITE,
|
||||
.shader = NO_SHADER
|
||||
},
|
||||
.clipModCount = 0,
|
||||
.pendingFlush = VK_FALSE,
|
||||
.pendingCommands = VK_FALSE,
|
||||
.pendingClear = VK_TRUE, // Clear the surface by default
|
||||
.currentComposite = NO_COMPOSITE,
|
||||
.currentPipeline = NO_PIPELINE,
|
||||
.lastTimestamp = 0
|
||||
};
|
||||
|
||||
@@ -622,48 +545,23 @@ static VkBool32 VKRenderer_InitRenderPass(VKSDOps* surface) {
|
||||
renderPass->context = VKPipelines_GetRenderPassContext(renderer->pipelineContext, surface->image->format);
|
||||
}
|
||||
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "VKRenderer_InitRenderPass(%p)", surface);
|
||||
return VK_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize surface framebuffer.
|
||||
* This function can be called between render passes of a single frame, unlike VKRenderer_InitRenderPass.
|
||||
*/
|
||||
static void VKRenderer_InitFramebuffer(VKSDOps* surface) {
|
||||
assert(surface != NULL && surface->device != NULL && surface->renderPass != NULL);
|
||||
VKDevice* device = surface->device;
|
||||
VKRenderPass* renderPass = surface->renderPass;
|
||||
|
||||
if (renderPass->state.stencilMode == STENCIL_MODE_NONE && surface->stencil != NULL) {
|
||||
// Queue outdated color-only framebuffer for destruction.
|
||||
POOL_RETURN(device->renderer, framebufferDestructionQueue, renderPass->framebuffer);
|
||||
renderPass->framebuffer = VK_NULL_HANDLE;
|
||||
renderPass->state.stencilMode = STENCIL_MODE_OFF;
|
||||
}
|
||||
|
||||
// Initialize framebuffer.
|
||||
if (renderPass->framebuffer == VK_NULL_HANDLE) {
|
||||
renderPass->renderPass = renderPass->context->renderPass[surface->stencil != NULL];
|
||||
VkImageView views[] = { surface->image->view, VK_NULL_HANDLE };
|
||||
VkFramebufferCreateInfo framebufferCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.renderPass = renderPass->renderPass,
|
||||
.renderPass = renderPass->context->renderPass,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = views,
|
||||
.pAttachments = &surface->image->view,
|
||||
.width = surface->image->extent.width,
|
||||
.height = surface->image->extent.height,
|
||||
.layers = 1
|
||||
};
|
||||
if (surface->stencil != NULL) {
|
||||
framebufferCreateInfo.attachmentCount = 2;
|
||||
views[1] = surface->stencil->view;
|
||||
}
|
||||
VK_IF_ERROR(device->vkCreateFramebuffer(device->handle, &framebufferCreateInfo, NULL,
|
||||
&renderPass->framebuffer)) VK_UNHANDLED_ERROR();
|
||||
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "VKRenderer_InitFramebuffer(%p)", surface);
|
||||
}
|
||||
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "VKRenderer_InitRenderPass(%p)", surface);
|
||||
return VK_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -671,7 +569,6 @@ static void VKRenderer_InitFramebuffer(VKSDOps* surface) {
|
||||
*/
|
||||
static void VKRenderer_BeginRenderPass(VKSDOps* surface) {
|
||||
assert(surface != NULL && surface->renderPass != NULL && !surface->renderPass->pendingCommands);
|
||||
VKRenderer_InitFramebuffer(surface);
|
||||
// We may have a pending flush, which is already obsolete.
|
||||
surface->renderPass->pendingFlush = VK_FALSE;
|
||||
VKDevice* device = surface->device;
|
||||
@@ -698,7 +595,7 @@ static void VKRenderer_BeginRenderPass(VKSDOps* surface) {
|
||||
// Begin recording render pass commands.
|
||||
VkCommandBufferInheritanceInfo inheritanceInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO,
|
||||
.renderPass = surface->renderPass->renderPass,
|
||||
.renderPass = surface->renderPass->context->renderPass,
|
||||
.subpass = 0,
|
||||
.framebuffer = surface->renderPass->framebuffer
|
||||
};
|
||||
@@ -727,7 +624,7 @@ static void VKRenderer_BeginRenderPass(VKSDOps* surface) {
|
||||
surface->renderPass->pendingClear = VK_FALSE;
|
||||
}
|
||||
|
||||
// Set viewport.
|
||||
// Set viewport and scissor.
|
||||
VkViewport viewport = {
|
||||
.x = 0.0f,
|
||||
.y = 0.0f,
|
||||
@@ -736,13 +633,15 @@ static void VKRenderer_BeginRenderPass(VKSDOps* surface) {
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f
|
||||
};
|
||||
VkRect2D scissor = {{0, 0}, surface->image->extent};
|
||||
device->vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
|
||||
device->vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
|
||||
// Calculate inverse viewport for vertex shader.
|
||||
viewport.width = 2.0f / viewport.width;
|
||||
viewport.height = 2.0f / viewport.height;
|
||||
device->vkCmdPushConstants(
|
||||
commandBuffer,
|
||||
renderer->pipelineContext->colorPipelineLayout, // TODO what if our pipeline layout differs?
|
||||
renderer->pipelineContext->pipelineLayout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0,
|
||||
sizeof(float) * 2,
|
||||
@@ -767,36 +666,30 @@ void VKRenderer_FlushRenderPass(VKSDOps* surface) {
|
||||
surface->renderPass->lastTimestamp = renderer->writeTimestamp;
|
||||
VkCommandBuffer cb = VKRenderer_Record(renderer);
|
||||
|
||||
// Insert barriers to prepare surface for rendering.
|
||||
VkImageMemoryBarrier barriers[2];
|
||||
// Insert barrier to prepare surface for rendering.
|
||||
VkImageMemoryBarrier barriers[1];
|
||||
VKBarrierBatch barrierBatch = {};
|
||||
VKRenderer_AddImageBarrier(barriers, &barrierBatch, surface->image,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
if (surface->stencil != NULL) {
|
||||
VKRenderer_AddImageBarrier(barriers, &barrierBatch, surface->stencil,
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
if (barrierBatch.barrierCount > 0) {
|
||||
device->vkCmdPipelineBarrier(cb, barrierBatch.srcStages, barrierBatch.dstStages,
|
||||
0, 0, NULL, 0, NULL, barrierBatch.barrierCount, barriers);
|
||||
}
|
||||
|
||||
// If there is a pending clear, record it into render pass.
|
||||
if (clear) VKRenderer_BeginRenderPass(surface);
|
||||
// Begin render pass.
|
||||
VkRenderPassBeginInfo renderPassInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = surface->renderPass->renderPass,
|
||||
.renderPass = surface->renderPass->context->renderPass,
|
||||
.framebuffer = surface->renderPass->framebuffer,
|
||||
.renderArea = (VkRect2D) {{0, 0}, surface->image->extent},
|
||||
.clearValueCount = 0,
|
||||
.pClearValues = NULL
|
||||
};
|
||||
device->vkCmdBeginRenderPass(cb, &renderPassInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
|
||||
// If there is a pending clear, record it into render pass.
|
||||
if (clear) VKRenderer_BeginRenderPass(surface);
|
||||
|
||||
// Execute render pass commands.
|
||||
if (surface->renderPass->pendingCommands) {
|
||||
@@ -847,8 +740,8 @@ void VKRenderer_FlushSurface(VKSDOps* surface) {
|
||||
|
||||
// Acquire swapchain image.
|
||||
VkSemaphore acquireSemaphore = VKRenderer_AddPendingSemaphore(renderer);
|
||||
ARRAY_PUSH_BACK(renderer->wait.semaphores) = acquireSemaphore;
|
||||
ARRAY_PUSH_BACK(renderer->wait.stages) = VK_PIPELINE_STAGE_TRANSFER_BIT; // Acquire image before blitting content onto swapchain
|
||||
ARRAY_PUSH_BACK(renderer->wait.semaphores, acquireSemaphore);
|
||||
ARRAY_PUSH_BACK(renderer->wait.stages, VK_PIPELINE_STAGE_TRANSFER_BIT); // Acquire image before blitting content onto swapchain
|
||||
|
||||
uint32_t imageIndex;
|
||||
VkResult acquireImageResult = device->vkAcquireNextImageKHR(device->handle, win->swapchain, UINT64_MAX,
|
||||
@@ -909,8 +802,8 @@ void VKRenderer_FlushSurface(VKSDOps* surface) {
|
||||
}
|
||||
|
||||
// Add pending presentation request.
|
||||
ARRAY_PUSH_BACK(renderer->pendingPresentation.swapchains) = win->swapchain;
|
||||
ARRAY_PUSH_BACK(renderer->pendingPresentation.indices) = imageIndex;
|
||||
ARRAY_PUSH_BACK(renderer->pendingPresentation.swapchains, win->swapchain);
|
||||
ARRAY_PUSH_BACK(renderer->pendingPresentation.indices, imageIndex);
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "VKRenderer_FlushSurface(%p): queued for presentation", surface);
|
||||
}
|
||||
}
|
||||
@@ -961,10 +854,8 @@ inline BufferWritingState VKRenderer_AllocateBufferData(VKSDOps* surface, Buffer
|
||||
* This function must not be used directly, use VK_DRAW macro instead.
|
||||
* It is responsibility of the caller to pass correct vertexSize, matching current pipeline.
|
||||
* This function cannot draw more vertices than fits into single vertex buffer at once.
|
||||
* This function must be called after all dynamic allocation functions,
|
||||
* which can invalidate drawing state, e.g. VKRenderer_AllocateMaskFillBytes.
|
||||
*/
|
||||
static void* VKRenderer_AllocateVertices(const VKRenderingContext* context, uint32_t vertices, size_t vertexSize) {
|
||||
static void* VKRenderer_AllocateVertices(VKRenderingContext* context, uint32_t vertices, size_t vertexSize) {
|
||||
assert(vertices > 0 && vertexSize > 0);
|
||||
assert(vertexSize * vertices <= VERTEX_BUFFER_SIZE);
|
||||
VKSDOps* surface = context->surface;
|
||||
@@ -973,7 +864,7 @@ static void* VKRenderer_AllocateVertices(const VKRenderingContext* context, uint
|
||||
if (!state.bound) {
|
||||
if (state.data == NULL) {
|
||||
VKBuffer buffer = VKRenderer_GetVertexBuffer(surface->device->renderer);
|
||||
ARRAY_PUSH_BACK(surface->renderPass->vertexBuffers) = buffer;
|
||||
ARRAY_PUSH_BACK(surface->renderPass->vertexBuffers, buffer);
|
||||
surface->renderPass->vertexBufferWriting.data = state.data = buffer.data;
|
||||
}
|
||||
assert(ARRAY_SIZE(surface->renderPass->vertexBuffers) > 0);
|
||||
@@ -987,100 +878,14 @@ static void* VKRenderer_AllocateVertices(const VKRenderingContext* context, uint
|
||||
|
||||
/**
|
||||
* Allocate vertices from vertex buffer, providing pointer for writing.
|
||||
* VKRenderer_Validate must have been called before.
|
||||
* This function cannot draw more vertices than fits into single vertex buffer at once.
|
||||
* This function must be called after all dynamic allocation functions,
|
||||
* which can invalidate drawing state, e.g. VKRenderer_AllocateMaskFillBytes.
|
||||
*/
|
||||
#define VK_DRAW(VERTICES, CONTEXT, VERTEX_COUNT) \
|
||||
(VERTICES) = VKRenderer_AllocateVertices((CONTEXT), (VERTEX_COUNT), sizeof((VERTICES)[0]))
|
||||
|
||||
/**
|
||||
* Allocate bytes from mask fill buffer. VKRenderer_Validate must have been called before.
|
||||
* This function cannot take more bytes than fits into single mask fill buffer at once.
|
||||
* Caller must write data at the returned pointer DrawingBufferWritingState.data
|
||||
* and take into account DrawingBufferWritingState.offset from the beginning of the bound buffer.
|
||||
* This function can invalidate drawing state, always call it before VK_DRAW.
|
||||
*/
|
||||
static BufferWritingState VKRenderer_AllocateMaskFillBytes(const VKRenderingContext* context, uint32_t size) {
|
||||
assert(size > 0);
|
||||
assert(size <= MASK_FILL_BUFFER_SIZE);
|
||||
VKSDOps* surface = context->surface;
|
||||
BufferWritingState state = VKRenderer_AllocateBufferData(
|
||||
surface, &surface->renderPass->maskFillBufferWriting, size, MASK_FILL_BUFFER_SIZE);
|
||||
if (!state.bound) {
|
||||
if (state.data == NULL) {
|
||||
VKTexelBuffer buffer = VKRenderer_GetMaskFillBuffer(surface->device->renderer);
|
||||
ARRAY_PUSH_BACK(surface->renderPass->maskFillBuffers) = buffer;
|
||||
surface->renderPass->maskFillBufferWriting.data = state.data = buffer.buffer.data;
|
||||
}
|
||||
assert(ARRAY_SIZE(surface->renderPass->maskFillBuffers) > 0);
|
||||
surface->device->vkCmdBindDescriptorSets(context->surface->renderPass->commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
surface->device->renderer->pipelineContext->maskFillPipelineLayout,
|
||||
0, 1, &ARRAY_LAST(surface->renderPass->maskFillBuffers).descriptorSet, 0, NULL);
|
||||
}
|
||||
state.data = (void*) ((uint8_t*) state.data + state.offset);
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup stencil attachment according to the context clip state.
|
||||
* If there is a clip shape, attachment is cleared with "fail" value and then
|
||||
* pixels inside the clip shape are set to "pass".
|
||||
* If there is no clip shape, whole attachment is cleared with "pass" value.
|
||||
*/
|
||||
static void VKRenderer_SetupStencil(const VKRenderingContext* context) {
|
||||
assert(context != NULL && context->surface != NULL && context->surface->renderPass != NULL);
|
||||
VKSDOps* surface = context->surface;
|
||||
VKRenderPass* renderPass = surface->renderPass;
|
||||
VkCommandBuffer cb = renderPass->commandBuffer;
|
||||
VKRenderer_FlushDraw(surface);
|
||||
|
||||
// Clear stencil attachment.
|
||||
VkClearAttachment clearAttachment = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT,
|
||||
.clearValue.depthStencil.stencil = ARRAY_SIZE(context->clipSpanVertices) > 0 ?
|
||||
CLIP_STENCIL_EXCLUDE_VALUE : CLIP_STENCIL_INCLUDE_VALUE
|
||||
};
|
||||
VkClearRect clearRect = {
|
||||
.rect = {{0, 0}, surface->stencil->extent},
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
surface->device->vkCmdClearAttachments(cb, 1, &clearAttachment, 1, &clearRect);
|
||||
|
||||
// Bind the clip pipeline.
|
||||
surface->device->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
VKPipelines_GetPipeline(surface->renderPass->context, (VKPipelineDescriptor) {
|
||||
.stencilMode = STENCIL_MODE_ON,
|
||||
.composite = NO_COMPOSITE,
|
||||
.shader = SHADER_CLIP,
|
||||
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
|
||||
}));
|
||||
// Reset vertex buffer binding.
|
||||
renderPass->vertexBufferWriting.bound = VK_FALSE;
|
||||
|
||||
// Rasterize clip spans.
|
||||
const uint32_t MAX_VERTICES_PER_DRAW = (VERTEX_BUFFER_SIZE / sizeof(VKIntVertex) / 3) * 3;
|
||||
VKIntVertex* vs;
|
||||
for (uint32_t drawn = 0;;) {
|
||||
uint32_t currentDraw = ARRAY_SIZE(context->clipSpanVertices) - drawn;
|
||||
if (currentDraw > MAX_VERTICES_PER_DRAW) currentDraw = MAX_VERTICES_PER_DRAW;
|
||||
else if (currentDraw == 0) break;
|
||||
VK_DRAW(vs, context, currentDraw);
|
||||
memcpy(vs, context->clipSpanVertices + drawn, currentDraw * sizeof(VKIntVertex));
|
||||
drawn += currentDraw;
|
||||
}
|
||||
VKRenderer_FlushDraw(surface);
|
||||
|
||||
// Reset pipeline state.
|
||||
renderPass->state.shader = NO_SHADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup pipeline for drawing. Returns FALSE if surface is not yet ready for drawing.
|
||||
*/
|
||||
VkBool32 VKRenderer_Validate(const VKRenderingContext* context, VKShader shader, VkPrimitiveTopology topology) {
|
||||
VkBool32 VKRenderer_Validate(VKRenderingContext* context, VKPipeline pipeline) {
|
||||
assert(context != NULL && context->surface != NULL);
|
||||
VKSDOps* surface = context->surface;
|
||||
|
||||
@@ -1093,72 +898,46 @@ VkBool32 VKRenderer_Validate(const VKRenderingContext* context, VKShader shader,
|
||||
VKRenderPass* renderPass = surface->renderPass;
|
||||
|
||||
// Validate render pass state.
|
||||
if (renderPass->state.composite != context->composite ||
|
||||
renderPass->clipModCount != context->clipModCount) {
|
||||
if (renderPass->currentComposite != context->composite) {
|
||||
// ALPHA_COMPOSITE_DST keeps destination intact, so don't even bother to change the state.
|
||||
if (context->composite == ALPHA_COMPOSITE_DST) return VK_FALSE;
|
||||
VKCompositeMode oldComposite = renderPass->state.composite;
|
||||
VkBool32 clipChanged = renderPass->clipModCount != context->clipModCount;
|
||||
// Init stencil attachment, if needed.
|
||||
if (clipChanged && ARRAY_SIZE(context->clipSpanVertices) > 0 && surface->stencil == NULL) {
|
||||
if (surface->renderPass->pendingCommands) VKRenderer_FlushRenderPass(surface);
|
||||
if (!VKSD_ConfigureImageSurfaceStencil(surface)) return VK_FALSE;
|
||||
}
|
||||
VKCompositeMode oldComposite = renderPass->currentComposite;
|
||||
// Update state.
|
||||
VKRenderer_FlushDraw(surface);
|
||||
renderPass->state.composite = context->composite;
|
||||
renderPass->clipModCount = context->clipModCount;
|
||||
renderPass->currentComposite = context->composite;
|
||||
// Begin render pass.
|
||||
VkBool32 renderPassJustStarted = !renderPass->pendingCommands;
|
||||
if (renderPassJustStarted) VKRenderer_BeginRenderPass(surface);
|
||||
// Validate current clip.
|
||||
if (clipChanged || renderPassJustStarted) {
|
||||
J2dTraceLn(J2D_TRACE_VERBOSE, "VKRenderer_Validate: updating clip");
|
||||
surface->device->vkCmdSetScissor(renderPass->commandBuffer, 0, 1, &context->clipRect);
|
||||
if (clipChanged) {
|
||||
if (ARRAY_SIZE(context->clipSpanVertices) > 0) {
|
||||
VKRenderer_SetupStencil(context);
|
||||
renderPass->state.stencilMode = STENCIL_MODE_ON;
|
||||
} else renderPass->state.stencilMode = surface->stencil != NULL ? STENCIL_MODE_OFF : STENCIL_MODE_NONE;
|
||||
}
|
||||
}
|
||||
if (!renderPass->pendingCommands) VKRenderer_BeginRenderPass(surface);
|
||||
// Validate current composite.
|
||||
if (oldComposite != context->composite) {
|
||||
J2dTraceLn2(J2D_TRACE_VERBOSE, "VKRenderer_Validate: updating composite, old=%d, new=%d", oldComposite, context->composite);
|
||||
// Reset the pipeline.
|
||||
renderPass->state.shader = NO_SHADER;
|
||||
}
|
||||
J2dTraceLn2(J2D_TRACE_VERBOSE, "VKRenderer_Validate: updating composite, old=%d, new=%d", oldComposite, context->composite);
|
||||
// Reset the pipeline.
|
||||
renderPass->currentPipeline = NO_PIPELINE;
|
||||
}
|
||||
|
||||
// Validate current pipeline.
|
||||
if (renderPass->state.shader != shader || renderPass->state.topology != topology) {
|
||||
if (renderPass->currentPipeline != pipeline) {
|
||||
J2dTraceLn2(J2D_TRACE_VERBOSE, "VKRenderer_Validate: updating pipeline, old=%d, new=%d",
|
||||
renderPass->state.shader, shader);
|
||||
renderPass->currentPipeline, pipeline);
|
||||
VKRenderer_FlushDraw(surface);
|
||||
VkCommandBuffer cb = renderPass->commandBuffer;
|
||||
renderPass->state.shader = shader;
|
||||
renderPass->state.topology = topology;
|
||||
renderPass->currentPipeline = pipeline;
|
||||
surface->device->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
VKPipelines_GetPipeline(renderPass->context, renderPass->state));
|
||||
VKPipelines_GetPipeline(renderPass->context, context->composite, pipeline));
|
||||
renderPass->vertexBufferWriting.bound = VK_FALSE;
|
||||
renderPass->maskFillBufferWriting.bound = VK_FALSE;
|
||||
}
|
||||
return VK_TRUE;
|
||||
}
|
||||
|
||||
// Drawing operations.
|
||||
|
||||
void VKRenderer_RenderRect(const VKRenderingContext* context, VkBool32 fill,
|
||||
jint x, jint y, jint w, jint h) {
|
||||
VKRenderer_RenderParallelogram(context, fill, (float) x, (float) y, (float) w, 0, 0, (float) h);
|
||||
void VKRenderer_RenderRect(VKRenderingContext* context, VKPipeline pipeline, jint x, jint y, jint w, jint h) {
|
||||
VKRenderer_RenderParallelogram(context, pipeline, (float) x, (float) y, (float) w, 0, 0, (float) h);
|
||||
}
|
||||
|
||||
void VKRenderer_RenderParallelogram(const VKRenderingContext* context, VkBool32 fill,
|
||||
void VKRenderer_RenderParallelogram(VKRenderingContext* context, VKPipeline pipeline,
|
||||
jfloat x11, jfloat y11,
|
||||
jfloat dx21, jfloat dy21,
|
||||
jfloat dx12, jfloat dy12) {
|
||||
if (!VKRenderer_Validate(context, SHADER_COLOR,
|
||||
fill ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST : VK_PRIMITIVE_TOPOLOGY_LINE_LIST)) return; // Not ready.
|
||||
if (!VKRenderer_Validate(context, pipeline)) return; // Not ready.
|
||||
Color c = context->color;
|
||||
/* dx21
|
||||
* (p1)---------(p2) | (p1)------
|
||||
@@ -1176,23 +955,23 @@ void VKRenderer_RenderParallelogram(const VKRenderingContext* context, VkBool32
|
||||
VKColorVertex p4 = {x11 + dx12, y11 + dy12, c};
|
||||
|
||||
VKColorVertex* vs;
|
||||
VK_DRAW(vs, context, fill ? 6 : 8);
|
||||
VK_DRAW(vs, context, pipeline == PIPELINE_DRAW_COLOR ? 8 : 6);
|
||||
uint32_t i = 0;
|
||||
vs[i++] = p1;
|
||||
vs[i++] = p2;
|
||||
vs[i++] = p3;
|
||||
vs[i++] = p4;
|
||||
vs[i++] = p1;
|
||||
if (!fill) {
|
||||
if (pipeline == PIPELINE_DRAW_COLOR) {
|
||||
vs[i++] = p4;
|
||||
vs[i++] = p2;
|
||||
}
|
||||
vs[i++] = p3;
|
||||
}
|
||||
|
||||
void VKRenderer_FillSpans(const VKRenderingContext* context, jint spanCount, jint *spans) {
|
||||
void VKRenderer_FillSpans(VKRenderingContext* context, jint spanCount, jint *spans) {
|
||||
if (spanCount == 0) return;
|
||||
if (!VKRenderer_Validate(context, SHADER_COLOR, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)) return; // Not ready.
|
||||
if (!VKRenderer_Validate(context, PIPELINE_FILL_COLOR)) return; // Not ready.
|
||||
Color c = context->color;
|
||||
|
||||
jfloat x1 = (float)*(spans++);
|
||||
@@ -1219,9 +998,9 @@ void VKRenderer_FillSpans(const VKRenderingContext* context, jint spanCount, jin
|
||||
}
|
||||
}
|
||||
|
||||
void VKRenderer_TextureRender(const VKRenderingContext* context, VKImage *destImage, VKImage *srcImage,
|
||||
void VKRenderer_TextureRender(VKRenderingContext* context, VKImage *destImage, VKImage *srcImage,
|
||||
VkBuffer vertexBuffer, uint32_t vertexNum) {
|
||||
if (!VKRenderer_Validate(context, SHADER_BLIT, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)) return; // Not ready.
|
||||
if (!VKRenderer_Validate(context, PIPELINE_BLIT)) return; // Not ready.
|
||||
VKSDOps* surface = (VKSDOps*)context->surface;
|
||||
VKRenderPass* renderPass = surface->renderPass;
|
||||
VkCommandBuffer cb = renderPass->commandBuffer;
|
||||
@@ -1281,37 +1060,4 @@ void VKRenderer_TextureRender(const VKRenderingContext* context, VKImage *destIm
|
||||
device->vkCmdDraw(cb, vertexNum, 1, 0, 0);
|
||||
}
|
||||
|
||||
void VKRenderer_MaskFill(const VKRenderingContext* context, jint x, jint y, jint w, jint h,
|
||||
jint maskoff, jint maskscan, jint masklen, uint8_t* mask) {
|
||||
if (!VKRenderer_Validate(context, SHADER_MASK_FILL_COLOR, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)) return; // Not ready.
|
||||
// maskoff is the offset from the beginning of mask,
|
||||
// it's the same as x and y offset within a tile (maskoff % maskscan, maskoff / maskscan).
|
||||
// maskscan is the number of bytes in a row/
|
||||
// masklen is the size of the whole mask tile, it may be way bigger, than number of actually needed bytes.
|
||||
uint32_t byteCount = maskscan * h;
|
||||
if (mask == NULL) {
|
||||
maskscan = 0;
|
||||
byteCount = 1;
|
||||
}
|
||||
BufferWritingState maskState = VKRenderer_AllocateMaskFillBytes(context, byteCount);
|
||||
if (mask != NULL) {
|
||||
memcpy(maskState.data, mask + maskoff, byteCount);
|
||||
} else {
|
||||
// Special case, fully opaque mask
|
||||
*((char *)maskState.data) = 0xFF;
|
||||
}
|
||||
|
||||
VKMaskFillColorVertex* vs;
|
||||
VK_DRAW(vs, context, 6);
|
||||
Color c = context->color;
|
||||
int offset = (int) maskState.offset;
|
||||
VKMaskFillColorVertex p1 = {x, y, offset, maskscan, c};
|
||||
VKMaskFillColorVertex p2 = {x + w, y, offset, maskscan, c};
|
||||
VKMaskFillColorVertex p3 = {x + w, y + h, offset, maskscan, c};
|
||||
VKMaskFillColorVertex p4 = {x, y + h, offset, maskscan, c};
|
||||
// Always keep p1 as provoking vertex for correct origin calculation in vertex shader.
|
||||
vs[0] = p1; vs[1] = p3; vs[2] = p2;
|
||||
vs[3] = p1; vs[4] = p3; vs[5] = p4;
|
||||
}
|
||||
|
||||
#endif /* !HEADLESS */
|
||||
|
||||
@@ -33,14 +33,12 @@
|
||||
struct VKRenderingContext {
|
||||
VKSDOps* surface;
|
||||
VKTransform transform;
|
||||
VkRect2D clipRect;
|
||||
Color color;
|
||||
VKCompositeMode composite;
|
||||
// Extra alpha is not used when painting with plain color,
|
||||
// in this case color.a already includes it.
|
||||
float extraAlpha;
|
||||
uint64_t clipModCount; // Used to track changes to the clip.
|
||||
VkRect2D clipRect;
|
||||
ARRAY(VKIntVertex) clipSpanVertices;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -54,7 +52,7 @@ VKRenderer* VKRenderer_Create(VKDevice* device);
|
||||
/**
|
||||
* Setup pipeline for drawing. Returns FALSE if surface is not yet ready for drawing.
|
||||
*/
|
||||
VkBool32 VKRenderer_Validate(const VKRenderingContext* context, VKShader shader, VkPrimitiveTopology topology);
|
||||
VkBool32 VKRenderer_Validate(VKRenderingContext* context, VKPipeline pipeline);
|
||||
|
||||
/**
|
||||
* Record commands into primary command buffer (outside of a render pass).
|
||||
@@ -104,23 +102,19 @@ void VKRenderer_FlushRenderPass(VKSDOps* surface);
|
||||
|
||||
// Blit operations.
|
||||
|
||||
void VKRenderer_TextureRender(const VKRenderingContext* context,
|
||||
void VKRenderer_TextureRender(VKRenderingContext* context,
|
||||
VKImage *destImage, VKImage *srcImage,
|
||||
VkBuffer vertexBuffer, uint32_t vertexNum);
|
||||
|
||||
// Drawing operations.
|
||||
|
||||
void VKRenderer_RenderRect(const VKRenderingContext* context, VkBool32 fill,
|
||||
jint x, jint y, jint w, jint h);
|
||||
void VKRenderer_RenderRect(VKRenderingContext* context, VKPipeline pipeline, jint x, jint y, jint w, jint h);
|
||||
|
||||
void VKRenderer_RenderParallelogram(const VKRenderingContext* context, VkBool32 fill,
|
||||
void VKRenderer_RenderParallelogram(VKRenderingContext* context, VKPipeline pipeline,
|
||||
jfloat x11, jfloat y11,
|
||||
jfloat dx21, jfloat dy21,
|
||||
jfloat dx12, jfloat dy12);
|
||||
|
||||
void VKRenderer_FillSpans(const VKRenderingContext* context, jint spanCount, jint *spans);
|
||||
|
||||
void VKRenderer_MaskFill(const VKRenderingContext* context,
|
||||
jint x, jint y, jint w, jint h, jint maskoff, jint maskscan, jint masklen, uint8_t* mask);
|
||||
void VKRenderer_FillSpans(VKRenderingContext* context, jint spanCount, jint *spans);
|
||||
|
||||
#endif //VKRenderer_h_Included
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, JetBrains s.r.o.. All rights reserved.
|
||||
* 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
|
||||
@@ -40,10 +40,9 @@ static void VKSD_ResetImageSurface(VKSDOps* vksdo) {
|
||||
// DestroyRenderPass also waits while the surface resources are being used by device.
|
||||
VKRenderer_DestroyRenderPass(vksdo);
|
||||
|
||||
if (vksdo->device != NULL) {
|
||||
VKImage_Destroy(vksdo->device, vksdo->stencil);
|
||||
if (vksdo->device != NULL && vksdo->image != NULL) {
|
||||
VKImage_Destroy(vksdo->device, vksdo->image);
|
||||
vksdo->image = vksdo->stencil = NULL;
|
||||
vksdo->image = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +90,7 @@ VkBool32 VKSD_ConfigureImageSurface(VKSDOps* vksdo) {
|
||||
|
||||
VKImage* image = VKImage_Create(device, vksdo->requestedExtent.width, vksdo->requestedExtent.height,
|
||||
0, format, VK_IMAGE_TILING_OPTIMAL,
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
VK_SAMPLE_COUNT_1_BIT, VKSD_FindImageSurfaceMemoryType);
|
||||
VK_RUNTIME_ASSERT(image);
|
||||
VKSD_ResetImageSurface(vksdo);
|
||||
@@ -102,25 +100,6 @@ VkBool32 VKSD_ConfigureImageSurface(VKSDOps* vksdo) {
|
||||
return vksdo->image != NULL;
|
||||
}
|
||||
|
||||
VkBool32 VKSD_ConfigureImageSurfaceStencil(VKSDOps* vksdo) {
|
||||
// Check that image is ready.
|
||||
if (vksdo->image == NULL) {
|
||||
J2dRlsTraceLn1(J2D_TRACE_WARNING, "VKSD_ConfigureImageSurfaceStencil(%p): image is not ready", vksdo);
|
||||
return VK_FALSE;
|
||||
}
|
||||
// Initialize stencil image.
|
||||
if (vksdo->stencil == NULL) {
|
||||
vksdo->stencil = VKImage_Create(vksdo->device, vksdo->image->extent.width, vksdo->image->extent.height,
|
||||
0, VK_FORMAT_S8_UINT, VK_IMAGE_TILING_OPTIMAL,
|
||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
|
||||
VK_SAMPLE_COUNT_1_BIT, VKSD_FindImageSurfaceMemoryType);
|
||||
VK_RUNTIME_ASSERT(vksdo->stencil);
|
||||
J2dRlsTraceLn3(J2D_TRACE_INFO, "VKSD_ConfigureImageSurfaceStencil(%p): stencil image updated %dx%d",
|
||||
vksdo, vksdo->stencil->extent.width, vksdo->stencil->extent.height);
|
||||
}
|
||||
return vksdo->stencil != NULL;
|
||||
}
|
||||
|
||||
VkBool32 VKSD_ConfigureWindowSurface(VKWinSDOps* vkwinsdo) {
|
||||
// Check that image is ready.
|
||||
if (vkwinsdo->vksdOps.image == NULL) {
|
||||
@@ -294,23 +273,4 @@ VkBool32 VKSD_ConfigureWindowSurface(VKWinSDOps* vkwinsdo) {
|
||||
return VK_TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_java2d_vulkan_VKOffScreenSurfaceData
|
||||
* Method: initOps
|
||||
* Signature: (II)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKOffScreenSurfaceData_initOps
|
||||
(JNIEnv *env, jobject vksd, jint width, jint height) {
|
||||
VKSDOps * sd = (VKSDOps*)SurfaceData_InitOps(env, vksd, sizeof(VKSDOps));
|
||||
J2dTraceLn1(J2D_TRACE_VERBOSE, "VKOffScreenSurfaceData_initOps(%p)", sd);
|
||||
if (sd == NULL) {
|
||||
JNU_ThrowOutOfMemoryError(env, "Initialization of SurfaceData failed.");
|
||||
return;
|
||||
}
|
||||
sd->drawableType = VKSD_RT_TEXTURE;
|
||||
sd->background = VKUtil_DecodeJavaColor(0);
|
||||
VKSD_ResetSurface(sd);
|
||||
VKRenderer_ConfigureSurface(sd, (VkExtent2D){width, height});
|
||||
}
|
||||
|
||||
#endif /* !HEADLESS */
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
|
||||
#include "SurfaceData.h"
|
||||
#include "sun_java2d_pipe_hw_AccelSurface.h"
|
||||
#include "VKUtil.h"
|
||||
#include "VKTypes.h"
|
||||
#include "VKRenderer.h"
|
||||
|
||||
@@ -51,7 +50,6 @@ struct VKSDOps {
|
||||
jint drawableType;
|
||||
VKDevice* device;
|
||||
VKImage* image;
|
||||
VKImage* stencil;
|
||||
|
||||
Color background;
|
||||
VkExtent2D requestedExtent;
|
||||
@@ -68,7 +66,7 @@ struct VKWinSDOps {
|
||||
VKSDOps vksdOps;
|
||||
VkSurfaceKHR surface;
|
||||
VkSwapchainKHR swapchain;
|
||||
ARRAY(VkImage) swapchainImages;
|
||||
VkImage* swapchainImages;
|
||||
VKDevice* swapchainDevice;
|
||||
VkExtent2D swapchainExtent;
|
||||
VKWinSD_SurfaceResizeCallback resizeCallback;
|
||||
@@ -86,12 +84,6 @@ void VKSD_ResetSurface(VKSDOps* vksdo);
|
||||
*/
|
||||
VkBool32 VKSD_ConfigureImageSurface(VKSDOps* vksdo);
|
||||
|
||||
/**
|
||||
* [Re]configure stencil attachment of the image surface.
|
||||
* VKSD_ConfigureImageSurface must have been called before.
|
||||
*/
|
||||
VkBool32 VKSD_ConfigureImageSurfaceStencil(VKSDOps* vksdo);
|
||||
|
||||
/**
|
||||
* Configure window surface. This [re]initializes the swapchain.
|
||||
* VKSD_ConfigureImageSurface must have been called before.
|
||||
|
||||
@@ -56,7 +56,6 @@ typedef struct VKRenderPass VKRenderPass;
|
||||
typedef struct VKRenderingContext VKRenderingContext;
|
||||
typedef struct VKPipelineContext VKPipelineContext;
|
||||
typedef struct VKRenderPassContext VKRenderPassContext;
|
||||
typedef struct VKTexelBuffer VKTexelBuffer;
|
||||
typedef struct VKBuffer VKBuffer;
|
||||
typedef struct VKImage VKImage;
|
||||
typedef struct VKSDOps VKSDOps;
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
#include "jni_util.h"
|
||||
#include "VKTypes.h"
|
||||
|
||||
#define C_ARRAY_UTIL_ALLOCATION_FAILED() VK_FATAL_ERROR("CArrayUtil allocation failed")
|
||||
#include "CArrayUtil.h"
|
||||
|
||||
// VK_DEBUG_RANDOM may be used to randomly tune some parameters and turn off some features,
|
||||
// which would allow to cover wider range of scenarios and catch configuration-specific errors early.
|
||||
// In debug builds it returns 1 with approximately CHANCE_PERCENT chance, on release builds it is always 0.
|
||||
@@ -66,9 +69,6 @@ inline VkBool32 VKUtil_CheckError(VkResult result, const char* errorMessage) {
|
||||
#define VK_UNHANDLED_ERROR() VK_FATAL_ERROR("Unhandled Vulkan error")
|
||||
#define VK_RUNTIME_ASSERT(...) if (!(__VA_ARGS__)) VK_FATAL_ERROR("Vulkan assertion failed: " #__VA_ARGS__)
|
||||
|
||||
#define C_ARRAY_UTIL_ALLOCATION_FAILED() VK_FATAL_ERROR("CArrayUtil allocation failed")
|
||||
#include "CArrayUtil.h"
|
||||
|
||||
typedef enum {
|
||||
FORMAT_ALIAS_ORIGINAL = 0,
|
||||
FORMAT_ALIAS_UNORM = 1,
|
||||
|
||||
@@ -470,8 +470,6 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
|
||||
|
||||
private int cachedScreenResolution = 72;
|
||||
|
||||
private boolean dirtyDevices = false;
|
||||
|
||||
/**
|
||||
* XSETTINGS for the default screen.
|
||||
* <p>
|
||||
@@ -643,6 +641,28 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
|
||||
});
|
||||
// Detect display mode changes
|
||||
XlibWrapper.XSelectInput(XToolkit.getDisplay(), XToolkit.getDefaultRootWindow(), XConstants.StructureNotifyMask);
|
||||
XToolkit.addEventDispatcher(XToolkit.getDefaultRootWindow(), new XEventDispatcher() {
|
||||
@Override
|
||||
public void dispatchEvent(XEvent ev) {
|
||||
if (ev.get_type() == XConstants.ConfigureNotify ||
|
||||
(checkDesktopGeometry && ev.get_type() == XConstants.PropertyNotify &&
|
||||
ev.get_xproperty().get_atom() == XWM.XA_NET_DESKTOP_GEOMETRY.getAtom())) // possible DPI change
|
||||
{
|
||||
awtUnlock();
|
||||
try {
|
||||
((X11GraphicsEnvironment)GraphicsEnvironment.
|
||||
getLocalGraphicsEnvironment()).rebuildDevices();
|
||||
} finally {
|
||||
awtLock();
|
||||
}
|
||||
} else {
|
||||
final XAtom XA_NET_WORKAREA = XAtom.get("_NET_WORKAREA");
|
||||
final boolean rootWindowWorkareaResized = (ev.get_type() == XConstants.PropertyNotify
|
||||
&& ev.get_xproperty().get_atom() == XA_NET_WORKAREA.getAtom());
|
||||
if (rootWindowWorkareaResized) resetScreenInsetsCache();
|
||||
}
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
awtUnlock();
|
||||
}
|
||||
@@ -877,29 +897,6 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
|
||||
private void dispatchEvent(XEvent ev) {
|
||||
final XAnyEvent xany = ev.get_xany();
|
||||
|
||||
if (xany.get_window() == getDefaultRootWindow()) {
|
||||
if (ev.get_type() == XConstants.ConfigureNotify ||
|
||||
(checkDesktopGeometry && ev.get_type() == XConstants.PropertyNotify &&
|
||||
ev.get_xproperty().get_atom() == XWM.XA_NET_DESKTOP_GEOMETRY.getAtom())) // possible DPI change
|
||||
{
|
||||
dirtyDevices = true;
|
||||
} else {
|
||||
final XAtom XA_NET_WORKAREA = XAtom.get("_NET_WORKAREA");
|
||||
final boolean rootWindowWorkareaResized = (ev.get_type() == XConstants.PropertyNotify
|
||||
&& ev.get_xproperty().get_atom() == XA_NET_WORKAREA.getAtom());
|
||||
if (rootWindowWorkareaResized) resetScreenInsetsCache();
|
||||
}
|
||||
} else if (dirtyDevices) {
|
||||
awtUnlock();
|
||||
try {
|
||||
((X11GraphicsEnvironment)GraphicsEnvironment.
|
||||
getLocalGraphicsEnvironment()).rebuildDevices();
|
||||
dirtyDevices = false;
|
||||
} finally {
|
||||
awtLock();
|
||||
}
|
||||
}
|
||||
|
||||
XBaseWindow baseWindow = windowToXWindow(xany.get_window());
|
||||
if (baseWindow != null && (ev.get_type() == XConstants.MotionNotify
|
||||
|| ev.get_type() == XConstants.EnterNotify
|
||||
|
||||
@@ -36,8 +36,6 @@ import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -225,24 +223,15 @@ public final class WLClipboard extends SunClipboard {
|
||||
}
|
||||
|
||||
if (flavor != null) {
|
||||
byte[] bytes = wlDataTransferer.translateTransferable(contents, flavor, targetFormat);
|
||||
if (bytes == null) return;
|
||||
FileDescriptor javaDestFD = new FileDescriptor();
|
||||
jdk.internal.access.SharedSecrets.getJavaIOFileDescriptorAccess().set(javaDestFD, destFD);
|
||||
try (var out = new FileOutputStream(javaDestFD)) {
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: about to write " + bytes.length + " bytes to " + out);
|
||||
}
|
||||
|
||||
FileChannel ch = out.getChannel();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
// Need to write with retries because when a pipe is in the non-blocking mode
|
||||
// writing more than its capacity (usually 16 pages or 64K) fails with EAGAIN.
|
||||
// Since we receive destFD from the Wayland sever, we can't assume it
|
||||
// to always be in the blocking mode.
|
||||
while (buffer.hasRemaining()) {
|
||||
ch.write(buffer);
|
||||
try (var out = new FileOutputStream(javaDestFD)) {
|
||||
byte[] bytes = wlDataTransferer.translateTransferable(contents, flavor, targetFormat);
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: about to write " + (bytes != null ? bytes.length : 0) + " bytes to " + out);
|
||||
}
|
||||
out.write(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,19 +33,15 @@ import sun.awt.SunToolkit;
|
||||
import sun.awt.event.IgnorePaintEvent;
|
||||
import sun.awt.image.SunVolatileImage;
|
||||
import sun.java2d.SunGraphics2D;
|
||||
import sun.java2d.SunGraphicsEnvironment;
|
||||
import sun.java2d.SurfaceData;
|
||||
import sun.java2d.pipe.Region;
|
||||
import sun.java2d.wl.WLSurfaceDataExt;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
import sun.util.logging.PlatformLogger.Level;
|
||||
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.RootPaneContainer;
|
||||
import javax.swing.SwingUtilities;
|
||||
import java.awt.AWTEvent;
|
||||
import java.awt.AWTException;
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.BufferCapabilities;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
@@ -61,7 +57,6 @@ import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.SystemColor;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
@@ -74,7 +69,6 @@ import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.awt.event.PaintEvent;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.VolatileImage;
|
||||
import java.awt.peer.ComponentPeer;
|
||||
@@ -91,8 +85,6 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
private static final int MINIMUM_WIDTH = 1;
|
||||
private static final int MINIMUM_HEIGHT = 1;
|
||||
|
||||
public static final String WINDOW_CORNER_RADIUS = "apple.awt.windowCornerRadius";
|
||||
|
||||
private long nativePtr; // accessed under AWT lock
|
||||
private volatile boolean surfaceAssigned = false;
|
||||
protected final Component target;
|
||||
@@ -108,7 +100,6 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
boolean visible = false;
|
||||
|
||||
private final Object dataLock = new Object();
|
||||
private boolean isFullscreen = false; // protected by dataLock
|
||||
boolean sizeIsBeingConfigured = false; // protected by dataLock
|
||||
int displayScale; // protected by dataLock
|
||||
double effectiveScale; // protected by dataLock
|
||||
@@ -116,13 +107,6 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
boolean repositionPopup = false; // protected by dataLock
|
||||
boolean resizePending = false; // protected by dataLock
|
||||
|
||||
private WLRoundedCornersManager.RoundedCornerKind roundedCornerKind = WLRoundedCornersManager.RoundedCornerKind.DEFAULT; // guarded by dataLock
|
||||
private Path2D.Double topLeftMask; // guarded by dataLock
|
||||
private Path2D.Double topRightMask; // guarded by dataLock
|
||||
private Path2D.Double bottomLeftMask; // guarded by dataLock
|
||||
private Path2D.Double bottomRightMask; // guarded by dataLock
|
||||
private SunGraphics2D graphics;// guarded by dataLock
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
@@ -134,7 +118,7 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
this.target = target;
|
||||
this.background = target.getBackground();
|
||||
Dimension size = constrainSize(target.getBounds().getSize());
|
||||
final WLGraphicsConfig config = (WLGraphicsConfig) target.getGraphicsConfiguration();
|
||||
final WLGraphicsConfig config = (WLGraphicsConfig)target.getGraphicsConfiguration();
|
||||
displayScale = config.getDisplayScale();
|
||||
effectiveScale = config.getEffectiveScale();
|
||||
wlSize.deriveFromJavaSize(size.width, size.height);
|
||||
@@ -144,16 +128,6 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
if (log.isLoggable(Level.FINE)) {
|
||||
log.fine("WLComponentPeer: target=" + target + " with size=" + wlSize);
|
||||
}
|
||||
|
||||
if (target instanceof RootPaneContainer) {
|
||||
JRootPane rootpane = ((RootPaneContainer)target).getRootPane();
|
||||
if (rootpane != null) {
|
||||
Object roundedCornerKind = rootpane.getClientProperty(WINDOW_CORNER_RADIUS);
|
||||
if (roundedCornerKind != null) {
|
||||
setRoundedCornerKind(WLRoundedCornersManager.roundedCornerKindFrom(roundedCornerKind));
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// setup parent window for target
|
||||
}
|
||||
@@ -204,12 +178,6 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
return surfaceAssigned;
|
||||
}
|
||||
|
||||
boolean isFullscreen() {
|
||||
synchronized (dataLock) {
|
||||
return isFullscreen;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reparent(ContainerPeer newContainer) {
|
||||
throw new UnsupportedOperationException();
|
||||
@@ -284,7 +252,7 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
|
||||
private static Window getToplevelFor(Component component) {
|
||||
Container container = component instanceof Container c ? c : component.getParent();
|
||||
for (Container p = container; p != null; p = p.getParent()) {
|
||||
for(Container p = container; p != null; p = p.getParent()) {
|
||||
if (p instanceof Window window && !isWlPopup(window)) {
|
||||
return window;
|
||||
}
|
||||
@@ -328,7 +296,7 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
protected void wlSetVisible(boolean v) {
|
||||
synchronized (getStateLock()) {
|
||||
if (this.visible == v) return;
|
||||
|
||||
|
||||
this.visible = v;
|
||||
}
|
||||
if (v) {
|
||||
@@ -394,11 +362,10 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
private boolean targetIsModal() {
|
||||
return target instanceof Dialog dialog
|
||||
&& (dialog.getModalityType() == Dialog.ModalityType.APPLICATION_MODAL
|
||||
|| dialog.getModalityType() == Dialog.ModalityType.TOOLKIT_MODAL);
|
||||
|| dialog.getModalityType() == Dialog.ModalityType.TOOLKIT_MODAL);
|
||||
}
|
||||
|
||||
void updateSurfaceData() {
|
||||
resetCornerMasks();
|
||||
SurfaceData.convertTo(WLSurfaceDataExt.class, surfaceData).revalidate(
|
||||
getBufferWidth(), getBufferHeight(), getDisplayScale());
|
||||
}
|
||||
@@ -410,7 +377,7 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
// which may result in visual artifacts.
|
||||
int surfaceWidth = wlSize.getSurfaceWidth();
|
||||
int surfaceHeight = wlSize.getSurfaceHeight();
|
||||
Dimension surfaceMinSize = javaUnitsToSurfaceSize(constrainSize(target.getMinimumSize()));
|
||||
Dimension surfaceMinSize = javaUnitsToSurfaceSize(constrainSize(getMinimumSize()));
|
||||
Dimension maxSize = target.isMaximumSizeSet() ? target.getMaximumSize() : null;
|
||||
Dimension surfaceMaxSize = maxSize != null ? javaUnitsToSurfaceSize(constrainSize(maxSize)) : null;
|
||||
|
||||
@@ -501,111 +468,12 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
* the displaying buffer is ready to accept new data.
|
||||
*/
|
||||
public void commitToServer() {
|
||||
if (roundedCornersRequested() && canPaintRoundedCorners()) {
|
||||
paintRoundCorners();
|
||||
}
|
||||
performLocked(() -> {
|
||||
if (getWLSurface(nativePtr) != 0) {
|
||||
SurfaceData.convertTo(WLSurfaceDataExt.class, surfaceData).commit();
|
||||
}
|
||||
});
|
||||
((WLToolkit) Toolkit.getDefaultToolkit()).flush();
|
||||
}
|
||||
|
||||
private boolean canPaintRoundedCorners() {
|
||||
int roundedCornerSize = WLRoundedCornersManager.roundCornerRadiusFor(roundedCornerKind);
|
||||
// Note: You would normally get a transparency-capable color model when using
|
||||
// the default graphics configuration
|
||||
return surfaceData.getColorModel().hasAlpha()
|
||||
&& getWidth() > roundedCornerSize * 2
|
||||
&& getHeight() > roundedCornerSize * 2;
|
||||
}
|
||||
|
||||
protected boolean roundedCornersRequested() {
|
||||
synchronized (dataLock) {
|
||||
return roundedCornerKind == WLRoundedCornersManager.RoundedCornerKind.FULL
|
||||
|| roundedCornerKind == WLRoundedCornersManager.RoundedCornerKind.SMALL;
|
||||
}
|
||||
}
|
||||
|
||||
WLRoundedCornersManager.RoundedCornerKind getRoundedCornerKind() {
|
||||
synchronized (dataLock) {
|
||||
return roundedCornerKind;
|
||||
}
|
||||
}
|
||||
|
||||
void setRoundedCornerKind(WLRoundedCornersManager.RoundedCornerKind kind) {
|
||||
synchronized (dataLock) {
|
||||
if (roundedCornerKind != kind) {
|
||||
roundedCornerKind = kind;
|
||||
resetCornerMasks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createCornerMasks() {
|
||||
if (graphics == null) {
|
||||
graphics = new SunGraphics2D(surfaceData, Color.WHITE, Color.BLACK, null);
|
||||
graphics.setComposite(AlphaComposite.Clear);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
}
|
||||
|
||||
if (topLeftMask == null) {
|
||||
createCornerMasks(WLRoundedCornersManager.roundCornerRadiusFor(roundedCornerKind));
|
||||
}
|
||||
}
|
||||
|
||||
private void resetCornerMasks() {
|
||||
synchronized (dataLock) {
|
||||
if (graphics != null) graphics.dispose();
|
||||
graphics = null;
|
||||
topLeftMask = null;
|
||||
topRightMask = null;
|
||||
bottomLeftMask = null;
|
||||
bottomRightMask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void createCornerMasks(int size) {
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
|
||||
topLeftMask = new Path2D.Double();
|
||||
topLeftMask.moveTo(0, 0);
|
||||
topLeftMask.lineTo(size, 0);
|
||||
topLeftMask.quadTo(0, 0, 0, size);
|
||||
topLeftMask.closePath();
|
||||
|
||||
topRightMask = new Path2D.Double();
|
||||
topRightMask.moveTo(w - size, 0);
|
||||
topRightMask.quadTo(w, 0, w, size);
|
||||
topRightMask.lineTo(w, 0);
|
||||
topRightMask.closePath();
|
||||
|
||||
bottomLeftMask = new Path2D.Double();
|
||||
bottomLeftMask.moveTo(0, h - size);
|
||||
bottomLeftMask.quadTo(0, h, size, h);
|
||||
bottomLeftMask.lineTo(0, h);
|
||||
bottomLeftMask.closePath();
|
||||
|
||||
bottomRightMask = new Path2D.Double();
|
||||
bottomRightMask.moveTo(w - size, h);
|
||||
bottomRightMask.quadTo(w, h, w, h - size);
|
||||
bottomRightMask.lineTo(w, h);
|
||||
bottomRightMask.closePath();
|
||||
}
|
||||
|
||||
private void paintRoundCorners() {
|
||||
synchronized (dataLock) {
|
||||
createCornerMasks();
|
||||
|
||||
graphics.fill(topLeftMask);
|
||||
graphics.fill(topRightMask);
|
||||
graphics.fill(bottomLeftMask);
|
||||
graphics.fill(bottomRightMask);
|
||||
}
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
}
|
||||
|
||||
public Component getTarget() {
|
||||
@@ -895,7 +763,7 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
}
|
||||
|
||||
public Dimension getMinimumSize() {
|
||||
return new Dimension(1, 1);
|
||||
return target.getMinimumSize();
|
||||
}
|
||||
|
||||
void showWindowMenu(long serial, int x, int y) {
|
||||
@@ -956,7 +824,6 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
resetCornerMasks();
|
||||
performLocked(() -> {
|
||||
SurfaceData oldData = surfaceData;
|
||||
surfaceData = null;
|
||||
@@ -1640,27 +1507,13 @@ public class WLComponentPeer implements ComponentPeer {
|
||||
return new Dimension(javaUnitsToSurfaceSize(d.width), javaUnitsToSurfaceSize(d.height));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a point in the device (screen) space into coordinates on this surface
|
||||
*/
|
||||
Point convertPontFromDeviceSpace(int x, int y) {
|
||||
Point userLoc = getLocationOnScreen();
|
||||
Point topLeft = SunGraphicsEnvironment.toDeviceSpace(getGraphicsConfiguration(), userLoc.x, userLoc.y, 0, 0).getLocation();
|
||||
return new Point(x - topLeft.x, y - topLeft.y);
|
||||
}
|
||||
|
||||
void notifyConfigured(int newSurfaceX, int newSurfaceY, int newSurfaceWidth, int newSurfaceHeight,
|
||||
boolean active, boolean maximized, boolean fullscreen) {
|
||||
void notifyConfigured(int newSurfaceX, int newSurfaceY, int newSurfaceWidth, int newSurfaceHeight, boolean active, boolean maximized) {
|
||||
// NB: The width and height, as well as X and Y arguments, specify the size and the location
|
||||
// of the window in surface-local coordinates.
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine(String.format("%s configured to %dx%d surface units", this, newSurfaceWidth, newSurfaceHeight));
|
||||
}
|
||||
|
||||
synchronized (dataLock) {
|
||||
isFullscreen = fullscreen;
|
||||
}
|
||||
|
||||
boolean isWlPopup = targetIsWlPopup();
|
||||
boolean acceptNewLocation = !popupNeedsReposition();
|
||||
if (isWlPopup && acceptNewLocation) { // Only popups provide (relative) location
|
||||
|
||||
@@ -110,10 +110,8 @@ public abstract class WLDecoratedPeer extends WLWindowPeer {
|
||||
}
|
||||
|
||||
@Override
|
||||
void notifyConfigured(int newSurfaceX, int newSurfaceY, int newSurfaceWidth, int newSurfaceHeight,
|
||||
boolean active, boolean maximized, boolean fullscreen) {
|
||||
super.notifyConfigured(newSurfaceX, newSurfaceY, newSurfaceWidth, newSurfaceHeight,
|
||||
active, maximized, fullscreen);
|
||||
void notifyConfigured(int newSurfaceX, int newSurfaceY, int newSurfaceWidth, int newSurfaceHeight, boolean active, boolean maximized) {
|
||||
super.notifyConfigured(newSurfaceX, newSurfaceY, newSurfaceWidth, newSurfaceHeight, active, maximized);
|
||||
decoration.setActive(active);
|
||||
}
|
||||
|
||||
|
||||
@@ -215,21 +215,8 @@ public class WLFrameDecoration {
|
||||
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
|
||||
g.setColor(getBackgroundColor(active));
|
||||
if (g.getDeviceConfiguration().isTranslucencyCapable()
|
||||
&& peer.getRoundedCornerKind() == WLRoundedCornersManager.RoundedCornerKind.DEFAULT
|
||||
&& peer.getState() != Frame.MAXIMIZED_BOTH
|
||||
&& !peer.isFullscreen()) {
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
Composite originalComposite = g.getComposite();
|
||||
g.setComposite(AlphaComposite.Clear);
|
||||
g.fillRect(0, 0, width, HEIGHT);
|
||||
g.setComposite(originalComposite);
|
||||
int radius = WLRoundedCornersManager.roundCornerRadiusFor(WLRoundedCornersManager.RoundedCornerKind.DEFAULT);
|
||||
g.fillRoundRect(0, 0, width, HEIGHT + radius + 1, radius, radius);
|
||||
} else {
|
||||
g.fillRect(0, 0, width, HEIGHT);
|
||||
}
|
||||
g.fillRect(0, 0, width, HEIGHT);
|
||||
|
||||
paintTitle(g, title, foregroundColor, width);
|
||||
|
||||
Point closeButtonCenter = getCloseButtonCenter();
|
||||
|
||||
@@ -154,15 +154,11 @@ public class WLFramePeer extends WLDecoratedPeer implements FramePeer {
|
||||
}
|
||||
|
||||
@Override
|
||||
void notifyConfigured(int newSurfaceX, int newSurfaceY, int newSurfaceWidth, int newSurfaceHeight,
|
||||
boolean active, boolean maximized, boolean fullscreen) {
|
||||
void notifyConfigured(int newSurfaceX, int newSurfaceY, int newSurfaceWidth, int newSurfaceHeight, boolean active, boolean maximized) {
|
||||
int widthBefore = getWidth();
|
||||
int heightBefore = getHeight();
|
||||
boolean notifyOfDecorationChange = isFullscreen() ^ fullscreen;
|
||||
|
||||
super.notifyConfigured(newSurfaceX, newSurfaceY, newSurfaceWidth, newSurfaceHeight,
|
||||
active, maximized, fullscreen);
|
||||
|
||||
super.notifyConfigured(newSurfaceX, newSurfaceY, newSurfaceWidth, newSurfaceHeight, active, maximized);
|
||||
|
||||
synchronized (getStateLock()) {
|
||||
int oldState = state;
|
||||
@@ -177,17 +173,12 @@ public class WLFramePeer extends WLDecoratedPeer implements FramePeer {
|
||||
performUnlocked(() -> target.setSize(widthBeforeMaximized, heightBeforeMaximized));
|
||||
}
|
||||
WLToolkit.postEvent(new WindowEvent(getFrame(), WindowEvent.WINDOW_STATE_CHANGED, oldState, state));
|
||||
notifyOfDecorationChange = true;
|
||||
notifyClientDecorationsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
if (notifyOfDecorationChange) {
|
||||
notifyClientDecorationsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private Frame getFrame() {
|
||||
return (Frame)target;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -112,10 +112,10 @@ public class WLGraphicsDevice extends GraphicsDevice {
|
||||
} else {
|
||||
// TODO: Actually, Wayland may support a lot more shared memory buffer configurations, need to
|
||||
// subscribe to the wl_shm:format event and get the list from there.
|
||||
newDefaultConfig = WLSMGraphicsConfig.getConfig(this, x, y, xLogical, yLogical, width, height, widthLogical, heightLogical, scale, true);
|
||||
newDefaultConfig = WLSMGraphicsConfig.getConfig(this, x, y, xLogical, yLogical, width, height, widthLogical, heightLogical, scale, false);
|
||||
newConfigs = new GraphicsConfiguration[2];
|
||||
newConfigs[0] = newDefaultConfig;
|
||||
newConfigs[1] = WLSMGraphicsConfig.getConfig(this, x, y, xLogical, yLogical, width, height, widthLogical, heightLogical, scale, false);
|
||||
newConfigs[1] = WLSMGraphicsConfig.getConfig(this, x, y, xLogical, yLogical, width, height, widthLogical, heightLogical, scale, true);
|
||||
}
|
||||
|
||||
configs = newConfigs;
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
package sun.awt.wl;
|
||||
|
||||
import java.awt.event.InputEvent;
|
||||
|
||||
/**
|
||||
* MouseEvent objects cannot be created directly from WLPointerEvent because they require
|
||||
* the information of certain events from the past like keyboard modifiers keys getting
|
||||
@@ -349,8 +347,4 @@ record WLInputState(WLPointerEvent eventWithSurface,
|
||||
public int getModifiers() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
public int getNonKeyboardModifiers() {
|
||||
return modifiers & ~(InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK | InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK);
|
||||
}
|
||||
}
|
||||
@@ -112,15 +112,49 @@ class WLKeyboard {
|
||||
}
|
||||
}
|
||||
|
||||
public static final int XKB_SHIFT_MASK = 1 << 0;
|
||||
public static final int XKB_CAPS_LOCK_MASK = 1 << 1;
|
||||
public static final int XKB_CTRL_MASK = 1 << 2;
|
||||
public static final int XKB_ALT_MASK = 1 << 3;
|
||||
public static final int XKB_NUM_LOCK_MASK = 1 << 4;
|
||||
public static final int XKB_MOD3_MASK = 1 << 5;
|
||||
public static final int XKB_META_MASK = 1 << 6;
|
||||
public static final int XKB_MOD5_MASK = 1 << 7;
|
||||
|
||||
private final KeyRepeatManager keyRepeatManager;
|
||||
|
||||
private native void initialize(KeyRepeatManager keyRepeatManager);
|
||||
|
||||
public native int getModifiers();
|
||||
public int getModifiers() {
|
||||
int result = 0;
|
||||
int mask = getXKBModifiersMask();
|
||||
|
||||
public native boolean isCapsLockPressed();
|
||||
if ((mask & XKB_SHIFT_MASK) != 0) {
|
||||
result |= InputEvent.SHIFT_DOWN_MASK;
|
||||
}
|
||||
|
||||
public native boolean isNumLockPressed();
|
||||
if ((mask & XKB_CTRL_MASK) != 0) {
|
||||
result |= InputEvent.CTRL_DOWN_MASK;
|
||||
}
|
||||
|
||||
if ((mask & XKB_ALT_MASK) != 0) {
|
||||
result |= InputEvent.ALT_DOWN_MASK;
|
||||
}
|
||||
|
||||
if ((mask & XKB_META_MASK) != 0) {
|
||||
result |= InputEvent.META_DOWN_MASK;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isCapsLockPressed() {
|
||||
return (getXKBModifiersMask() & XKB_CAPS_LOCK_MASK) != 0;
|
||||
}
|
||||
|
||||
public boolean isNumLockPressed() {
|
||||
return (getXKBModifiersMask() & XKB_NUM_LOCK_MASK) != 0;
|
||||
}
|
||||
|
||||
public void onLostFocus() {
|
||||
assert EventQueue.isDispatchThread();
|
||||
|
||||
@@ -121,17 +121,20 @@ public class WLRobotPeer implements RobotPeer {
|
||||
} else {
|
||||
// Can get pixels from the singular window's surface data,
|
||||
// not necessarily the true value that the user observes.
|
||||
return getRGBPixelsOfSingularWindow(bounds);
|
||||
Rectangle deviceBounds = wgc.getDefaultTransform().createTransformedShape(wgc.getBounds()).getBounds();
|
||||
|
||||
Rectangle grabBounds = new Rectangle(bounds.x - deviceBounds.x, bounds.y - deviceBounds.y,
|
||||
bounds.width, bounds.height);
|
||||
return getRGBPixelsOfSingularWindow(grabBounds);
|
||||
}
|
||||
}
|
||||
|
||||
private int getRGBPixelOfSingularWindow(int x, int y) {
|
||||
WLComponentPeer peer = WLToolkit.getSingularWindowPeer();
|
||||
Point loc = peer.convertPontFromDeviceSpace(x, y);
|
||||
WLToolkit.awtLock();
|
||||
try {
|
||||
checkPeerForPixelGrab(peer);
|
||||
return SurfaceData.convertTo(WLPixelGrabberExt.class, peer.surfaceData).getRGBPixelAt(loc.x, loc.y);
|
||||
return SurfaceData.convertTo(WLPixelGrabberExt.class, peer.surfaceData).getRGBPixelAt(x, y);
|
||||
} finally {
|
||||
WLToolkit.awtUnlock();
|
||||
}
|
||||
@@ -139,12 +142,10 @@ public class WLRobotPeer implements RobotPeer {
|
||||
|
||||
private int [] getRGBPixelsOfSingularWindow(Rectangle bounds) {
|
||||
WLComponentPeer peer = WLToolkit.getSingularWindowPeer();
|
||||
Point loc = peer.convertPontFromDeviceSpace(bounds.x, bounds.y);
|
||||
Rectangle adjustedBounds = new Rectangle(loc, bounds.getSize());
|
||||
WLToolkit.awtLock();
|
||||
try {
|
||||
checkPeerForPixelGrab(peer);
|
||||
return SurfaceData.convertTo(WLPixelGrabberExt.class, peer.surfaceData).getRGBPixelsAt(adjustedBounds);
|
||||
return SurfaceData.convertTo(WLPixelGrabberExt.class, peer.surfaceData).getRGBPixelsAt(bounds);
|
||||
} finally {
|
||||
WLToolkit.awtUnlock();
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.awt.wl;
|
||||
|
||||
import com.jetbrains.exported.JBRApi;
|
||||
import sun.awt.AWTAccessor;
|
||||
import sun.awt.RoundedCornersManager;
|
||||
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.RootPaneContainer;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
|
||||
public class WLRoundedCornersManager implements RoundedCornersManager {
|
||||
|
||||
public WLRoundedCornersManager() {
|
||||
var toolkit = Toolkit.getDefaultToolkit();
|
||||
if (toolkit == null || !toolkit.getClass().getName().equals("sun.awt.wl.WLToolkit")) {
|
||||
throw new JBRApi.ServiceNotAvailableException("Supported only with WLToolkit");
|
||||
}
|
||||
}
|
||||
|
||||
public enum RoundedCornerKind {
|
||||
DEFAULT,
|
||||
NONE,
|
||||
SMALL,
|
||||
FULL
|
||||
}
|
||||
|
||||
public static int roundCornerRadiusFor(RoundedCornerKind kind) {
|
||||
return switch (kind) {
|
||||
case DEFAULT, FULL -> 24;
|
||||
case NONE -> 0;
|
||||
case SMALL -> 8;
|
||||
};
|
||||
}
|
||||
|
||||
public static RoundedCornerKind roundedCornerKindFrom(Object o) {
|
||||
if (o instanceof String kind) {
|
||||
return switch (kind) {
|
||||
case "none" -> RoundedCornerKind.NONE;
|
||||
case "small" -> RoundedCornerKind.SMALL;
|
||||
case "full" -> RoundedCornerKind.FULL;
|
||||
default -> RoundedCornerKind.DEFAULT;
|
||||
};
|
||||
}
|
||||
|
||||
return RoundedCornerKind.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRoundedCorners(Window window, Object params) {
|
||||
Object peer = AWTAccessor.getComponentAccessor().getPeer(window);
|
||||
if (peer instanceof WLComponentPeer) {
|
||||
RoundedCornerKind kind = roundedCornerKindFrom(params);
|
||||
((WLComponentPeer) peer).setRoundedCornerKind(kind);
|
||||
} else if (window instanceof RootPaneContainer) {
|
||||
JRootPane rootpane = ((RootPaneContainer)window).getRootPane();
|
||||
if (rootpane != null) {
|
||||
rootpane.putClientProperty(WLComponentPeer.WINDOW_CORNER_RADIUS, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ import sun.awt.SunToolkit;
|
||||
import sun.awt.UNIXToolkit;
|
||||
import sun.awt.datatransfer.DataTransferer;
|
||||
import sun.java2d.vulkan.VKInstance;
|
||||
import sun.java2d.vulkan.VKRenderQueue;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -344,8 +343,7 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
int keyLocation,
|
||||
int rawCode,
|
||||
int extendedKeyCode,
|
||||
char keyChar,
|
||||
int modifiers) {
|
||||
char keyChar) {
|
||||
// Invoked from the native code
|
||||
assert EventQueue.isDispatchThread();
|
||||
|
||||
@@ -377,8 +375,7 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
keyChar,
|
||||
keyLocation,
|
||||
rawCode,
|
||||
extendedKeyCode,
|
||||
modifiers
|
||||
extendedKeyCode
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -394,11 +391,9 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
char keyChar,
|
||||
int keyLocation,
|
||||
long rawCode,
|
||||
int extendedKeyCode,
|
||||
int modifiers) {
|
||||
int mergedModifiers = inputState.getNonKeyboardModifiers() | modifiers;
|
||||
int extendedKeyCode) {
|
||||
final KeyEvent keyEvent = new KeyEvent(source, id, timestamp,
|
||||
mergedModifiers, keyCode, keyChar, keyLocation);
|
||||
inputState.getModifiers(), keyCode, keyChar, keyLocation);
|
||||
|
||||
AWTAccessor.KeyEventAccessor kea = AWTAccessor.getKeyEventAccessor();
|
||||
kea.setRawCode(keyEvent, rawCode);
|
||||
@@ -1051,13 +1046,6 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
|
||||
@Override
|
||||
public void sync() {
|
||||
if(VKInstance.isVulkanEnabled()) {
|
||||
VKRenderQueue.sync();
|
||||
}
|
||||
flushImpl();
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
flushImpl();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,9 @@
|
||||
package sun.java2d;
|
||||
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Image;
|
||||
|
||||
import sun.awt.X11GraphicsConfig;
|
||||
import sun.awt.image.SunVolatileImage;
|
||||
import sun.awt.image.SurfaceManager;
|
||||
import sun.awt.image.VolatileSurfaceManager;
|
||||
import sun.java2d.opengl.GLXGraphicsConfig;
|
||||
import sun.java2d.opengl.GLXVolatileSurfaceManager;
|
||||
@@ -75,8 +73,4 @@ public class UnixSurfaceManagerFactory extends SurfaceManagerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceManager createTextureWrapperSurfaceManager(GraphicsConfiguration gc, Image image, long texture) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ public final class WLVKGraphicsConfig extends WLGraphicsConfig
|
||||
|
||||
@Override
|
||||
public SurfaceData createSurfaceData(WLComponentPeer peer) {
|
||||
return new WLVKWindowSurfaceData(peer);
|
||||
return WLVKSurfaceData.createData(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* 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
|
||||
@@ -27,31 +28,39 @@ package sun.java2d.vulkan;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import sun.awt.image.BufImgSurfaceData;
|
||||
import sun.awt.wl.WLComponentPeer;
|
||||
import sun.java2d.SurfaceData;
|
||||
import sun.java2d.loops.Blit;
|
||||
import sun.java2d.loops.CompositeType;
|
||||
import sun.java2d.loops.SurfaceType;
|
||||
import sun.java2d.pipe.BufferedContext;
|
||||
import static sun.java2d.pipe.BufferedOpCodes.FLUSH_BUFFER;
|
||||
import sun.java2d.pipe.RenderBuffer;
|
||||
import sun.java2d.wl.WLPixelGrabberExt;
|
||||
import sun.java2d.wl.WLSurfaceDataExt;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
import static sun.java2d.pipe.BufferedOpCodes.FLUSH_BUFFER;
|
||||
import static sun.java2d.pipe.BufferedOpCodes.CONFIGURE_SURFACE;
|
||||
|
||||
public abstract class WLVKSurfaceData extends VKSurfaceData implements WLSurfaceDataExt, WLPixelGrabberExt {
|
||||
private static final PlatformLogger log = PlatformLogger.getLogger("sun.java2d.vulkan.WLVKSurfaceData");
|
||||
|
||||
public class WLVKWindowSurfaceData extends VKSurfaceData
|
||||
implements WLPixelGrabberExt, WLSurfaceDataExt
|
||||
{
|
||||
protected WLComponentPeer peer;
|
||||
|
||||
private native void initOps(int backgroundRGB);
|
||||
|
||||
private native void assignWlSurface(long surfacePtr);
|
||||
|
||||
public WLVKWindowSurfaceData(WLComponentPeer peer)
|
||||
protected WLVKSurfaceData(WLComponentPeer peer, WLVKGraphicsConfig gc,
|
||||
SurfaceType sType, ColorModel cm, int type)
|
||||
{
|
||||
super((VKGraphicsConfig) peer.getGraphicsConfiguration(), peer.getColorModel(), WINDOW, 0, 0);
|
||||
super(gc, cm, type, 0, 0);
|
||||
this.peer = peer;
|
||||
final int backgroundRGB = peer.getBackground() != null
|
||||
? peer.getBackground().getRGB()
|
||||
@@ -59,46 +68,10 @@ public class WLVKWindowSurfaceData extends VKSurfaceData
|
||||
initOps(backgroundRGB);
|
||||
}
|
||||
|
||||
public SurfaceData getReplacement() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeResource(int resType) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Rectangle getBounds() {
|
||||
Rectangle r = peer.getBufferBounds();
|
||||
r.x = r.y = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns destination Component associated with this SurfaceData.
|
||||
*/
|
||||
public Object getDestination() {
|
||||
return peer.getTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDefaultScaleX() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDefaultScaleY() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedContext getContext() {
|
||||
return ((WLVKGraphicsConfig) getDeviceConfiguration()).getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnScreen() {
|
||||
return true;
|
||||
private void bufferAttached() {
|
||||
// Called from the native code when a buffer has just been attached to this surface
|
||||
// but the surface has not been committed yet.
|
||||
peer.updateSurfaceSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,7 +79,6 @@ public class WLVKWindowSurfaceData extends VKSurfaceData
|
||||
assignWlSurface(surfacePtr);
|
||||
if (surfacePtr != 0) configure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revalidate(int width, int height, int scale) {
|
||||
this.width = width;
|
||||
@@ -115,6 +87,23 @@ public class WLVKWindowSurfaceData extends VKSurfaceData
|
||||
configure();
|
||||
}
|
||||
|
||||
private synchronized void configure() {
|
||||
VKRenderQueue rq = VKRenderQueue.getInstance();
|
||||
rq.lock();
|
||||
try {
|
||||
RenderBuffer buf = rq.getBuffer();
|
||||
rq.ensureCapacityAndAlignment(20, 4);
|
||||
buf.putInt(CONFIGURE_SURFACE);
|
||||
buf.putLong(getNativeOps());
|
||||
buf.putInt(width);
|
||||
buf.putInt(height);
|
||||
|
||||
rq.flushNow();
|
||||
} finally {
|
||||
rq.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void commit() {
|
||||
VKRenderQueue rq = VKRenderQueue.getInstance();
|
||||
@@ -130,16 +119,38 @@ public class WLVKWindowSurfaceData extends VKSurfaceData
|
||||
rq.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnScreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphicsConfiguration getDeviceConfiguration() {
|
||||
return peer.getGraphicsConfiguration();
|
||||
}
|
||||
|
||||
private void bufferAttached() {
|
||||
// Called from the native code when a buffer has just been attached to this surface
|
||||
// but the surface has not been committed yet.
|
||||
peer.updateSurfaceSize();
|
||||
/**
|
||||
* Creates a SurfaceData object representing surface of on-screen Window.
|
||||
*/
|
||||
public static WLVKWindowSurfaceData createData(WLComponentPeer peer) {
|
||||
WLVKGraphicsConfig gc = getGC(peer);
|
||||
return new WLVKWindowSurfaceData(peer, gc);
|
||||
}
|
||||
|
||||
public static WLVKGraphicsConfig getGC(WLComponentPeer peer) {
|
||||
if (peer != null) {
|
||||
return (WLVKGraphicsConfig) peer.getGraphicsConfiguration();
|
||||
} else {
|
||||
// REMIND: this should rarely (never?) happen, but what if
|
||||
// default config is not WLVK?
|
||||
GraphicsEnvironment env =
|
||||
GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice gd = env.getDefaultScreenDevice();
|
||||
return (WLVKGraphicsConfig)gd.getDefaultConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
public int getRGBPixelAt(int x, int y) {
|
||||
Rectangle r = peer.getBufferBounds();
|
||||
if (x < r.x || x >= r.x + r.width || y < r.y || y >= r.y + r.height) {
|
||||
@@ -152,7 +163,7 @@ public class WLVKWindowSurfaceData extends VKSurfaceData
|
||||
Blit blit = Blit.getFromCache(getSurfaceType(), CompositeType.SrcNoEa,
|
||||
resData.getSurfaceType());
|
||||
blit.Blit(this, resData, AlphaComposite.Src, null,
|
||||
x, y, 0, 0, 1, 1);
|
||||
x, y, 0, 0, 1, 1);
|
||||
|
||||
return resImg.getRGB(0, 0);
|
||||
}
|
||||
@@ -183,4 +194,54 @@ public class WLVKWindowSurfaceData extends VKSurfaceData
|
||||
resImg.getRGB(0, 0, b.width, b.height, pixels, 0, b.width);
|
||||
return pixels;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class WLVKWindowSurfaceData extends WLVKSurfaceData {
|
||||
public WLVKWindowSurfaceData(WLComponentPeer peer, WLVKGraphicsConfig gc)
|
||||
{
|
||||
super(peer, gc, gc.getSurfaceType(), peer.getColorModel(), WINDOW);
|
||||
}
|
||||
|
||||
public SurfaceData getReplacement() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeResource(int resType) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Rectangle getBounds() {
|
||||
Rectangle r = peer.getBufferBounds();
|
||||
r.x = r.y = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns destination Component associated with this SurfaceData.
|
||||
*/
|
||||
public Object getDestination() {
|
||||
return peer.getTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDefaultScaleX() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDefaultScaleY() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedContext getContext() {
|
||||
return ((WLVKGraphicsConfig) getDeviceConfiguration()).getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnScreen() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user