Compare commits

..

13 Commits

Author SHA1 Message Date
Vladimir Lagunov
860020a1fe wip: catch security exceptions 2025-02-04 13:59:31 +01:00
Vladimir Lagunov
ae2e0a1773 wip: fix JFileChooser test 2025-02-04 13:24:33 +01:00
Vladimir Lagunov
ff8e67c141 wip: mimic error messages 2025-02-03 20:22:35 +01:00
Vladimir Lagunov
c053250dbd wip 2025-01-31 12:36:52 +01:00
Vladimir Lagunov
5a1f42627b wip: File.exists 2025-01-31 12:36:52 +01:00
Vladimir Lagunov
a8500e1357 wip: trace logs 2025-01-31 12:36:51 +01:00
Vladimir Lagunov
d7931c14b8 wip 2025-01-30 20:47:14 +01:00
Vladimir Lagunov
07a86f7646 wip: fixed bugs in path canonicalization 2025-01-30 19:41:25 +01:00
Vladimir Lagunov
878e4e61ed wip: debug messages 2025-01-29 17:01:35 +01:00
Vladimir Lagunov
289a58f1f2 wip: file replace 2025-01-29 16:55:48 +01:00
Vladimir Lagunov
1372f86a0c wip: java.io over java.nio 2025-01-29 16:15:57 +01:00
Vladimir Lagunov
ee9eab7621 Don't throw ArrayIndexOutOfBoundsException from FileSystemProvider.newByteChannel on Unix
Due to a missed check, a call of `newByteChannel(Path.of(""), EnumSet.of(WRITE, CREATE_NEW))` led to `ArrayIndexOutOfBoundsException`, because there was no check for an empty path.

Now this code throws `FileAlreadyExists`, as it happens for other corner cases like `Path.of(".")"`
2025-01-29 16:15:09 +01:00
Vladimir Lagunov
d5db58c8e3 tmp delete me 2025-01-29 16:14:51 +01:00
163 changed files with 6402 additions and 4850 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);
}

View File

@@ -1052,6 +1052,7 @@ void InstanceKlass::clean_initialization_error_table() {
}
};
assert_locked_or_safepoint(ClassInitError_lock);
InitErrorTableCleaner cleaner;
if (_initialization_error_table != nullptr) {

View File

@@ -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");
}
}

View File

@@ -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;

View 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;
}
}
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View 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;
}
}
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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);
}
}
});
}

View File

@@ -1559,4 +1559,8 @@ public class FileChannelImpl
assert fileLockTable != null;
fileLockTable.remove(fli);
}
public FileDescriptor getFD() {
return fd;
}
}

View File

@@ -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));
}

View File

@@ -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;

View File

@@ -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] == '/')))
{

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -783,7 +783,7 @@ public class LWWindowPeer
@Override
public void notifyUpdateCursor() {
getLWToolkit().getCursorManager().updateCursorLater(this);
getLWToolkit().getCursorManager().updateCursorLater(this.getTarget());
}
@Override

View File

@@ -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);
}
}

View File

@@ -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());
}

View File

@@ -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")

View File

@@ -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);

View File

@@ -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];

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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()

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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.
*/

View File

@@ -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 {

View File

@@ -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);
}
/**

View File

@@ -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) {

View 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);
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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,

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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];
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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 */

View File

@@ -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

View File

@@ -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 */

View File

@@ -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.

View File

@@ -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;

View File

@@ -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,

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -219,7 +219,7 @@ public final class WLVKGraphicsConfig extends WLGraphicsConfig
@Override
public SurfaceData createSurfaceData(WLComponentPeer peer) {
return new WLVKWindowSurfaceData(peer);
return WLVKSurfaceData.createData(peer);
}
/**

View File

@@ -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