mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 17:24:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab3bf17696 | ||
|
|
cfc6e12e00 | ||
|
|
8537835d0c | ||
|
|
8e1809622c | ||
|
|
f81ee4b03a | ||
|
|
49db8e111d | ||
|
|
bd9a3c3c5c | ||
|
|
01ff612d1d | ||
|
|
0cae2be1f6 | ||
|
|
85d0641a73 | ||
|
|
46f8157087 | ||
|
|
d3dee841e8 | ||
|
|
3d3d0f9e18 | ||
|
|
23aa04361d | ||
|
|
69428c12f6 | ||
|
|
3978be161d | ||
|
|
2ed838acf1 | ||
|
|
d29a8278cd | ||
|
|
2f1f380e8e | ||
|
|
990dadc03f | ||
|
|
1946c2ad65 | ||
|
|
bfb66d84ee | ||
|
|
cae4fea2bb | ||
|
|
78abc05711 | ||
|
|
159f7ff8cc | ||
|
|
0d959c2b90 | ||
|
|
ce2462cad3 | ||
|
|
5457df5369 | ||
|
|
acedba4eac | ||
|
|
21a054a4db | ||
|
|
d056bf6fa7 | ||
|
|
5ed1c92590 | ||
|
|
3389b952d0 | ||
|
|
b49d37d827 | ||
|
|
12857006c6 | ||
|
|
38af64f10c | ||
|
|
10922edca3 | ||
|
|
2a8c8756d3 | ||
|
|
23e36f2b76 | ||
|
|
fcf8ad064a | ||
|
|
6dfb0312b1 | ||
|
|
0a4d48964e | ||
|
|
231a2ab216 | ||
|
|
61bb779dea | ||
|
|
7cd97d1216 | ||
|
|
fe82cd320a | ||
|
|
c7a0f3a208 | ||
|
|
338d94197c | ||
|
|
75a0278138 | ||
|
|
c97fb7004f | ||
|
|
bdcb4d1882 | ||
|
|
37afddab5d | ||
|
|
d876a203da | ||
|
|
85892c0f7f | ||
|
|
1587a12cd7 | ||
|
|
38b1d9a0c3 | ||
|
|
12fa08ab46 | ||
|
|
80e5b544c0 | ||
|
|
db7d328481 | ||
|
|
9f19d94c5f | ||
|
|
bc08e4baed | ||
|
|
8387b4e9cd | ||
|
|
6c888a634a | ||
|
|
fe67a22d5a | ||
|
|
66d2a548fc | ||
|
|
d16c1e7cf6 | ||
|
|
a8e95f70c1 | ||
|
|
9cb35c67cd | ||
|
|
cbd16b1a76 | ||
|
|
cd0a9608d3 | ||
|
|
fa71eda3db | ||
|
|
9705722469 | ||
|
|
977db3d604 | ||
|
|
c10d30b2da | ||
|
|
2f4ac2a9b7 | ||
|
|
9081f4c3e8 | ||
|
|
1f274a64ea | ||
|
|
9814a96b10 | ||
|
|
af599f292e | ||
|
|
7bb5bfd45c | ||
|
|
74ea5c4709 | ||
|
|
233c74e0d7 | ||
|
|
e17b45c842 | ||
|
|
c9d46e70e0 | ||
|
|
a8c29fa3f9 | ||
|
|
7c28a69bfe | ||
|
|
7f3fb35ebb | ||
|
|
44c930f4ab | ||
|
|
b76284048a | ||
|
|
762c5661eb | ||
|
|
1babfd968c | ||
|
|
888a4de0c9 | ||
|
|
a23cefba92 | ||
|
|
f2c07dea28 | ||
|
|
53776684c8 | ||
|
|
9dd60f6a75 | ||
|
|
87c83f0645 | ||
|
|
c578970a66 | ||
|
|
ef68af5850 | ||
|
|
b6a4497fb3 | ||
|
|
939e31461a | ||
|
|
02f0b0fca1 | ||
|
|
b7d439e6b1 | ||
|
|
c36647af34 | ||
|
|
f787801a0e | ||
|
|
f192793395 | ||
|
|
3876f7e693 | ||
|
|
94ee37bfed | ||
|
|
26c161f0e8 | ||
|
|
75639d3b73 | ||
|
|
a07cecb879 | ||
|
|
81b82748ce | ||
|
|
8d0c90c423 | ||
|
|
8ce6a2f8c6 | ||
|
|
2aa4ac7509 | ||
|
|
8f98151049 | ||
|
|
2b70318327 | ||
|
|
3b4ebcf611 | ||
|
|
2845c9e078 | ||
|
|
6f054a09a9 | ||
|
|
37a10eee3f | ||
|
|
744c1f971f | ||
|
|
32d1c27b9c | ||
|
|
f180894da7 | ||
|
|
bb67f82701 | ||
|
|
73f5556595 | ||
|
|
19bc7cbaed | ||
|
|
d189a644e2 | ||
|
|
94ba40ae4d | ||
|
|
961b23fd2d | ||
|
|
61b5301f1a | ||
|
|
0e539ae034 | ||
|
|
e581326641 | ||
|
|
c707cc51f1 | ||
|
|
7d890bf3e7 | ||
|
|
ffc324bab6 | ||
|
|
0dfffca88e | ||
|
|
a66db1517a | ||
|
|
269b7245d3 | ||
|
|
67f2484976 | ||
|
|
1c8687e6fd | ||
|
|
e6bbd155da | ||
|
|
2f64fef6a6 | ||
|
|
b0fd1ea46e | ||
|
|
6d8cbf9c2f | ||
|
|
096dd01446 | ||
|
|
22c9479946 | ||
|
|
b2f597a73a | ||
|
|
553dec5b6d | ||
|
|
bc8116006c | ||
|
|
bf9492dffb | ||
|
|
69aed42b89 | ||
|
|
ffe54f61b9 | ||
|
|
c86e1de9f6 | ||
|
|
6af401c834 | ||
|
|
b17bb42c49 | ||
|
|
3659b59db9 | ||
|
|
8c31eec525 |
No files matched your search
@@ -20,3 +20,11 @@ indent_size = 2
|
||||
|
||||
[*.lang]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.java]
|
||||
max_line_length = 240
|
||||
ij_java_wrap_long_lines = false
|
||||
ij_java_use_single_class_imports = true
|
||||
ij_java_class_count_to_use_import_on_demand = 999
|
||||
ij_java_names_count_to_use_import_on_demand = 999
|
||||
ij_java_packages_to_use_import_on_demand = empty
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds the libTracyClient shared library for the tracy-client module.
|
||||
# Builds the libTracyClient and AngelicaTracyCapture shared libraries for the tracy-client module.
|
||||
set -euo pipefail
|
||||
|
||||
TRACY_VERSION="${TRACY_VERSION:-v0.13.1}"
|
||||
TRACY_VERSION="${TRACY_VERSION:-v0.14.1}"
|
||||
TRACY_VERSION_BARE="${TRACY_VERSION#v}"
|
||||
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
|
||||
TRACY_REPO="https://github.com/wolfpld/tracy"
|
||||
|
||||
@@ -16,7 +17,7 @@ case "$(uname -m)" in
|
||||
arm64|aarch64) PLAT_ARCH="arm64" ;;
|
||||
*) PLAT_ARCH="x64" ;;
|
||||
esac
|
||||
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$PLAT_OS-$PLAT_ARCH}"
|
||||
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$TRACY_VERSION_BARE/$PLAT_OS-$PLAT_ARCH}"
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
@@ -30,35 +31,95 @@ else
|
||||
git clone --depth 1 --branch "$TRACY_VERSION" "$TRACY_REPO" "$SRC_DIR"
|
||||
fi
|
||||
|
||||
BUILD_DIR="$WORK_DIR/build"
|
||||
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
|
||||
|
||||
cmake -S "$SRC_DIR" -B "$BUILD_DIR" \
|
||||
CLIENT_BUILD_DIR="$WORK_DIR/build-client"
|
||||
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$CLIENT_BUILD_DIR" \
|
||||
-DTRACY_SRC_DIR="$SRC_DIR" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DTRACY_STATIC=OFF \
|
||||
-DTRACY_ENABLE=ON \
|
||||
-DTRACY_ON_DEMAND=ON \
|
||||
-DTRACY_DELAYED_INIT=ON \
|
||||
-DTRACY_MANUAL_LIFETIME=ON \
|
||||
-DTRACY_NO_SAMPLING=ON \
|
||||
-DTRACY_NO_SYSTEM_TRACING=ON \
|
||||
-DTRACY_NO_CRASH_HANDLER=ON \
|
||||
-DTRACY_LTO=OFF
|
||||
cmake --build "$BUILD_DIR" --config Release --parallel
|
||||
cmake --build "$CLIENT_BUILD_DIR" --config Release --parallel
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
CPM_CACHE_DIR="$WORK_DIR/cpm"
|
||||
CAPTURE_BUILD_DIR="$WORK_DIR/build-capture"
|
||||
cmake -S "$REPO_ROOT/tracy-client/src/main/native-capture" -B "$CAPTURE_BUILD_DIR" \
|
||||
-DTRACY_SRC_DIR="$SRC_DIR" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCPM_SOURCE_CACHE="$CPM_CACHE_DIR"
|
||||
cmake --build "$CAPTURE_BUILD_DIR" --config Release --parallel
|
||||
|
||||
find_one() {
|
||||
find "$1" -name "$2" 2>/dev/null | head -1
|
||||
}
|
||||
|
||||
CLIENT_LIB_NAME=""
|
||||
CLIENT_LIB=""
|
||||
for NAME in libTracyClient.so libTracyClient.dylib TracyClient.dll; do
|
||||
LIB="$(find "$BUILD_DIR" -name "$NAME" | head -1)"
|
||||
LIB="$(find_one "$CLIENT_BUILD_DIR" "$NAME")"
|
||||
if [[ -n "$LIB" ]]; then
|
||||
cp -L "$LIB" "$OUT_DIR/$NAME"
|
||||
cp "$SRC_DIR/LICENSE" "$OUT_DIR/LICENSE"
|
||||
mkdir -p "$RES_DIR"
|
||||
cp -L "$LIB" "$RES_DIR/$NAME"
|
||||
cp "$SRC_DIR/LICENSE" "$RES_DIR/LICENSE"
|
||||
echo "built: $OUT_DIR/$NAME ($TRACY_VERSION), staged: $RES_DIR/$NAME"
|
||||
exit 0
|
||||
CLIENT_LIB="$LIB"
|
||||
CLIENT_LIB_NAME="$NAME"
|
||||
break
|
||||
fi
|
||||
done
|
||||
echo "error: built library not found under $BUILD_DIR" >&2
|
||||
exit 1
|
||||
if [[ -z "$CLIENT_LIB" ]]; then
|
||||
echo "error: libTracyClient not found under $CLIENT_BUILD_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CAPTURE_LIB_NAME=""
|
||||
CAPTURE_LIB=""
|
||||
for NAME in libAngelicaTracyCapture.so libAngelicaTracyCapture.dylib AngelicaTracyCapture.dll; do
|
||||
LIB="$(find_one "$CAPTURE_BUILD_DIR" "$NAME")"
|
||||
if [[ -n "$LIB" ]]; then
|
||||
CAPTURE_LIB="$LIB"
|
||||
CAPTURE_LIB_NAME="$NAME"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$CAPTURE_LIB" ]]; then
|
||||
echo "error: AngelicaTracyCapture not found under $CAPTURE_BUILD_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STRIPPED_CAPTURE_LIB="$WORK_DIR/$CAPTURE_LIB_NAME"
|
||||
cp -L "$CAPTURE_LIB" "$STRIPPED_CAPTURE_LIB"
|
||||
case "$PLAT_OS" in
|
||||
macos) strip -x "$STRIPPED_CAPTURE_LIB" ;;
|
||||
linux) strip --strip-unneeded "$STRIPPED_CAPTURE_LIB" ;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
LICENSE_ZSTD="$(find_one "$CPM_CACHE_DIR/zstd" LICENSE)"
|
||||
LICENSE_CAPSTONE="$(find_one "$CPM_CACHE_DIR/capstone" LICENSE.TXT)"
|
||||
LICENSE_PPQSORT="$(find_one "$CPM_CACHE_DIR/ppqsort" LICENSE)"
|
||||
|
||||
LICENSE_NAMES=(LICENSE LICENSE-zstd LICENSE-capstone LICENSE-ppqsort)
|
||||
LICENSE_SRCS=("$SRC_DIR/LICENSE" "$LICENSE_ZSTD" "$LICENSE_CAPSTONE" "$LICENSE_PPQSORT")
|
||||
|
||||
for i in "${!LICENSE_NAMES[@]}"; do
|
||||
if [[ -z "${LICENSE_SRCS[$i]}" || ! -f "${LICENSE_SRCS[$i]}" ]]; then
|
||||
echo "error: ${LICENSE_NAMES[$i]} source not found under $CPM_CACHE_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$OUT_DIR" "$RES_DIR"
|
||||
for DEST in "$OUT_DIR" "$RES_DIR"; do
|
||||
cp -L "$CLIENT_LIB" "$DEST/$CLIENT_LIB_NAME"
|
||||
cp -L "$STRIPPED_CAPTURE_LIB" "$DEST/$CAPTURE_LIB_NAME"
|
||||
for i in "${!LICENSE_NAMES[@]}"; do
|
||||
cp "${LICENSE_SRCS[$i]}" "$DEST/${LICENSE_NAMES[$i]}"
|
||||
done
|
||||
done
|
||||
|
||||
echo "built: $OUT_DIR/$CLIENT_LIB_NAME, $OUT_DIR/$CAPTURE_LIB_NAME ($TRACY_VERSION)"
|
||||
echo "staged: $RES_DIR/$CLIENT_LIB_NAME, $RES_DIR/$CAPTURE_LIB_NAME"
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# usage: companion-version.sh <name> <catalog-key>; TAG is <name>-v<version>[-N] or unset
|
||||
set -euo pipefail
|
||||
|
||||
name="${1:?name required}"
|
||||
key="${2:?catalog key required}"
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
base="$(sed -nE "s/^$key = \"(.*)\"\$/\\1/p" gradle/libs.versions.toml)"
|
||||
[[ -n "$base" ]] || { echo "$key not found" >&2; exit 1; }
|
||||
|
||||
tag="${TAG:-}"
|
||||
if [[ -z "$tag" ]]; then
|
||||
echo "$base"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
version="${tag#"$name"-v}"
|
||||
if [[ "$version" == "$tag" || ( "$version" != "$base" && ! "$version" =~ ^"$base"-[1-9][0-9]*$ ) ]]; then
|
||||
echo "bad tag '$tag': want $name-v$base[-N]" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$version"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# usage: release-companion-jar.sh <jar> <notes>; TAG is <name>-v<version>
|
||||
set -euo pipefail
|
||||
|
||||
: "${TAG:?TAG required}"
|
||||
jar="${1:?jar required}"
|
||||
notes="${2:?notes required}"
|
||||
|
||||
gh release create "$TAG" "$jar" --latest=false --title "angelica-${TAG%%-v*} ${TAG#*-v}" --notes "$notes"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$REPO_ROOT/umbra"
|
||||
|
||||
gradle_args=(:compileJava :compileMixinJava :test --no-daemon --console=plain --stacktrace)
|
||||
if [[ -n "${UMBRA_GRADLE_ARGS:-}" ]]; then
|
||||
read -ra extra_args <<< "$UMBRA_GRADLE_ARGS"
|
||||
gradle_args+=("${extra_args[@]}")
|
||||
fi
|
||||
|
||||
sh ./gradlew "${gradle_args[@]}"
|
||||
@@ -9,5 +9,42 @@ on:
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@05e7cd7ce0ea015acda8c10c1c39fb3a20a49e60
|
||||
with:
|
||||
disable-spotless-pr: true
|
||||
java-distribution: temurin
|
||||
cache-rfg: true
|
||||
secrets: inherit
|
||||
|
||||
sdl-gpu-tests:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
concurrency:
|
||||
group: sdl-gpu-tests-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Install a software Vulkan driver
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y xvfb mesa-vulkan-drivers
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
21
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
- name: Run SDL-GPU tests
|
||||
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest
|
||||
- name: Check LWJGL did not extract natives to the shared temp dir
|
||||
run: test ! -e "/tmp/lwjgl_$USER"
|
||||
- name: Attach SDL-GPU test results
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sdl-gpu-test-results
|
||||
path: |
|
||||
sdl-gpu/build/test-results/
|
||||
sdl-gpu/build/reports/tests/
|
||||
sdl-gpu/hs_err_pid*.log
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Build angelica-moltenvk
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "moltenvk-v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: Publish angelica-moltenvk.jar to the GTNH maven
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
- name: Compute version
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
|
||||
run: |
|
||||
version="$(bash .github/scripts/companion-version.sh moltenvk lwjgl3Release)"
|
||||
echo "MOLTENVK_VERSION=$version" >> "$GITHUB_ENV"
|
||||
- name: Build angelica-moltenvk.jar
|
||||
run: ./gradlew -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:distJar
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: angelica-moltenvk
|
||||
path: moltenvk/build/libs/angelica-moltenvk-*.jar
|
||||
if-no-files-found: error
|
||||
- name: Publish to GTNH maven
|
||||
if: github.ref_type == 'tag' || inputs.publish
|
||||
env:
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
run: ./gradlew -PpublishMoltenVK=true -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:publish
|
||||
- name: Create GitHub release
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: bash .github/scripts/release-companion-jar.sh moltenvk/build/libs/angelica-moltenvk-*.jar "MoltenVK for the SDL GPU Vulkan driver on macOS."
|
||||
@@ -6,39 +6,38 @@ on:
|
||||
- "tracy-v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tracy-version:
|
||||
description: Tracy git tag to build
|
||||
required: false
|
||||
default: v0.13.1
|
||||
publish:
|
||||
description: Publish angelica-tracy.jar to the GTNH maven
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [linux-x64, windows-x64, macos-x64, macos-arm64]
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
platform: linux-x64
|
||||
- os: windows-2025
|
||||
platform: windows-x64
|
||||
- os: macos-15-intel
|
||||
platform: macos-x64
|
||||
- os: macos-15
|
||||
platform: macos-arm64
|
||||
- platform: linux-x64
|
||||
os: ubuntu-22.04
|
||||
- platform: windows-x64
|
||||
os: windows-2025
|
||||
- platform: macos-x64
|
||||
os: macos-15-intel
|
||||
- platform: macos-arm64
|
||||
os: macos-15
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Build libTracyClient
|
||||
- name: Build Tracy natives
|
||||
shell: bash
|
||||
env:
|
||||
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
|
||||
OUT_DIR: dist/tracy-natives
|
||||
run: |
|
||||
export TRACY_VERSION="${RAW_VERSION#tracy-}"
|
||||
export TRACY_VERSION="v$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
|
||||
bash .github/scripts/build-tracy-natives.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -56,14 +55,6 @@ jobs:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
- name: Check tracy version matches the version catalog
|
||||
shell: bash
|
||||
env:
|
||||
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
|
||||
run: |
|
||||
ver="${RAW_VERSION#tracy-}"
|
||||
grep -q "^tracy = \"${ver#v}\"$" gradle/libs.versions.toml \
|
||||
|| { echo "libs.versions.toml tracy does not match $ver" >&2; exit 1; }
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: tracy-natives-*
|
||||
@@ -71,14 +62,22 @@ jobs:
|
||||
- name: Stage natives into tracy-client resources
|
||||
shell: bash
|
||||
run: |
|
||||
version="$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
|
||||
for d in staging/tracy-natives-*; do
|
||||
dest="tracy-client/src/main/resources/natives/tracy/${d#staging/tracy-natives-}"
|
||||
dest="tracy-client/src/main/resources/natives/tracy/$version/${d#staging/tracy-natives-}"
|
||||
mkdir -p "$dest"
|
||||
cp "$d"/* "$dest/"
|
||||
done
|
||||
find tracy-client/src/main/resources/natives -type f
|
||||
- name: Compute version
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
|
||||
run: |
|
||||
version="$(bash .github/scripts/companion-version.sh tracy tracy)"
|
||||
echo "TRACY_JAR_VERSION=$version" >> "$GITHUB_ENV"
|
||||
- name: Build angelica-tracy.jar
|
||||
run: ./gradlew :tracy-client:distJar
|
||||
run: ./gradlew -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:distJar
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: angelica-tracy
|
||||
@@ -89,4 +88,10 @@ jobs:
|
||||
env:
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
run: ./gradlew :tracy-client:publish
|
||||
run: ./gradlew -PpublishTracy=true -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:publish
|
||||
- name: Create GitHub release
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: bash .github/scripts/release-companion-jar.sh tracy-client/build/libs/angelica-tracy-*.jar "Tracy profiler backend for Angelica."
|
||||
@@ -3,7 +3,7 @@ name: Release tagged build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ '*' ]
|
||||
tags: [ '*', '!tracy-v*', '!moltenvk-v*' ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Umbra build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, main ]
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
|
||||
jobs:
|
||||
umbra-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: |
|
||||
21
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
validate-wrappers: true
|
||||
- run: .github/scripts/umbra-build.sh
|
||||
@@ -1,5 +1,6 @@
|
||||
.gradle
|
||||
.kotlin/
|
||||
.lwjgl/
|
||||
.settings
|
||||
/.idea/
|
||||
/.vs/
|
||||
@@ -40,5 +41,10 @@ addon.late.local.gradle
|
||||
addon.late.local.gradle.kts
|
||||
local/
|
||||
tracy-client/src/main/resources/natives/
|
||||
tracy-client/angelica/
|
||||
dist/tracy-natives/
|
||||
*.tracy
|
||||
*.jfr
|
||||
|
||||
tracy-client/angelica/
|
||||
shaderc_failures/
|
||||
@@ -1,5 +1,7 @@
|
||||
# AI Usage Policy
|
||||
|
||||
🌐 **Languages:** [English](AI_POLICY.md) | [Українська](AI_POLICY_uk.md)
|
||||
|
||||
This project has strict rules for AI usage:
|
||||
|
||||
* **The human-in-the-loop must fully understand all code.** If you
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Політика використання ШІ
|
||||
|
||||
🌐 **Мови:** [English](AI_POLICY.md) | [Українська](AI_POLICY_uk.md)
|
||||
|
||||
Цей проєкт має суворі правила щодо використання штучного інтелекту (ШІ):
|
||||
|
||||
* **Людина, яка контролює процес, повинна повністю розуміти весь код.**
|
||||
Якщо ви не можете пояснити, що роблять ваші зміни та як вони взаємодіють із
|
||||
більшою системою без допомоги інструментів ШІ, не пропонуйте ці зміни.
|
||||
|
||||
* **Обговорення та задачі можуть створюватися за допомогою ШІ, але повинні перебувати
|
||||
під повним контролем людини.** Це означає, що будь-який контент, згенерований за допомогою ШІ,
|
||||
перед надсиланням має бути перевірений *та відредагований* людиною.
|
||||
Автор повинен сам розібратися в питанні та відсіяти все зайве.
|
||||
|
||||
* **Медіаконтент, згенерований ШІ, заборонений (текстури, відео, аудіо тощо).**
|
||||
Текст і код є єдиним контентом, згенерованим ШІ, який дозволено використовувати
|
||||
згідно з іншими правилами цієї політики.
|
||||
|
||||
* **Повідомлення до комітів та описи PR повинні бути написані у вашому власному стилі.**
|
||||
Коміти, повністю або частково створені ШІ, заборонені, включно з тегами `Co-authored-by` з вказівкою ШІ.
|
||||
|
||||
* **Подавайте виправлення лише для тих багів, із якими ви особисто зіткнулися в грі.**
|
||||
Якщо ви особисто не стикалися з багом, не відтворили його власноруч і ще не знаєте
|
||||
добре відповідний код, не натравлюйте на нього ШІ, щоб потім перенаправити результат нам.
|
||||
Патчі, написані для проблем, із якими автор особисто ніколи не стикався, зазвичай
|
||||
виправляють не те, що потрібно, та/або створюють нові баги.
|
||||
|
||||
* **Стиль та суть письмового контенту повинні належати вам.**
|
||||
Для документації та особливо для квестів тон і думка, яку ви намагаєтеся донести, мають
|
||||
виходити від вас, а не від ШІ. Ви можете використовувати ШІ для покращення формулювань
|
||||
або вичитки, якщо бажаєте, але якщо суть і стиль не ваші, не пропонуйте такий контент.
|
||||
|
||||
* **За зловживання ШІ можна отримати блокування.** Користувачі, які створюють
|
||||
низькоякісний контент, очевидно згенерований ШІ (слоп), можуть бути заблоковані для подальших внесків.
|
||||
Ми залюбки допомагаємо розробникам-початківцям навчатися та рости, але якщо ви зацікавлені
|
||||
в цьому, не використовуйте ШІ, і ми вам допоможемо. Нам прикро, що недобросовісні
|
||||
користувачі ШІ зіпсували це для вас.
|
||||
|
||||
|
||||
## Тут працюють люди
|
||||
|
||||
Будь ласка, пам’ятайте, що цей проєкт підтримують люди.
|
||||
|
||||
Кожне обговорення, задача та запит на злиття читаються й перевіряються
|
||||
людьми (а іноді й машинами). Це точка перетину, де люди взаємодіють
|
||||
один з одним та виконують роботу. Неґречно та неповажно приходити сюди
|
||||
з неякісною, некваліфікованою та зробленою абияк роботою, оскільки це
|
||||
перекладає тягар перевірки на мейнтейнерів.
|
||||
|
||||
В ідеальному світі ШІ щоразу створював би високоякісну та точну роботу.
|
||||
Але сьогодні ця реальність залежить від користувача ШІ. І сьогодні чимало
|
||||
користувачів використовують ШІ неналежним чином. Тому, поки це не зміниться,
|
||||
ми змушені мати суворі правила, щоб захистити мейнтейнерів.
|
||||
|
||||
Створено на основі https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
|
||||
@@ -7,24 +7,54 @@
|
||||
* It's a cute angle -mitchej123
|
||||
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
|
||||
|
||||
## **Beta Quality - Here there be Dragons**
|
||||
* Shaders - Beta
|
||||
## What's in it
|
||||
* Shaders - Iris backport & enhancements
|
||||
* Terrain - Celeritas 2.5.12 with threaded chunk building and translucency sorting
|
||||
* GL 3.3 core profile with Java based fixed function pipeline emulation
|
||||
* Renderer selection - OpenGL, or SDL GPU for Vulkan/Metal/Direct3D 12
|
||||
* Batching and instancing for entities, tile entities, items, particles and weather
|
||||
* Dynamic lights
|
||||
* HUD caching
|
||||
* Custom fonts and text effects
|
||||
* MCPatcher - connected textures, custom item textures, custom colors, random mobs, better skies
|
||||
* Bundled NotFine, zoom, FPS reducer
|
||||
|
||||
## Status
|
||||
* OpenGL + Celeritas terrain - Default
|
||||
* Shaders - Generally Available
|
||||
* Complementary + Euphoria Patches is the primary compatibility target
|
||||
* Sildurs, Seus, and any other shaders might work
|
||||
* SDL GPU renderer - Experimental
|
||||
* MCPatcher features - Alpha
|
||||
* HUD caching - Semi-stable
|
||||
|
||||
## SDL GPU renderer
|
||||
Optional, OpenGL is the default
|
||||
* Vulkan, Metal (macOS) and Direct3D 12 (Windows) through SDL3
|
||||
* Needs [LWJGL3ify](https://github.com/GTNewHorizons/lwjgl3ify/releases/) 3.0.25+
|
||||
* Falls back to OpenGL when no device can be created
|
||||
* Vulkan on macOS via MoltenVK from the optional `angelica-moltenvk` jar
|
||||
|
||||
## Core Profile
|
||||
Targets GL 3.3+ core profile
|
||||
* FFP emulation via generated GLSL shaders
|
||||
* Display list emulation
|
||||
* Software matrix stacks and state management through GLSM
|
||||
* GL_QUADS → GL_TRIANGLES conversion
|
||||
* GL_QUADS -> GL_TRIANGLES conversion
|
||||
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
|
||||
|
||||
|
||||
# Required Dependencies
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
|
||||
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
|
||||
* NOTE: Some mods are not required, but a specific version is required if present - see: `Minimum versions` below.
|
||||
|
||||
# Minimum versions (if present)
|
||||
* [LWJGL3ify](https://github.com/GTNewHorizons/lwjgl3ify/releases/) 1.5.3+ (3.0.25+ for the SDL GPU renderer)
|
||||
* [Hodgepodge](https://github.com/GTNewHorizons/Hodgepodge/releases) 2.4.4+
|
||||
* [ArchaicFix](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) 0.7.0+
|
||||
* [CodeChickenCore](https://github.com/GTNewHorizons/CodeChickenCore/releases) 1.2.0+ - Threading issues below that
|
||||
* [Amazing Trophies](https://github.com/GTNewHorizons/Amazing-Trophies/releases) 1.2.2+
|
||||
|
||||
# Known (temporary) Incompatibilities
|
||||
* Forge Relocation - Currently non Thread Safe; safe if not in use
|
||||
@@ -43,15 +73,15 @@ Targets GL 3.3+ core profile
|
||||
* FastCraft [Disabled, won't fix]
|
||||
* BetterFPS [Disabled, won't fix]
|
||||
* OptimizationsAndTweaks [Disabled, won't fix]
|
||||
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
|
||||
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
|
||||
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
|
||||
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
|
||||
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
|
||||
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
|
||||
* SmoothFont - Equivalent functionality added
|
||||
* ClientFixer - Use a resource pack like [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
|
||||
|
||||
# Building
|
||||
* `./gradlew build`, `./gradlew test`
|
||||
* Subprojects: `glsm` (state manager, FFP emulation, backends), `lwjgl3-backend`, `sdl-gpu`, `tracy-client`, `moltenvk`
|
||||
* Read the [AI policy](AI_POLICY.md) before opening a PR
|
||||
|
||||
# Credits
|
||||
* ShadersMod
|
||||
* Original ShadersMods: karyonix, sonic ether, id_miner, daxnitro
|
||||
@@ -94,8 +124,10 @@ Targets GL 3.3+ core profile
|
||||
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
|
||||
* Beddium
|
||||
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) - Code ported and/or inspired from Mesa - under the MIT license
|
||||
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
* RGSS Terrain Texture Filtering
|
||||
* Indiesoft for [defold-sharp-sprite](https://github.com/indiesoftby/defold-sharp-sprite), used as the reference implementation under the MIT license
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# Angelica<img src="img/logo-readme.png" align="right">
|
||||
|
||||
<sup>**[CurseForge](https://www.curseforge.com/minecraft/mc-mods/angelica) | [Modrinth](https://modrinth.com/mod/angelica)**</sup>
|
||||
|
||||
🌐 **Мови:** [English](README.MD) | [Українська](README_uk.MD)
|
||||
|
||||
* Маленький Кут [sic], що підтримує шейдери, водночас рятуючи ваші FPS від вірної смерті — boubou
|
||||
* Він тупий чи гострий? — Caedis
|
||||
* Це милий кут — mitchej123
|
||||
* Це янгол, який витягує вас із пекла OptiFine — Roadhog360
|
||||
|
||||
## **Бета-якість — Тут водяться дракони**
|
||||
* Шейдери — Бета
|
||||
* Complementary + Euphoria Patches є головною ціллю для сумісності
|
||||
* Sildurs, Seus та будь-які інші шейдери можуть працювати
|
||||
|
||||
## Базовий профіль
|
||||
Цільовим є базовий профіль GL 3.3+
|
||||
* Емуляція FFP через згенеровані GLSL-шейдери
|
||||
* Емуляція списків відображення
|
||||
* Програмні стеки матриць та керування станом через GLSM
|
||||
* Конвертація GL_QUADS → GL_TRIANGLES
|
||||
* Автоматична трансформація шейдерів з модів, які використовують вбудовані функції GLSL виключно для сумісності (`gl_ModelViewMatrix`, `texture2D` тощо)
|
||||
|
||||
|
||||
# Обов’язкові залежності
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
|
||||
* ПРИМІТКА: Деякі моди не є обов’язковими, але якщо вони встановлені, вимагається їхня конкретна версія — див. розділ `Постійна несумісність` нижче.
|
||||
|
||||
# Відома (тимчасова) несумісність
|
||||
* Forge Relocation — Наразі не є потокобезпечним; безпечно, якщо не використовується
|
||||
* ProjectRed Frames — Наразі не є потокобезпечним; безпечно, якщо не використовується
|
||||
|
||||
# Можлива несумісність
|
||||
* EndlessIDs — Коміти для сумісності були зроблені, може працювати, а може й ні — не повністю протестовано
|
||||
|
||||
# Відома несумісність
|
||||
* FalseTweaks — Наразі не планується (Шукаємо волонтерів) [Може працювати, якщо фактично вимкнути більшу частину мода]
|
||||
* RPLE — Наразі не планується (Шукаємо волонтерів)
|
||||
* LiteLoader — Встановіть [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
|
||||
|
||||
# Постійна несумісність
|
||||
* OptiFine [Вимкнено, виправлятись не буде]
|
||||
* FastCraft [Вимкнено, виправлятись не буде]
|
||||
* BetterFPS [Вимкнено, виправлятись не буде]
|
||||
* OptimizationsAndTweaks [Вимкнено, виправлятись не буде]
|
||||
* [Amazing Trophies 1.2.1 і нижче](https://github.com/GTNewHorizons/Amazing-Trophies/releases) — Використовуйте 1.2.2+
|
||||
* [LWJGL3ify 1.5.4 і нижче](https://github.com/GTNewHorizons/lwjgl3ify/releases/) — Використовуйте найновішу доступну версію, 1.5.7+
|
||||
* [ArchaicFix 0.6.2 і нижче](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) — Використовуйте 0.7.0 або вище
|
||||
* [Hodgepodge 2.4.3 і нижче](https://github.com/GTNewHorizons/Hodgepodge/releases) — Використовуйте 2.4.5 або вище
|
||||
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 — Проблеми з багатопотоковістю, використовуйте 1.3.0+
|
||||
* Neodymium — Не перевірено, але навряд чи буде сумісним. (Побудова сітки чанків точно несумісна, інші функції можуть працювати залежно від конфігурації)
|
||||
* SmoothFont — Додано еквівалентний функціонал
|
||||
* ClientFixer — Використовуйте пакет ресурсів, як-от [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
|
||||
|
||||
# Подяки
|
||||
* ShadersMod
|
||||
* Оригінальні ShadersMods: karyonix, sonic ether, id_miner, daxnitro
|
||||
* eigenraven, Caedis, glowredman та mitchej123 за конвертацію ASM у Mixin
|
||||
* eigenraven та vlaetansky за те, що змусили оригінальний ShadersMod знову компілюватися
|
||||
* ArchaicFix
|
||||
* Включає графічні покращення з [ArchaicFix](https://github.com/embeddedt/ArchaicFix/tree/4afb943751883b27a1f0aa7c5ba71698bc897e95) від embeddedt за ліцензією [LGPL](https://github.com/embeddedt/ArchaicFix/blob/main/LICENSE.md)
|
||||
* NotFine
|
||||
* Включає модифіковану версію [NotFine](https://github.com/jss2a98aj/NotFine/tree/a2652e24b5654f437fb7138f6d1f5e947a7d7125) від jss2a98aj за ліцензією LGPL
|
||||
* Iris Shaders
|
||||
* Команда [Iris](https://github.com/IrisShaders/Iris): coderbot, IMS212, Justsnoopy30, FoundationGames
|
||||
* Asek3 за порт Iris для Forge [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
|
||||
* Бекпортований код за ліцензією LGPL
|
||||
* Sodium / Celeritas
|
||||
* JellySquid та команді CaffeineMC за створення Sodium
|
||||
* Embeddedt за Celeritas, що використовується за ліцензією LGPL 3.0
|
||||
* Reese's Sodium Options
|
||||
* FlashyReese за [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) за ліцензією [MIT](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
|
||||
* Neodymium
|
||||
* Makamys за [Neodymium](https://github.com/makamys/Neodymium) — Різноманітний функціонал Tessellator -> Quad взято за ліцензією LGPL 3
|
||||
* MCPatcherForge
|
||||
* Оригінальний [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
|
||||
* [Версія для Forge](https://github.com/mist475/MCPatcherForge): Mist475 за ліцензією LGPL 3.0 з типовими ctm-текстурами для скла та книжкових полиць від DarkBum за ліцензією CC-BY-SA
|
||||
* HUDCaching
|
||||
* Створено [Moulberry](https://github.com/Moulberry/MCHUDCaching), бекпортовано з мода [Patcher](https://github.com/Sk1erLLC/Patcher) від Alexdoru за ліцензією CC-BY-SA-NC 4.0
|
||||
* Angelica
|
||||
* mitchej123, Omni, Makamys, Embeddedt, NanoLive, Caedis, Cardinalstar, Alexdoru, Eigenraven, mist475, Clepto, kurrycat за допомогу в запуску та налаштуванні Angelica!
|
||||
* IMS, jss2a98aj, || dbp ||, roadhog360 за поради та моральну підтримку
|
||||
* Forge 1.12+
|
||||
* Деякі файли були взяті з Minecraft Forge за ліцензією LGPL 2.1 і позначені відповідно
|
||||
* FRAPI та Indigo
|
||||
* API для побудови моделей значною мірою натхненний/скопійований з Fabric; автори оригінального коду (upstream) можуть використовувати його за оригінальною ліцензією Apache 2.0,
|
||||
див. LICENSE у `com.gtnewhorizons.angelica.models`
|
||||
* Dynamic Lights
|
||||
* LambdAurora за [LambDynamicLights](https://github.com/LambdAurora/LambDynamicLights) за ліцензією MIT, та anthxnymc за порт для Forge [DynamicLightsReforged](https://github.com/anthxnymc/DynamicLightsReforged), також за ліцензією MIT
|
||||
* Embeddedt та Ferri_Arnus за [glsl-transformation-lib](https://github.com/TauMC/glsl-transformation-lib)
|
||||
* Також за роботу над [Monocle](https://github.com/ferriarnus/Monocle), який використовувався як еталон для реалізації glsl-transformation-lib
|
||||
* Код з обох glsl-transformation-lib та Monocle взято за їхніми відповідними ліцензіями LGPL 3.0
|
||||
* FalseTweaks
|
||||
* FalsePattern за [FalseTweaks](https://github.com/falsepattern/falsetweaks) — Оптимізації рендерингу предметів взято з FalseTweaks за ліцензією LGPL 3.0
|
||||
* Beddium
|
||||
* Venn та Falsepattern за [Beddium](https://github.com/vfx-dev/Beddium) — MinimapRenderer, SafeChunkTracker та деякі команди для налагодження адаптовано за ліцензією LGPL 3.0
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Код портований з Mesa та/або натхненний нею — за ліцензією MIT
|
||||
* Розподілювач ID списків відображення портовано з [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* Генерація вершинних шейдерів FFP натхненна ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* Генерація фрагментних шейдерів FFP натхненна ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Оптимізація параметрів туману натхненна st_nir_lower_fog.c (Copyright 2023 Google LLC) та prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
+68
-84
@@ -1,18 +1,18 @@
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
|
||||
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
|
||||
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
|
||||
apply(from = "gradle/mod-version.gradle.kts")
|
||||
|
||||
val lwjglDebug = false
|
||||
val gpuHud = false
|
||||
val renderdoc = false
|
||||
val rgp = false
|
||||
extra["lwjglDebug"] = lwjglDebug
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
|
||||
@@ -23,24 +23,13 @@ minecraft {
|
||||
|
||||
extraRunJvmArguments.add("-Dsun.net.client.defaultConnectTimeout=5000")
|
||||
extraRunJvmArguments.add("-Dsun.net.client.defaultReadTimeout=5000")
|
||||
// TODO: Remove once GTNHGradle includes shaderc/spvc in the default lwjgl3Bindings list
|
||||
lwjgl3Bindings.addAll("shaderc", "spvc")
|
||||
lwjgl3Version = libs.versions.lwjgl3.get()
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.debug.redirectorLogspam=true")
|
||||
|
||||
}
|
||||
|
||||
// Forward -Dangelica.* from the gradle invocation to the client
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
for ((key, value) in System.getProperties()) {
|
||||
val name = key.toString()
|
||||
if (name.startsWith("angelica.")) {
|
||||
jvmArgs("-D$name=$value")
|
||||
}
|
||||
}
|
||||
}
|
||||
apply(from = "gradle/angelica-run-common.gradle.kts")
|
||||
|
||||
if (gpuHud) {
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
@@ -56,47 +45,12 @@ if (gpuHud) {
|
||||
|
||||
configurations.all {
|
||||
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(module("com.github.GTNewHorizons:lwjgl3ify"))
|
||||
.using(module("com.github.GTNewHorizons:lwjgl3ify:${libs.versions.lwjgl3ify.get()}"))
|
||||
}
|
||||
}
|
||||
|
||||
// lwjgl3ify pulls a pinned lwjgl 3.4.2 snapshot transitively from here.
|
||||
repositories {
|
||||
maven {
|
||||
name = "Maven Central Snapshots"
|
||||
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
|
||||
content { includeGroup("org.lwjgl") }
|
||||
}
|
||||
}
|
||||
|
||||
val osName = System.getProperty("os.name").lowercase()
|
||||
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
|
||||
val isMacOsArm64 = isMacOs && System.getProperty("os.arch") == "aarch64"
|
||||
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient")) {
|
||||
if (isMacOs) {
|
||||
// SDL3 / Cocoa / Metal must initialize on the JVM main thread.
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
|
||||
if (lwjglDebug) {
|
||||
environment("METAL_DEVICE_WRAPPER_TYPE", "1")
|
||||
environment("METAL_DEBUG_ERROR_MODE", "0")
|
||||
environment("MTL_SHADER_VALIDATION", "1")
|
||||
environment("MTL_SHADER_VALIDATION_REPORT_TO_STDERR", "1")
|
||||
environment("MTL_DEBUG_LAYER", "1")
|
||||
environment("MallocScribble", "1")
|
||||
environment("MallocPreScribble", "1")
|
||||
environment("MallocGuardEdges", "1")
|
||||
environment("MallocStackLogging", "1")
|
||||
environment("MallocStackLoggingNoCompact", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linux runClient: RenderDoc frame capture (F12) OR RADV RGP/SQTT capture. Mutually exclusive; RGP wins
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient") && !isMacOs && !osName.contains("windows")) {
|
||||
@@ -125,14 +79,6 @@ tasks.withType<JavaExec>().configureEach {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
val projectVersion = project.version.toString()
|
||||
inputs.property("version", projectVersion)
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to projectVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copyDependencies") {
|
||||
group = "Angelica"
|
||||
description = "Collect dependencies into the testDependencies folder"
|
||||
@@ -143,8 +89,11 @@ tasks.register<Copy>("copyDependencies") {
|
||||
val embedOnly: Configuration by configurations
|
||||
val shadowImplementation: Configuration by configurations
|
||||
|
||||
val dependencyClasspath: FileCollection = sourceSets.main.get().runtimeClasspath - sourceSets.main.get().output
|
||||
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -158,6 +107,7 @@ val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
|
||||
|
||||
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = files(stripModuleInfoFromShadow.map { it.archiveFile })
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -172,6 +122,7 @@ val glsmTestFixtures: Configuration by configurations.creating {
|
||||
|
||||
val downgradeGlsmTestFixturesForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = glsmTestFixtures
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -186,6 +137,9 @@ fun Test.configureAngelicaJava8() {
|
||||
}
|
||||
dependsOn(tasks.extractNatives2)
|
||||
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
|
||||
if (isMacOs) {
|
||||
jvmArgs("-Dapple.awt.UIElement=true")
|
||||
}
|
||||
testLogging { events("passed", "skipped", "failed") }
|
||||
|
||||
val swaps = mapOf(
|
||||
@@ -199,15 +153,50 @@ fun Test.configureAngelicaJava8() {
|
||||
}
|
||||
}
|
||||
|
||||
if (isMacOs && providers.gradleProperty("clientJvmVendor").isPresent) {
|
||||
for (javaVersion in listOf(17, 21, 25)) {
|
||||
tasks.named<JavaExec>("runClient$javaVersion") {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
vendor = JvmVendorSpec.matching(providers.gradleProperty("clientJvmVendor").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forge's asm-debug-all 5.0.3 cannot read the Java 25 class files ArchScan walks; this ASM has to resolve first.
|
||||
val archScanAsm: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
archScanAsm(libs.asm.tree.test)
|
||||
archScanAsm(libs.asm.commons.test)
|
||||
}
|
||||
|
||||
sourceSets.test {
|
||||
compileClasspath = archScanAsm + compileClasspath
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform { excludeTags = setOf("gl-core") }
|
||||
configureAngelicaJava8()
|
||||
classpath = archScanAsm + classpath
|
||||
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
|
||||
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
|
||||
}
|
||||
|
||||
val downgradeRootMainClasses = tasks.named<DowngradeFiles>("downgradeMainClasses")
|
||||
val downgradeRootTestClasses = tasks.named<DowngradeFiles>("downgradeTestClasses")
|
||||
|
||||
// JVMDowngrader only declares outputs for inputs that exist at configuration, so a clean tree has none.
|
||||
for (name in listOf("main", "mixin", "test")) {
|
||||
tasks.named<DowngradeFiles>("downgrade${name.replaceFirstChar { it.uppercase() }}Classes") {
|
||||
sourceSets[name].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
}
|
||||
}
|
||||
|
||||
val glCoreTest by tasks.registering(Test::class) {
|
||||
description = "Runs the gl-core tests in their own JVM with a real GL context."
|
||||
group = "verification"
|
||||
@@ -224,36 +213,31 @@ val glCoreTest by tasks.registering(Test::class) {
|
||||
.plus(sourceSets["test"].runtimeClasspath.minus(rawMain).minus(rawTest))
|
||||
|
||||
configureAngelicaJava8()
|
||||
mustRunAfter(tasks.test)
|
||||
}
|
||||
|
||||
tasks.test { finalizedBy(glCoreTest) }
|
||||
tasks.check { dependsOn(glCoreTest) }
|
||||
|
||||
tasks.shadowJar {
|
||||
dependsOn(embedOnly)
|
||||
from(embedOnly.map(::zipTree))
|
||||
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
mergeServiceFiles()
|
||||
|
||||
minimize {
|
||||
exclude(project(rootProject.path))
|
||||
exclude(project(":glsm"))
|
||||
exclude(project(":lwjgl3-backend"))
|
||||
exclude(project(":sdl-gpu"))
|
||||
exclude(dependency("org.taumc:.*:.*"))
|
||||
exclude(dependency("org.antlr:.*:.*"))
|
||||
val verifyTestsRan by tasks.registering
|
||||
listOf(tasks.test, glCoreTest).forEach { testTask ->
|
||||
val name = testTask.name
|
||||
val verify = tasks.register("verify${name.replaceFirstChar { it.uppercase() }}Ran") {
|
||||
val resultsDir = layout.buildDirectory.dir("test-results/$name")
|
||||
dependsOn(testTask)
|
||||
doLast {
|
||||
val dir = resultsDir.get().asFile
|
||||
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
|
||||
check(xmls.isNotEmpty()) { ":$name produced no TEST-*.xml in $dir - test task likely went NO-SOURCE" }
|
||||
}
|
||||
}
|
||||
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
relocate("com.gtnewhorizons.angelica.config", "com.gtnewhorizons.angelica.config")
|
||||
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
|
||||
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
|
||||
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
|
||||
verifyTestsRan.configure { dependsOn(verify) }
|
||||
}
|
||||
tasks.check { dependsOn(verifyTestsRan) }
|
||||
|
||||
apply(from = "gradle/angelica-shadow-common.gradle.kts")
|
||||
apply(from = "experimental-surround/surround.root.gradle.kts")
|
||||
|
||||
tasks.withType<TaskPublishCurseForge>().configureEach {
|
||||
uploadArtifacts.forEach { it.addGameVersion("Client") }
|
||||
}
|
||||
|
||||
tasks.withType<TaskModrinthUpload>().configureEach {
|
||||
setDependsOn(dependsOn.filterNot { it == "build" } + "assemble")
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.ArchiveOperations
|
||||
import org.gradle.api.tasks.SourceSetContainer
|
||||
import org.gradle.api.tasks.Sync
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.register
|
||||
import org.gradle.kotlin.dsl.the
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class UnpackLwjgl3Natives : Sync() {
|
||||
@get:Inject
|
||||
abstract val archives: ArchiveOperations
|
||||
}
|
||||
|
||||
// LWJGL extracts jar-packed natives to a shared temp dir with no cross-process safety; plain files it loads in place.
|
||||
fun Project.registerLwjgl3NativesUnpack(): TaskProvider<UnpackLwjgl3Natives> {
|
||||
val jars = the<SourceSetContainer>()["test"].runtimeClasspath.filter {
|
||||
it.name.startsWith("lwjgl") && it.name.contains("-natives-") && !it.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
return tasks.register<UnpackLwjgl3Natives>("unpackLwjgl3Natives") {
|
||||
val archives = archives
|
||||
from(jars.elements.map { files -> files.map { archives.zipTree(it) } })
|
||||
exclude("META-INF/**")
|
||||
into(layout.buildDirectory.dir("lwjgl3-natives"))
|
||||
}
|
||||
}
|
||||
|
||||
fun Test.loadLwjgl3NativesFrom(unpack: TaskProvider<UnpackLwjgl3Natives>) {
|
||||
classpath = project.files(unpack) + classpath
|
||||
}
|
||||
+13
-5
@@ -52,8 +52,8 @@ configurations.configureEach {
|
||||
}
|
||||
|
||||
DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
@@ -93,6 +93,10 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
|
||||
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
|
||||
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
|
||||
if (System.getProperty("os.name").lowercase().contains("mac")) {
|
||||
runtimeOnlyNonPublishable(project(":moltenvk")) { isTransitive = false }
|
||||
}
|
||||
devOnlyNonPublishable(libs.async.profiler)
|
||||
shadowImplementation(libs.eventbus)
|
||||
|
||||
shadowImplementation(libs.jcpp) // Apache 2.0
|
||||
@@ -104,7 +108,6 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
|
||||
// Celeritas (isTransitive=false: deps provided by GTNHLib/runtime)
|
||||
embedOnly(libs.celeritas.common) { isTransitive = false }
|
||||
embedOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.109-GTNH:dev")
|
||||
@@ -161,6 +164,9 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
|
||||
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:OpenBlocks:1.12.19-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Draconic-Evolution:1.5.30-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Chisel:2.17.31-GTNH:dev")
|
||||
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.6-GTNH:dev")
|
||||
@@ -175,7 +181,7 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.5-GTNH:dev")
|
||||
|
||||
// Distant Horizons
|
||||
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
|
||||
compileOnly(libs.distant.horizons.api)
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
@@ -198,12 +204,14 @@ afterEvaluate {
|
||||
testImplementation(libs.junit.platform.reporting)
|
||||
testImplementation(libs.junit.platform.launcher)
|
||||
|
||||
testImplementation(libs.archunit.junit5)
|
||||
testImplementation(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
|
||||
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
|
||||
testCompileOnly(testFixtures(project(":glsm")))
|
||||
testImplementation("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
testImplementation(libs.distant.horizons.api)
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
# A specification for models
|
||||
|
||||
🌐 **Languages:** [English](model-spec.md) | [Українська](model-spec_uk.md)
|
||||
|
||||
This is borrowed from [the unofficial Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) under the CC BY-NC-SA 3.0.
|
||||
|
||||
The folder `assets/<namespace>/models/block` holds the model files for all the specified variants. The names of the files can be changed, but must always correspond with the names used in the variant files.
|
||||
@@ -8,7 +11,7 @@ The folder `assets/<namespace>/models/block` holds the model files for all the s
|
||||
* Can be set to `"builtin/generated"` to use a model that is created out of the specified icon. Note that only the first layer is supported, and rotation can be achieved only by using block states files.
|
||||
* `[TAG_byte]` **ambientocclusion**: Whether to use [ambient occlusion](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` - default), or not (`false`). Note:only works on Parent file
|
||||
* `[TAG_object]` **display**: Holds the different places where item models are displayed.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. '''fixed''' refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. **'''fixed'''** refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_list]` **rotation**: Specifies the rotation of the model according to the scheme `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Specifies the position of the model according to the scheme `[x, y, z]`. The values are clamped between -80 and 80.
|
||||
* `[TAG_list]` **scale**: Specifies the scale of the model according to the scheme `[x, y, z]`. If the value is greater than 4, it is displayed as 4.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Специфікація моделей
|
||||
|
||||
🌐 **Мови:** [English](model-spec.md) | [Українська](model-spec_uk.md)
|
||||
|
||||
Запозичено з [неофіційної Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) на умовах ліцензії CC BY-NC-SA 3.0.
|
||||
|
||||
Папка `assets/<namespace>/models/block` містить файли моделей для усіх вказаних варіантів. Назви файлів можна змінювати, але вони завжди повинні відповідати назвам, що використовуються у файлах варіантів.
|
||||
|
||||
* `[TAG_compound]` Кореневий тег
|
||||
* `[TAG_string]` **parent**: Завантажує іншу модель із вказаного шляху у формі [локації ресурсу](https://minecraft.wiki/w/Tutorials/Models#File_path). Якщо задано і `"parent"`, і `"elements"`, тег `"elements"` перевизначає тег `"elements"` із попередньої моделі.
|
||||
* Може мати значення `"builtin/generated"`, щоб використовувати модель, створену із вказаної піктограми. Зверніть увагу: підтримується лише перший шар, а обертання можна досягти лише за допомогою файлів станів блоку.
|
||||
* `[TAG_byte]` **ambientocclusion**: Визначає, чи використовувати [розсіяне затінення](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` — за замовчуванням), чи ні (`false`). Примітка: працює лише у батьківському файлі.
|
||||
* `[TAG_object]` **display**: Містить різні місця відображення моделей предметів.
|
||||
* `[TAG_compound]` ***Position***: Має назву `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground` або `fixed`. Місце, де відображається модель предмета. Містить її обертання, зміщення та масштаб для вказаної ситуації. **'''fixed'''** стосується рамок для предметів, а решта відповідають своїм назвам. Зверніть увагу: зміщення застосовується до моделі перед обертанням.
|
||||
* `[TAG_list]` **rotation**: Задає обертання моделі за схемою `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Задає зміщення моделі за схемою `[x, y, z]`. Значення обмежені в діапазоні від -80 до 80.
|
||||
* `[TAG_list]` **scale**: Задає масштаб моделі за схемою `[x, y, z]`. Якщо значення більше за 4, воно відображається як 4.
|
||||
* `[TAG_compound]` **textures**: Містить текстури моделі у формі [локації ресурсу](https://minecraft.wiki/w/Tutorials/Models#File_path) або у вигляді іншої текстурної змінної.
|
||||
* `[TAG_string]` **particle**: Текстура, з якої завантажуються частинки. Ця текстура також використовується як оверлей, якщо ви перебуваєте в [порталі Незеру](https://minecraft.wiki/w/Nether_portal), а також для нерухомих текстур [води](https://minecraft.wiki/w/water) та [лави](https://minecraft.wiki/w/Lava).<sup>[[1]](https://bugs.mojang.com/browse/MC-240042)</sup> Ця текстура також вважається текстурною змінною, на яку можна посилатися як `"#particle"`. Примітка: Усі частинки руйнування для блоків без моделей вшиті в код, наприклад, для [бар’єрів](https://minecraft.wiki/w/Barrier).
|
||||
* `[TAG_string]` ***Texture variable***: Визначає текстурну змінну з ім’ям `name` та призначає її текстурі або іншій змінній.
|
||||
* `[TAG_list]` **elements**: Містить усі елементи моделі. Вони можуть мати лише кубічну форму. Якщо задано і `"parent"`, і `"elements"`, тег `"elements"` перевизначає тег `"elements"` із попередньої моделі.
|
||||
* `[TAG_compound]` Елемент.
|
||||
* `[TAG_list]` **from**: Початкова точка кубоїда за схемою `[x, y, z]`. Значення мають бути від -16 до 32.
|
||||
* `[TAG_list]` **to**: Кінцева точка кубоїда за схемою `[x, y, z]`. Значення мають бути від -16 до 32.
|
||||
* `[TAG_compound]` **rotation**: Визначає обертання елемента.
|
||||
* `[TAG_list]` **origin**: Встановлює центр обертання за схемою `[x, y, z]`.
|
||||
* `[TAG_string]` **axis**: Задає вісь обертання: може бути `"x"`, `"y"` або `"z"`.
|
||||
* `[TAG_float]` **angle**: Задає кут обертання. Може бути від 45 до -45 градусів із кроком у 22,5 градуса.
|
||||
* `[TAG_byte]` **rescale**: Визначає, чи масштабувати грані по всьому блоку. Може бути `true` або `false`. За замовчуванням — `false`.
|
||||
* `[TAG_byte]` **shade**: Визначає, чи рендерити тіні (`true` — за замовчуванням), чи ні (`false`).
|
||||
* `[TAG_compound]` **faces**: Містить усі грані кубоїда. Якщо грань пропущено, вона не рендериться.
|
||||
* `[TAG_compound]` ***Face***: Має назву `down`, `up`, `north`, `south`, `west` або `east`. Містить властивості вказаної грані.
|
||||
* `[TAG_list]` **uv**: Визначає ділянку текстури для використання за схемою `[x1, y1, x2, y2]`. Поведінка текстури стає непередбачуваною, якщо значення UV виходять за межі 0 або 16. Якщо значення `x1` та `x2` поміняти місцями (наприклад, з `0, 0, 16, 16` на `16, 0, 0, 16`), текстура віддзеркалюється. Параметр UV є необов’язковим; якщо його не вказано, він автоматично генерується на основі позиції елемента.
|
||||
* `[TAG_string]` **texture**: Вказує текстуру у формі текстурної змінної зі знаком `#` на початку.
|
||||
* `[TAG_string]` **cullface**: Визначає, чи не потрібно рендерити грань, якщо до неї в указаній позиції торкається інший блок. Позиція може бути: `down`, `up`, `north`, `south`, `west` або `east`. Цей параметр також визначає сторону блоку, з якої береться рівень освітлення для цієї грані; якщо параметр не задано, за замовчуванням використовується відповідна сторона.
|
||||
* `[TAG_int]` **rotation**: Обертає текстуру за годинниковою стрілкою на вказаний кут у градусах. Може бути 0, 90, 180 або 270. За замовчуванням — 0. Обертання не впливає на те, яка частина текстури використовується. Натомість воно зводиться до перестановки вершин вибраної текстури (вибраних неявно або явно через `uv`).
|
||||
* `[TAG_int]` **tintindex**: Визначає, чи потрібно тонувати текстуру за допомогою вшитого індексу відтінку. Значення за замовчуванням, -1, вказує на те, що відтінок не використовується. Будь-яке інше число передається в `BlockColors` для отримання значення відтінку, відповідного цьому індексу. Однак більшість блоків не мають визначеного значення відтінку (у такому разі використовується білий колір). Крім того, жоден ванільний блок наразі не використовує кілька значень відтінку, тому значення індексу відтінку ігнорується (якщо воно відмінне від -1); втім, його можна використовувати для модифікованих блоків, яким потрібні кілька різних значень відтінку в одному блоці.
|
||||
@@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
apply(from = rootProject.file("experimental-surround/surround.project.gradle.kts"))
|
||||
|
||||
val mixinProviderSpec = rootProject.extra["mixinProviderSpec"] as String
|
||||
|
||||
val refmapProbe = sourceSets.create("refmapProbe")
|
||||
|
||||
dependencies {
|
||||
compileOnly(mixinProviderSpec)
|
||||
testImplementation(mixinProviderSpec)
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(project(":experimental-surround-runtime"))
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
"refmapProbeCompileOnly"(mixinProviderSpec)
|
||||
"refmapProbeCompileOnly"(project(":experimental-surround-runtime"))
|
||||
"refmapProbeAnnotationProcessor"(mixinProviderSpec)
|
||||
"refmapProbeAnnotationProcessor"(sourceSets.main.get().output)
|
||||
}
|
||||
|
||||
val probeRefmaps = layout.buildDirectory.dir("refmap-probe")
|
||||
val probeSrg = file("src/refmapProbe/mappings.srg")
|
||||
val surroundAp = "com.gtnewhorizons.angelica.experimental.surround.ap.SurroundProcessor"
|
||||
val mixinAps = "org.spongepowered.tools.obfuscation.MixinObfuscationProcessorInjection,org.spongepowered.tools.obfuscation.MixinObfuscationProcessorTargets"
|
||||
|
||||
fun JavaCompile.probe(name: String, processors: String?) {
|
||||
val refmap = probeRefmaps.map { it.file("refmap-probe/$name.json") }
|
||||
val srg = probeSrg
|
||||
javaCompiler = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
outputs.file(refmap)
|
||||
inputs.file(srg)
|
||||
options.compilerArgumentProviders.add(CommandLineArgumentProvider {
|
||||
listOf("-proc:only", "-AoutRefMapFile=" + refmap.get().asFile.absolutePath,
|
||||
"-AreobfSrgFile=" + srg.absolutePath, "-AdefaultObfuscationEnv=searge") +
|
||||
(if (processors == null) emptyList() else listOf("-processor", processors))
|
||||
})
|
||||
}
|
||||
|
||||
fun JavaCompile.probeOrdered(name: String, classesDir: String, processors: String) {
|
||||
source = refmapProbe.java
|
||||
classpath = refmapProbe.compileClasspath
|
||||
options.annotationProcessorPath = refmapProbe.annotationProcessorPath
|
||||
destinationDirectory = layout.buildDirectory.dir("classes/$classesDir")
|
||||
probe(name, processors)
|
||||
}
|
||||
|
||||
tasks.named<JavaCompile>(refmapProbe.compileJavaTaskName) { probe("discovered", null) }
|
||||
val probeSurroundFirst by tasks.registering(JavaCompile::class) { probeOrdered("surround-first", "refmapProbeFirst", "$surroundAp,$mixinAps") }
|
||||
val probeSurroundLast by tasks.registering(JavaCompile::class) { probeOrdered("surround-last", "refmapProbeLast", "$mixinAps,$surroundAp") }
|
||||
|
||||
sourceSets.test { resources.srcDir(files(probeRefmaps).builtBy(refmapProbe.compileJavaTaskName, probeSurroundFirst, probeSurroundLast)) }
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.ap;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.tools.obfuscation.mirror.AnnotationHandle;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@SupportedAnnotationTypes("com.gtnewhorizons.angelica.experimental.surround.Surround")
|
||||
public class SurroundProcessor extends AbstractProcessor {
|
||||
|
||||
record Registration(TypeElement mixin, ExecutableElement method, AnnotationMirror surround) {
|
||||
}
|
||||
|
||||
private final List<Registration> registrations = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latestSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
if (roundEnv.processingOver()) {
|
||||
if (!this.registrations.isEmpty()) {
|
||||
register();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (TypeElement annotation : annotations) {
|
||||
for (Element annotated : roundEnv.getElementsAnnotatedWith(annotation)) {
|
||||
if (annotated.getEnclosingElement() instanceof TypeElement mixin && AnnotationHandle.of(mixin, Mixin.class).exists()) {
|
||||
for (AnnotationMirror mirror : annotated.getAnnotationMirrors()) {
|
||||
if (mirror.getAnnotationType().asElement().equals(annotation)) {
|
||||
this.registrations.add(new Registration(mixin, (ExecutableElement) annotated, mirror));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void register() {
|
||||
final ProcessingEnvironment env = this.processingEnv;
|
||||
try {
|
||||
// pinned by UniMixins 0.3.1; recheck on bump
|
||||
final Class<?> annotatedMixins = Class.forName("org.spongepowered.tools.obfuscation.AnnotatedMixins", false, AnnotationHandle.class.getClassLoader());
|
||||
final Method forEnvironment = annotatedMixins.getDeclaredMethod("getMixinsForEnvironment", ProcessingEnvironment.class);
|
||||
final Method registerInjector = annotatedMixins.getDeclaredMethod("registerInjector", TypeElement.class, ExecutableElement.class, AnnotationHandle.class);
|
||||
final Method writeReferences = annotatedMixins.getDeclaredMethod("writeReferences");
|
||||
forEnvironment.setAccessible(true);
|
||||
registerInjector.setAccessible(true);
|
||||
writeReferences.setAccessible(true);
|
||||
final Object mixins = forEnvironment.invoke(null, env);
|
||||
for (Registration registration : this.registrations) {
|
||||
registerInjector.invoke(mixins, registration.mixin(), registration.method(), AnnotationHandle.of(registration.surround()));
|
||||
}
|
||||
writeReferences.invoke(mixins);
|
||||
} catch (ReflectiveOperationException | LinkageError | RuntimeException e) {
|
||||
env.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Surround: refmap entries were not written: " + e + (e.getCause() == null ? "" : " caused by " + e.getCause()));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
com.gtnewhorizons.angelica.experimental.surround.ap.SurroundProcessor
|
||||
@@ -0,0 +1,20 @@
|
||||
package probe;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(RefmapTarget.class)
|
||||
class MixinRefmapTarget {
|
||||
@Surround(method = "run()V")
|
||||
private void wholeMethod() {}
|
||||
|
||||
@Surround.Finally
|
||||
private void always() {}
|
||||
|
||||
@Surround(method = "run(Ljava/lang/String;)V", id = "call", at = @At(value = "INVOKE", target = "Lprobe/RefmapTarget;helper()V"))
|
||||
private void callForm() {}
|
||||
|
||||
@Surround.Finally("call")
|
||||
private void alwaysCall() {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package probe;
|
||||
|
||||
public class RefmapTarget {
|
||||
void run() {}
|
||||
void run(String s) { helper(); }
|
||||
void helper() {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
MD: probe/RefmapTarget/run ()V probe/RefmapTarget/func_1_a ()V
|
||||
MD: probe/RefmapTarget/run (Ljava/lang/String;)V probe/RefmapTarget/func_2_b (Ljava/lang/String;)V
|
||||
MD: probe/RefmapTarget/helper ()V probe/RefmapTarget/func_3_c ()V
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.ap;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class RefmapRegistrationTest {
|
||||
|
||||
private static final Pattern ENTRY = Pattern.compile("\"([^\"]+)\": \"([^\"]+)\"");
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"discovered", "surround-first", "surround-last"})
|
||||
void surroundTargetsAreRemappedInTheRefmap(String variant) throws IOException {
|
||||
final Map<String, String> expected = new HashMap<>();
|
||||
expected.put("run()V", "Lprobe/RefmapTarget;func_1_a()V");
|
||||
expected.put("run(Ljava/lang/String;)V", "Lprobe/RefmapTarget;func_2_b(Ljava/lang/String;)V");
|
||||
expected.put("Lprobe/RefmapTarget;helper()V", "Lprobe/RefmapTarget;func_3_c()V");
|
||||
assertEquals(expected, mappings(variant, "probe/MixinRefmapTarget"));
|
||||
}
|
||||
|
||||
private static Map<String, String> mappings(String variant, String mixin) throws IOException {
|
||||
final String json = new String(RefmapRegistrationTest.class.getResourceAsStream("/refmap-probe/" + variant + ".json").readAllBytes(), UTF_8);
|
||||
final int start = json.indexOf('{', json.indexOf("\"" + mixin + "\"", json.indexOf("\"mappings\"")));
|
||||
final Matcher m = ENTRY.matcher(json.substring(start, json.indexOf('}', start)));
|
||||
final Map<String, String> entries = new HashMap<>();
|
||||
while (m.find()) entries.put(m.group(1), m.group(2));
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
apply(plugin = "xyz.wagyourtail.jvmdowngrader")
|
||||
|
||||
apply(from = rootProject.file("experimental-surround/surround.project.gradle.kts"))
|
||||
|
||||
// Required by RFG consumers (runClient17 java17Dependencies) to pick a variant.
|
||||
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
|
||||
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
|
||||
|
||||
configurations {
|
||||
listOf("apiElements", "runtimeElements").forEach { name ->
|
||||
named(name) {
|
||||
attributes {
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mixinProviderSpec = rootProject.extra["mixinProviderSpec"] as String
|
||||
|
||||
dependencies {
|
||||
compileOnly(mixinProviderSpec)
|
||||
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(mixinProviderSpec)
|
||||
testImplementation(libs.launchwrapper)
|
||||
testImplementation(libs.commons.io)
|
||||
testImplementation(libs.guava)
|
||||
testImplementation(libs.log4j.core)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "WagYourTail"
|
||||
url = uri("https://maven.wagyourtail.xyz/releases")
|
||||
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
outputs.cacheIf { true }
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
logLevel.set("FATAL")
|
||||
}
|
||||
|
||||
fun SourceSet.registerDowngrade(taskName: String) = tasks.register<DowngradeFiles>(taskName) {
|
||||
inputCollection = output.classesDirs
|
||||
classpath = compileClasspath
|
||||
output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
dependsOn(tasks.named(classesTaskName))
|
||||
}
|
||||
|
||||
val downgradeMainClasses = sourceSets["main"].registerDowngrade("downgradeMainClasses")
|
||||
val downgradeTestClasses = sourceSets["test"].registerDowngrade("downgradeTestClasses")
|
||||
|
||||
val goldenDir = layout.projectDirectory.dir("src/test/resources/golden").asFile.absolutePath
|
||||
|
||||
// Runs on Java 8 against the JVMDowngrader output, the bytecode production ships.
|
||||
fun Test.configureSurroundTest(runDirName: String) {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (System.getProperty("os.name").lowercase().contains("mac") && System.getProperty("os.arch") == "aarch64") {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
dependsOn(downgradeMainClasses, downgradeTestClasses)
|
||||
val downgradedTest = files(downgradeTestClasses.map { it.outputCollection })
|
||||
testClassesDirs = downgradedTest
|
||||
classpath = downgradedTest
|
||||
.plus(files(downgradeMainClasses.map { it.outputCollection }))
|
||||
.plus(sourceSets["test"].runtimeClasspath.minus(sourceSets["main"].output.classesDirs).minus(sourceSets["test"].output.classesDirs))
|
||||
val dir = layout.buildDirectory.dir(runDirName).get().asFile
|
||||
workingDir = dir
|
||||
systemProperty("surround.golden.dir", goldenDir)
|
||||
systemProperty("mixin.debug.verify", "true")
|
||||
val javac = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-Dsurround.javac=" + javac.get().executablePath.asFile.absolutePath) })
|
||||
doFirst { dir.mkdirs() }
|
||||
}
|
||||
|
||||
fun Test.configureUntaggedSurroundTest(runDirName: String, vararg systemProps: Pair<String, String>) {
|
||||
useJUnitPlatform { excludeTags("mixinextras") }
|
||||
for ((key, value) in systemProps) {
|
||||
systemProperty(key, value)
|
||||
}
|
||||
configureSurroundTest(runDirName)
|
||||
}
|
||||
|
||||
fun Test.configureTaggedSurroundTest(runDirName: String, tag: String, vararg systemProps: Pair<String, String>) {
|
||||
useJUnitPlatform { includeTags(tag) }
|
||||
for ((key, value) in systemProps) {
|
||||
systemProperty(key, value)
|
||||
}
|
||||
configureSurroundTest(runDirName)
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
configureUntaggedSurroundTest("test-run")
|
||||
}
|
||||
|
||||
val mixinExtrasTest by tasks.registering(Test::class) {
|
||||
configureTaggedSurroundTest(
|
||||
"test-run-mixinextras",
|
||||
"mixinextras",
|
||||
"surround.test.mixinextras" to "true"
|
||||
)
|
||||
}
|
||||
|
||||
val mixinExtrasAdditionsFirstTest by tasks.registering(Test::class) {
|
||||
configureTaggedSurroundTest(
|
||||
"test-run-mixinextras-additions-first",
|
||||
"mixinextras",
|
||||
"surround.test.mixinextras" to "true",
|
||||
"surround.test.additionsFirst" to "true"
|
||||
)
|
||||
}
|
||||
|
||||
val obfTest by tasks.registering(Test::class) {
|
||||
configureUntaggedSurroundTest("test-run-obf", "surround.test.obf" to "true")
|
||||
filter { excludeTestsMatching("*.RejectionTest") }
|
||||
}
|
||||
|
||||
tasks.named("check") {
|
||||
dependsOn(mixinExtrasTest, mixinExtrasAdditionsFirstTest, obfTest)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
|
||||
record CallSiteSpec(MethodNode target, InjectionNode node, MethodNode entry, String entryDesc, Type[] operands, Type result, Exits exits, String description, int[] entryLocals, int[][] locals) {}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Experimental and internal to Angelica; the API may change without notice.
|
||||
* <p>
|
||||
* Surrounds one method call ({@link #at()} set) or a whole method ({@link #at()} empty) with try/catch/finally.
|
||||
* The call form replaces MixinExtras' {@code @WrapOperation} and the whole-method form replaces {@code @WrapMethod},
|
||||
* without their per-call {@code Operation} and {@code Object[]} allocation. Use it when the original runs at most once
|
||||
* with its original arguments.
|
||||
* <p>
|
||||
* A surround can:
|
||||
* <ul>
|
||||
* <li>run code before the original: the entry handler, the annotated method itself;</li>
|
||||
* <li>hand state from that code to everything after it: {@link Carry @Surround.Carry} locals;</li>
|
||||
* <li>read the call's operands, the target's parameters, and locals at the call: operands,
|
||||
* {@link Local @Surround.Local};</li>
|
||||
* <li>skip the original and supply its result instead: {@link Skip @Surround.Skip} + {@link Skipped @Surround.Skipped};</li>
|
||||
* <li>replace the result after a normal completion: {@link Return @Surround.Return};</li>
|
||||
* <li>react to an exception, rethrowing or (calls only) swallowing it and supplying the result:
|
||||
* {@link Catch @Surround.Catch};</li>
|
||||
* <li>run code on every way out: {@link Finally @Surround.Finally}.</li>
|
||||
* </ul>
|
||||
* It cannot call the original twice or with different arguments.
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
* <pre>
|
||||
* @Surround(method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V")
|
||||
* private void enter(EntityItem entity, double x, double y, double z, float yaw, float partialTicks) {
|
||||
* @Surround.Carry int prevItemId = ItemIdManager.getItemId();
|
||||
* ItemIdManager.setItemId(entity.getEntityItem());
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void restore(@Surround.Carry int prevItemId) {
|
||||
* ItemIdManager.setItemIdRaw(prevItemId);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Handlers</h2>
|
||||
* The annotated method is the entry handler. Exit handlers are methods in the same mixin whose {@code value} is this
|
||||
* surround's {@link #id()}:
|
||||
* <pre>
|
||||
* @Surround void enter(operands, @Local params) before the original
|
||||
* @Surround.Skipped T skipped(operands, @Local params, @Carry params) instead of the original
|
||||
* @Surround.Return T ret(T result, operands, @Local params, @Carry params) after it completes normally
|
||||
* @Surround.Catch void caught(E error, operands, @Local params, @Carry params) when it throws E
|
||||
* @Surround.Finally void always(operands, @Local params, @Carry params) on every way out
|
||||
* </pre>
|
||||
* {@code T} is the return type of the call or method. When it is {@code void}, {@code ret} takes no result and
|
||||
* {@code Skipped} returns {@code void}.
|
||||
* <ul>
|
||||
* <li>The entry handler returns {@code void}. Exit handlers are static exactly when the entry handler is.</li>
|
||||
* <li>Per surround: at most one {@code Skipped}, one {@code Return} and one {@code Finally}, and one {@code Catch} per
|
||||
* exception type. A method has one role.</li>
|
||||
* <li>{@link #id()} is required when the mixin declares more than one {@code @Surround}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Parameters</h2>
|
||||
* Declare them in this order, each group optional:
|
||||
* <ol>
|
||||
* <li>Operands, as a leading prefix. For a call: the receiver (unless static), then the arguments. A reference
|
||||
* operand may be declared as a supertype. For a whole method: the target's parameters.</li>
|
||||
* <li>{@link Local @Surround.Local} parameters (call form only). Exit handlers see the value as of entry.</li>
|
||||
* <li>{@link Carry @Surround.Carry} parameters (exit handlers only).</li>
|
||||
* </ol>
|
||||
*
|
||||
* <h2>Carrying state</h2>
|
||||
* Exit handlers see entry-handler locals only through {@link Carry @Surround.Carry}; see there for the rules. Two
|
||||
* carries of one kind are told apart by name:
|
||||
* <pre>
|
||||
* @Surround(method = ..., at = @At(value = "INVOKE", target = ...))
|
||||
* private void enter() {
|
||||
* @Surround.Carry("depth") int depth = GLStateManager.pushState(StateSet.BLEND);
|
||||
* @Surround.Carry("prev") int prevProgram = GLStateManager.getActiveProgram();
|
||||
* @Surround.Carry Class<?> prevRenderable = TesrAttribution.currentRenderable;
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void exit(@Surround.Carry("depth") int depth, @Surround.Carry("prev") int prevProgram,
|
||||
* @Surround.Carry Class<?> prevRenderable) { ... }
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Entry-handler body</h2>
|
||||
* <ul>
|
||||
* <li>Every path must fall through to the end: no early {@code return}. Branch with {@code if} instead.</li>
|
||||
* <li>Do not assign its parameters. To change arguments, use {@code @ModifyVariable} or {@code @WrapMethod}.</li>
|
||||
* <li>In the call form it is inlined at every matched call, as are the exit-handler calls. Keep it short: move bulky
|
||||
* logic into a helper and carry the helper's result. Huge targets stop being JIT-compiled past 8000 bytes.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Skipping</h2>
|
||||
* A {@link Skip @Surround.Skip} local in the entry handler decides whether the original runs; see there for the rules.
|
||||
*
|
||||
* <h2>Order and exceptions</h2>
|
||||
* <ol>
|
||||
* <li>The entry handler. If it throws, nothing else runs.</li>
|
||||
* <li>The original, or {@link Skipped} when skipped.</li>
|
||||
* <li>{@link Return}, only if the original completed normally (not after {@code Skipped}). Its return value replaces
|
||||
* the result.</li>
|
||||
* <li>{@link Finally}, on every way out: normal, after a catch, or rethrowing.</li>
|
||||
* </ol>
|
||||
* {@link Catch} covers step 2, not {@code Return}, and only the most specific matching one runs. It rethrows when
|
||||
* it returns, unless {@link Catch#handle()} is set (call form only): the exception then ends there, and the
|
||||
* handler's return value becomes the call's result.
|
||||
*
|
||||
* <h2>Whole-method form</h2>
|
||||
* <ul>
|
||||
* <li>The original body becomes a private {@code name$surround} method, so stack traces gain that frame.</li>
|
||||
* <li>A {@code synchronized} target holds its lock across the handlers too.</li>
|
||||
* <li>An entry handler that calls the target goes through the surround again.</li>
|
||||
* <li>{@code @Local}, {@link #slice()} and {@code handle} are call-form only.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>With other injectors</h2>
|
||||
* <ul>
|
||||
* <li>A call surround encloses a {@code @Redirect} or {@code @WrapOperation} on the same call. A whole-method surround
|
||||
* encloses every other injection in the method, including call surrounds.</li>
|
||||
* <li>Several surrounds on one call or method nest, lowest priority innermost. An outer skip skips the inner ones
|
||||
* entirely, and {@code Return} replacements chain from inner to outer.</li>
|
||||
* <li>A matched call that another injector removed is skipped with a warning.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Surround {
|
||||
|
||||
String[] method() default {};
|
||||
|
||||
/**
|
||||
* Calls to surround, as INVOKE-style {@code @At} without shift or by; each match is surrounded separately. Empty
|
||||
* surrounds the whole method.
|
||||
*/
|
||||
At[] at() default {};
|
||||
|
||||
/**
|
||||
* As for {@code @Inject}; calls only.
|
||||
*/
|
||||
Slice[] slice() default {};
|
||||
|
||||
/**
|
||||
* Names this surround for its exit handlers. Required when the mixin declares more than one {@code @Surround};
|
||||
* unique per mixin.
|
||||
*/
|
||||
String id() default "";
|
||||
|
||||
boolean remap() default true;
|
||||
|
||||
/**
|
||||
* As for {@code @Inject}, as are {@link #expect()} and {@link #allow()}; each surrounded call or whole target counts once.
|
||||
*/
|
||||
int require() default -1;
|
||||
|
||||
int expect() default 1;
|
||||
|
||||
int allow() default -1;
|
||||
|
||||
/**
|
||||
* Carries a value from the entry handler to the exit handlers.
|
||||
* <p>
|
||||
* Annotate a local in the entry handler, then declare a parameter with the same annotation in any exit handler that
|
||||
* needs the value:
|
||||
* <pre>
|
||||
* @Surround(method = ...)
|
||||
* private void enter() {
|
||||
* @Surround.Carry int prevItemId = ItemIdManager.getItemId();
|
||||
* ItemIdManager.setItemId(...);
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void restore(@Surround.Carry int prevItemId) {
|
||||
* ItemIdManager.setItemIdRaw(prevItemId);
|
||||
* }
|
||||
* </pre>
|
||||
* Declaring the local:
|
||||
* <ul>
|
||||
* <li>Annotate the declaration itself: {@code @Surround.Carry int x = ...}. Not a parameter of the entry handler
|
||||
* (use operands for those), and not part of the type (e.g. an array component).</li>
|
||||
* <li>Declare it at the top level of the entry handler and initialize it there. It must still be in scope and
|
||||
* definitely assigned when the entry handler ends.</li>
|
||||
* </ul>
|
||||
* Receiving it:
|
||||
* <ul>
|
||||
* <li>Carry parameters come last, after operands and {@code @Local} parameters. An exit handler may take any
|
||||
* subset.</li>
|
||||
* <li>Parameters bind to locals by kind: reference, {@code int}-like ({@code boolean}, {@code byte},
|
||||
* {@code char}, {@code short}, {@code int}), {@code long}, {@code float}, {@code double}. If the entry handler
|
||||
* carries two locals of one kind, name every one of that kind with the same {@link #value()} on the local and on
|
||||
* each parameter that reads it: {@code @Surround.Carry("depth") int depth}. Names are unique within a handler.</li>
|
||||
* <li>A reference is cast to the parameter's type on read. Every exit handler must read a given carry as the same
|
||||
* type.</li>
|
||||
* </ul>
|
||||
* Each carry lives in a local slot of the target method, with no allocation, so nested and recursive invocations
|
||||
* keep separate values. The entry handler returns {@code void}; carry its results instead.
|
||||
*/
|
||||
@Target({ElementType.TYPE_USE, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Carry {
|
||||
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether the original runs.
|
||||
* <p>
|
||||
* Annotate one {@code boolean} or {@code int}-like local in the entry handler, declared like a {@link Carry}: at
|
||||
* the top level, initialized at its declaration, in scope at the end:
|
||||
* <pre>
|
||||
* @Surround(method = ..., at = @At(value = "INVOKE", target = ...))
|
||||
* private void enter(RenderBlocks renderer, Block block) {
|
||||
* @Surround.Skip boolean skip = FallingBlockRendering.isActive();
|
||||
* }
|
||||
*
|
||||
* @Surround.Skipped
|
||||
* private boolean replacement(RenderBlocks renderer, Block block) { ... }
|
||||
* </pre>
|
||||
* <ul>
|
||||
* <li>If the local is nonzero when the entry handler ends, the original does not run and {@link Skipped} runs in
|
||||
* its place; {@link Return} does not run after it, {@link Catch} and {@link Finally} still do.</li>
|
||||
* <li>{@link Skipped} is required unless the result is {@code void}, and rejected without a {@code Skip}
|
||||
* local.</li>
|
||||
* <li>At most one per entry handler.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Target(ElementType.TYPE_USE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Skip {
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies the result in place of a skipped original. Required unless that result is {@code void}.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Skipped {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs after the original completes normally. Takes the result first (none for {@code void}) and returns its
|
||||
* replacement.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Return {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the original or {@link Skipped} throws the first parameter's type, unless a more specific
|
||||
* {@code Catch} matches. Rethrows when it returns.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Catch {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Call form only: the exception ends here and the returned value becomes the call's result.
|
||||
*/
|
||||
boolean handle() default false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on every way out.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Finally {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* As MixinExtras' {@code @Local}, read at the call; exit handlers see its value at entry. Calls only.
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Local {
|
||||
|
||||
int ordinal() default -1;
|
||||
|
||||
int index() default -1;
|
||||
|
||||
String[] name() default {};
|
||||
|
||||
boolean argsOnly() default false;
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.llamalad7.mixinextras.utils.MixinInternals;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.IExtension;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class SurroundApplicatorExtension implements IExtension {
|
||||
|
||||
private static final String MIXINEXTRAS_PACKAGE = "com.llamalad7.mixinextras.";
|
||||
private static final Pattern HANDLER_NAME = Pattern.compile("surround\\$[a-z]{3,}[0-9a-f]{3,}\\$.*");
|
||||
|
||||
private final Map<ITargetClassContext, Pending> pending = new WeakHashMap<>();
|
||||
|
||||
private record Pending(List<CallSiteSpec> calls, List<CallSiteSpec> wholes, Set<String> declarers) {}
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
static final SurroundApplicatorExtension INSTANCE = create();
|
||||
|
||||
private static SurroundApplicatorExtension create() {
|
||||
final SurroundApplicatorExtension extension = new SurroundApplicatorExtension();
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
MixinInternals.registerExtension(extension, false);
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
|
||||
static SurroundApplicatorExtension instance() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
void declare(ITargetClassContext context, String mixin) {
|
||||
this.pending(context).declarers.add(mixin);
|
||||
}
|
||||
|
||||
void offer(ITargetClassContext context, CallSiteSpec spec) {
|
||||
final Pending pending = this.pending(context);
|
||||
(spec.node() == null ? pending.wholes : pending.calls).add(spec);
|
||||
}
|
||||
|
||||
private Pending pending(ITargetClassContext context) {
|
||||
return this.pending.computeIfAbsent(context, key -> new Pending(new ArrayList<>(), new ArrayList<>(), new HashSet<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkActive(MixinEnvironment environment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preApply(ITargetClassContext context) {
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
final List<IExtension> active = MixinInternals.getExtensions().getActiveExtensions();
|
||||
boolean seen = false;
|
||||
for (int i = 0, n = active.size(); i < n; i++) {
|
||||
final IExtension extension = active.get(i);
|
||||
if (extension == this) {
|
||||
seen = true;
|
||||
} else if (seen && extension.getClass().getName().startsWith(MIXINEXTRAS_PACKAGE)) {
|
||||
MixinInternals.unregisterExtension(this);
|
||||
MixinInternals.registerExtension(this, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postApply(ITargetClassContext context) {
|
||||
final ClassNode targetClass = context.getClassNode();
|
||||
final Pending pending = this.pending.remove(context);
|
||||
checkHandlersRenamed(targetClass, pending == null ? Set.of() : pending.declarers);
|
||||
if (pending == null) {
|
||||
return;
|
||||
}
|
||||
final List<CallSiteSpec> calls = pending.calls;
|
||||
for (int i = 0, n = calls.size(); i < n; i++) {
|
||||
SurroundCallSite.apply(targetClass, calls.get(i));
|
||||
}
|
||||
final List<CallSiteSpec> wholes = pending.wholes;
|
||||
for (int i = 0, n = wholes.size(); i < n; i++) {
|
||||
SurroundCallSite.apply(targetClass, wholes.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(MixinEnvironment env, String name, boolean force, ClassNode classNode) {
|
||||
}
|
||||
|
||||
static void checkHandlersRenamed(ClassNode classNode, Set<String> declarers) {
|
||||
for (MethodNode method : classNode.methods) {
|
||||
if (method.visibleAnnotations == null) {
|
||||
continue;
|
||||
}
|
||||
for (AnnotationNode annotation : method.visibleAnnotations) {
|
||||
final String desc = annotation.desc;
|
||||
if (SurroundSpec.CATCH.equals(desc) || SurroundSpec.FINALLY.equals(desc) || SurroundSpec.SKIPPED.equals(desc) || SurroundSpec.RETURN.equals(desc)) {
|
||||
final String mixin = SurroundSpec.mergedBy(method);
|
||||
if (mixin != null && !declarers.contains(mixin)) {
|
||||
throw new IllegalStateException("@Surround " + SurroundBinding.exitRef(method) + " " + SurroundInjector.noSurroundForId(Annotations.<String>getValue(annotation, "value", ""), Set.of()));
|
||||
}
|
||||
} else if (SurroundSpec.SURROUND.equals(desc) && !HANDLER_NAME.matcher(method.name).matches()) {
|
||||
throw new IllegalStateException(method.name + method.desc + " in " + classNode.name + " (" + SurroundSpec.describeOwner(method) + ") carries @Surround but was never parsed as an injector: the InjectionInfo registry lacked @Surround when mixins were applied to this target; SurroundBootstrap.init() must run before any class a @Surround targets is transformed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.TypePath;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.FrameNode;
|
||||
import org.spongepowered.asm.lib.tree.IincInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableAnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.VarInsnNode;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
final class SurroundBinding {
|
||||
|
||||
private static final int TOP = -1;
|
||||
private static final String[] SUGARS = {"Local", "Share", "Cancellable"};
|
||||
static final String SUGAR_HINT = ", which a @Surround handler cannot resolve; use @Surround.Carry, or @Surround.Local on a call";
|
||||
|
||||
enum Role {
|
||||
ENTRY(SurroundSpec.SURROUND), CATCH(SurroundSpec.CATCH), FINALLY(SurroundSpec.FINALLY), SKIPPED(SurroundSpec.SKIPPED), RETURN(SurroundSpec.RETURN);
|
||||
|
||||
static final Role[] EXITS = {CATCH, FINALLY, SKIPPED, RETURN};
|
||||
|
||||
final String annotation;
|
||||
|
||||
Role(String annotation) {
|
||||
this.annotation = annotation;
|
||||
}
|
||||
}
|
||||
|
||||
record Load(int slot, Type type) {}
|
||||
|
||||
static final class Handler {
|
||||
|
||||
final MethodNode method;
|
||||
final Role role;
|
||||
final Type[] params;
|
||||
final boolean isStatic;
|
||||
final boolean handles;
|
||||
final int leading;
|
||||
final int localStart;
|
||||
final int carryStart;
|
||||
private final String[] carryIds;
|
||||
int index;
|
||||
Load[] loads;
|
||||
|
||||
Handler(MethodNode method, Role role, int leading) {
|
||||
this.method = method;
|
||||
this.role = role;
|
||||
this.params = Type.getArgumentTypes(method.desc);
|
||||
this.isStatic = Bytecode.isStatic(method);
|
||||
final AnnotationNode caught = role == Role.CATCH ? Annotations.get(method.visibleAnnotations, SurroundSpec.CATCH) : null;
|
||||
this.handles = caught != null && Annotations.<Boolean>getValue(caught, "handle", Boolean.FALSE).booleanValue();
|
||||
this.leading = leading;
|
||||
final int count = this.params.length;
|
||||
int firstLocal = count;
|
||||
int firstCarry = count;
|
||||
int section = 0;
|
||||
String[] ids = null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final AnnotationNode carry = carry(method, i);
|
||||
final boolean local = local(method, i) != null;
|
||||
if (carry != null && role == Role.ENTRY) {
|
||||
throw this.reject("has @Surround.Carry on " + paramRef(method, i) + "; only the entry handler's own locals can be carried");
|
||||
}
|
||||
if (local && carry != null) {
|
||||
throw this.reject(paramRef(method, i) + " cannot be both @Surround.Local and @Surround.Carry");
|
||||
}
|
||||
if ((local || carry != null) && i < leading) {
|
||||
throw this.reject("cannot mark its " + (role == Role.CATCH ? "caught Throwable" : "result") + " parameter @Surround." + (local ? "Local" : "Carry"));
|
||||
}
|
||||
final int next = carry != null ? 2 : local ? 1 : 0;
|
||||
if (next < section) {
|
||||
throw this.reject(paramRef(method, i) + " is out of order; declare operands first, then @Surround.Local, then @Surround.Carry parameters");
|
||||
}
|
||||
section = next;
|
||||
if (local && firstLocal == count) {
|
||||
firstLocal = i;
|
||||
}
|
||||
if (carry != null) {
|
||||
if (ids == null) {
|
||||
firstCarry = i;
|
||||
ids = new String[count - i];
|
||||
}
|
||||
ids[i - firstCarry] = Annotations.<String>getValue(carry, "value", "");
|
||||
}
|
||||
}
|
||||
this.localStart = Math.min(firstLocal, firstCarry);
|
||||
this.carryStart = firstCarry;
|
||||
this.carryIds = ids;
|
||||
}
|
||||
|
||||
Type caught() {
|
||||
return this.params[0];
|
||||
}
|
||||
|
||||
String ref() {
|
||||
return this.role == Role.ENTRY ? "entry handler" : exitRef(this.method);
|
||||
}
|
||||
|
||||
SurroundRejection reject(String message) {
|
||||
return new SurroundRejection(this.ref() + " " + message);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Exits {
|
||||
|
||||
final Handler[] handlers;
|
||||
final int catches;
|
||||
final Handler finallyHandler;
|
||||
final Handler skipped;
|
||||
final Handler returnHandler;
|
||||
final boolean handles;
|
||||
final int skipSlot;
|
||||
final Type[] carried;
|
||||
final int argsSize;
|
||||
|
||||
Exits(Handler[] handlers, int skipSlot, Type[] carried) {
|
||||
this.handlers = handlers;
|
||||
int catches = 0;
|
||||
Handler finallyHandler = null;
|
||||
Handler skipped = null;
|
||||
Handler returnHandler = null;
|
||||
boolean handles = false;
|
||||
int argsSize = 0;
|
||||
for (int i = 0; i < handlers.length; i++) {
|
||||
final Handler handler = handlers[i];
|
||||
handler.index = i;
|
||||
handles |= handler.handles;
|
||||
argsSize = Math.max(argsSize, (handler.isStatic ? 0 : 1) + Bytecode.getArgsSize(handler.params));
|
||||
switch (handler.role) {
|
||||
case CATCH -> catches++;
|
||||
case FINALLY -> finallyHandler = handler;
|
||||
case SKIPPED -> skipped = handler;
|
||||
case RETURN -> returnHandler = handler;
|
||||
}
|
||||
}
|
||||
this.catches = catches;
|
||||
this.finallyHandler = finallyHandler;
|
||||
this.skipped = skipped;
|
||||
this.returnHandler = returnHandler;
|
||||
this.handles = handles;
|
||||
this.skipSlot = skipSlot;
|
||||
this.carried = carried;
|
||||
this.argsSize = argsSize;
|
||||
}
|
||||
}
|
||||
|
||||
private SurroundBinding() {
|
||||
}
|
||||
|
||||
static Type[] bind(MethodNode entry, int[] kinds, Handler[] handlers) {
|
||||
checkNoParameterStores(entry);
|
||||
final List<Carried> carried = carriedLocals(entry, kinds);
|
||||
final Type[] slots = new Type[entry.maxLocals];
|
||||
for (Handler exit : handlers) {
|
||||
final Load[] loads = new Load[exit.params.length - exit.carryStart];
|
||||
for (int i = exit.carryStart; i < exit.params.length; i++) {
|
||||
loads[i - exit.carryStart] = resolve(carried, slots, exit, i);
|
||||
}
|
||||
exit.loads = loads;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
static int skipSlot(MethodNode entry, int[] kinds) {
|
||||
final List<LocalVariableAnnotationNode> annotations = entry.visibleLocalVariableAnnotations;
|
||||
int slot = -1;
|
||||
for (int a = 0, n = annotations == null ? 0 : annotations.size(); a < n; a++) {
|
||||
final LocalVariableAnnotationNode annotation = annotations.get(a);
|
||||
if (!SurroundSpec.SKIP.equals(annotation.desc)) {
|
||||
continue;
|
||||
}
|
||||
if (slot != -1) {
|
||||
throw new SurroundRejection("declares two @Surround.Skip locals; there can only be one");
|
||||
}
|
||||
slot = annotatedSlot(entry, annotation, 0);
|
||||
checkLocalAnnotation(entry, kinds, "@Surround.Skip", annotation, slot, "");
|
||||
if (kinds[slot] != Type.INT) {
|
||||
throw new SurroundRejection("has a @Surround.Skip local that is " + withArticle(kinds[slot]) + "; it must be boolean or int-like");
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
private static AnnotationNode carry(MethodNode method, int index) {
|
||||
final List<AnnotationNode> invisible = parameter(method.invisibleParameterAnnotations, index);
|
||||
for (int i = 0, n = invisible == null ? 0 : invisible.size(); i < n; i++) {
|
||||
final String name = Type.getType(invisible.get(i).desc).getClassName();
|
||||
for (String sugar : SUGARS) {
|
||||
if (name.endsWith("mixinextras.sugar." + sugar)) {
|
||||
throw new SurroundRejection(paramRef(method, index) + " of '" + method.name + "' is annotated @" + sugar + SUGAR_HINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Annotations.get(parameter(method.visibleParameterAnnotations, index), SurroundSpec.CARRY);
|
||||
}
|
||||
|
||||
static AnnotationNode local(MethodNode method, int index) {
|
||||
return Annotations.get(parameter(method.visibleParameterAnnotations, index), SurroundSpec.LOCAL);
|
||||
}
|
||||
|
||||
private static List<AnnotationNode> parameter(List<AnnotationNode>[] table, int index) {
|
||||
return table == null || index >= table.length ? null : table[index];
|
||||
}
|
||||
|
||||
static String paramRef(MethodNode method, int index) {
|
||||
final String name = paramName(method, index);
|
||||
return name == null || name.isEmpty() ? "parameter " + index : "parameter " + index + " ('" + name + "')";
|
||||
}
|
||||
|
||||
static String paramName(MethodNode method, int index) {
|
||||
if (method.localVariables == null) {
|
||||
return null;
|
||||
}
|
||||
final int slot = (Bytecode.isStatic(method) ? 0 : 1) + Bytecode.getArgsSize(Type.getArgumentTypes(method.desc), 0, index);
|
||||
LocalVariableNode earliest = null;
|
||||
int earliestPos = Integer.MAX_VALUE;
|
||||
for (LocalVariableNode local : method.localVariables) {
|
||||
if (local.index != slot) {
|
||||
continue;
|
||||
}
|
||||
final int pos = method.instructions.indexOf(local.start);
|
||||
if (pos < earliestPos) {
|
||||
earliestPos = pos;
|
||||
earliest = local;
|
||||
}
|
||||
}
|
||||
return earliest == null ? null : earliest.name;
|
||||
}
|
||||
|
||||
static AbstractInsnNode soleReturn(MethodNode entry) {
|
||||
AbstractInsnNode sole = null;
|
||||
AbstractInsnNode last = null;
|
||||
int returns = 0;
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn.getOpcode() < 0) {
|
||||
continue;
|
||||
}
|
||||
last = insn;
|
||||
if (insn.getOpcode() == Opcodes.RETURN) {
|
||||
sole = insn;
|
||||
returns++;
|
||||
}
|
||||
}
|
||||
if (returns != 1 || sole != last) {
|
||||
throw new SurroundRejection("must reach its end to hand over to the body; restructure it so every path falls through to its end, with no early return, throw or endless loop");
|
||||
}
|
||||
return sole;
|
||||
}
|
||||
|
||||
private static Load resolve(List<Carried> carried, Type[] slots, Handler exit, int index) {
|
||||
final Type type = exit.params[index];
|
||||
final String id = exit.carryIds[index - exit.carryStart];
|
||||
final int kind = kindOf(type);
|
||||
final boolean named = !id.isEmpty();
|
||||
boolean idDeclared = false;
|
||||
int matches = 0;
|
||||
Carried match = null;
|
||||
for (int i = 0, n = carried.size(); i < n; i++) {
|
||||
final Carried candidate = carried.get(i);
|
||||
if (named && !id.equals(candidate.id())) {
|
||||
continue;
|
||||
}
|
||||
idDeclared = true;
|
||||
if (candidate.kind() == kind) {
|
||||
matches++;
|
||||
match = candidate;
|
||||
}
|
||||
}
|
||||
final String param = paramRef(exit.method, index);
|
||||
if (named && !idDeclared) {
|
||||
throw exit.reject(param + " asks for the carried local '" + id + "', which the entry handler does not declare" + declaredList(carried));
|
||||
}
|
||||
if (matches == 0) {
|
||||
throw exit.reject(param + " is " + type.getClassName() + " (" + withArticle(kind) + "), which matches no carried local of that kind" + declaredList(carried));
|
||||
}
|
||||
if (matches > 1) {
|
||||
throw exit.reject(param + " is " + type.getClassName() + " (" + withArticle(kind) + "), which matches " + matches + " carried locals of that kind; name them with @Surround.Carry(\"...\") on both sides");
|
||||
}
|
||||
final Type existing = slots[match.slot()];
|
||||
if (existing != null && !existing.equals(type)) {
|
||||
throw exit.reject(param + " reads carried local " + match + " as " + type.getClassName() + ", but another exit handler reads it as " + existing.getClassName());
|
||||
}
|
||||
slots[match.slot()] = type;
|
||||
return new Load(match.slot(), type);
|
||||
}
|
||||
|
||||
private static List<Carried> carriedLocals(MethodNode entry, int[] kinds) {
|
||||
final List<Carried> carried = new ArrayList<>();
|
||||
final List<LocalVariableAnnotationNode> annotations = entry.visibleLocalVariableAnnotations;
|
||||
for (int a = 0, n = annotations == null ? 0 : annotations.size(); a < n; a++) {
|
||||
final LocalVariableAnnotationNode annotation = annotations.get(a);
|
||||
if (!SurroundSpec.CARRY.equals(annotation.desc)) {
|
||||
continue;
|
||||
}
|
||||
final int slot = annotatedSlot(entry, annotation, 0);
|
||||
final String id = Annotations.<String>getValue(annotation, "value", "");
|
||||
checkLocalAnnotation(entry, kinds, "@Surround.Carry", annotation, slot, id);
|
||||
if (!id.isEmpty()) {
|
||||
for (int i = 0, count = carried.size(); i < count; i++) {
|
||||
if (id.equals(carried.get(i).id())) {
|
||||
throw new SurroundRejection("declares two @Surround.Carry locals named '" + id + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
carried.add(new Carried(slot, kinds[slot], id));
|
||||
}
|
||||
return carried;
|
||||
}
|
||||
|
||||
// FML's deobf remapper (ASM LocalVariablesSorter) corrupts annotation indices of long/double locals; the LVT stays right.
|
||||
private static int annotatedSlot(MethodNode entry, LocalVariableAnnotationNode annotation, int range) {
|
||||
final int raw = annotation.index.get(range).intValue();
|
||||
final LabelNode start = annotation.start.get(range);
|
||||
final LabelNode end = annotation.end.get(range);
|
||||
boolean any = false;
|
||||
boolean rawWide = false;
|
||||
boolean rawFound = false;
|
||||
int wide = -1;
|
||||
int wideCount = 0;
|
||||
for (int i = 0, n = entry.localVariables == null ? 0 : entry.localVariables.size(); i < n; i++) {
|
||||
final LocalVariableNode local = entry.localVariables.get(i);
|
||||
if (local.start != start || local.end != end) {
|
||||
continue;
|
||||
}
|
||||
any = true;
|
||||
final boolean isWide = Type.getType(local.desc).getSize() == 2;
|
||||
if (isWide) {
|
||||
wide = local.index;
|
||||
wideCount++;
|
||||
}
|
||||
if (local.index == raw) {
|
||||
rawFound = true;
|
||||
rawWide = isWide;
|
||||
}
|
||||
}
|
||||
final int slot = !any || rawFound && !(rawWide && wideCount > 1) ? raw : wideCount == 1 && !rawFound ? wide : -1;
|
||||
if (slot < 0) {
|
||||
throw new SurroundRejection("has a long or double annotated local that shares its scope with another; initialize it at its declaration");
|
||||
}
|
||||
if (slot >= entry.maxLocals) {
|
||||
throw new SurroundRejection("has an annotated local outside its method's locals (slot " + slot + " of " + entry.maxLocals + "); compile with local variable tables");
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
private static void checkLocalAnnotation(MethodNode entry, int[] kinds, String what, LocalVariableAnnotationNode annotation, int slot, String id) {
|
||||
final String local = id.isEmpty() ? "in slot " + slot : "named '" + id + "'";
|
||||
final TypePath path = annotation.typePath;
|
||||
for (int i = 0, length = path == null ? 0 : path.getLength(); i < length; i++) {
|
||||
if (path.getStep(i) != TypePath.ARRAY_ELEMENT) {
|
||||
throw new SurroundRejection("has a " + what + " " + local + " inside the local's type (" + path + "); " + what + " must annotate the local itself, not part of its type");
|
||||
}
|
||||
}
|
||||
for (int i = 1, count = annotation.index.size(); i < count; i++) {
|
||||
if (annotatedSlot(entry, annotation, i) != slot) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " recorded in several slots " + annotation.index + "; initialize it at its declaration");
|
||||
}
|
||||
}
|
||||
LabelNode end = annotation.end.get(0);
|
||||
for (int i = 1, n = annotation.end.size(); i < n; i++) {
|
||||
if (entry.instructions.indexOf(annotation.end.get(i)) > entry.instructions.indexOf(end)) {
|
||||
end = annotation.end.get(i);
|
||||
}
|
||||
}
|
||||
for (AbstractInsnNode insn = end.getNext(); insn != null; insn = insn.getNext()) {
|
||||
if (insn.getOpcode() >= 0 && insn.getOpcode() != Opcodes.RETURN) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " that goes out of scope before the entry handler ends; declare it in the entry handler's outermost block");
|
||||
}
|
||||
}
|
||||
if (kinds[slot] == TOP) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " that is not definitely assigned when the entry handler ends; initialize it at its declaration");
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkNoParameterStores(MethodNode entry) {
|
||||
final int limit = Bytecode.getFirstNonArgLocalIndex(entry);
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
final int var;
|
||||
if (insn instanceof VarInsnNode varInsn && storeKind(insn.getOpcode()) != -1) {
|
||||
var = varInsn.var;
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
var = iinc.var;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (var < limit) {
|
||||
throw new SurroundRejection("must not assign its parameters; use @ModifyVariable or @WrapMethod to change arguments");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int[] kindsAtEnd(MethodNode entry, AbstractInsnNode soleReturn) {
|
||||
final int maxLocals = entry.maxLocals;
|
||||
int[] vars = initialVars(entry);
|
||||
int count = vars.length;
|
||||
int[] state = toSlots(vars, count, maxLocals);
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != soleReturn; insn = insn.getNext()) {
|
||||
if (insn instanceof FrameNode frame) {
|
||||
final int locals = frame.local == null ? 0 : frame.local.size();
|
||||
switch (frame.type) {
|
||||
case Opcodes.F_NEW, Opcodes.F_FULL, Opcodes.F_APPEND -> {
|
||||
if (frame.type != Opcodes.F_APPEND) {
|
||||
count = 0;
|
||||
}
|
||||
if (count + locals > vars.length) {
|
||||
vars = Arrays.copyOf(vars, count + locals);
|
||||
}
|
||||
for (int i = 0; i < locals; i++) {
|
||||
vars[count++] = frameKind(frame.local.get(i));
|
||||
}
|
||||
}
|
||||
case Opcodes.F_CHOP -> count -= locals;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
state = toSlots(vars, count, maxLocals);
|
||||
} else if (insn instanceof VarInsnNode var && storeKind(insn.getOpcode()) != -1) {
|
||||
store(state, var.var, storeKind(insn.getOpcode()));
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private static void store(int[] state, int var, int kind) {
|
||||
if (var > 0 && wide(state[var - 1])) {
|
||||
state[var - 1] = TOP;
|
||||
}
|
||||
state[var] = kind;
|
||||
if (wide(kind) && var + 1 < state.length) {
|
||||
state[var + 1] = TOP;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean wide(int kind) {
|
||||
return kind == Type.LONG || kind == Type.DOUBLE;
|
||||
}
|
||||
|
||||
private static int[] initialVars(MethodNode entry) {
|
||||
final Type[] args = Type.getArgumentTypes(entry.desc);
|
||||
final int receiver = Bytecode.isStatic(entry) ? 0 : 1;
|
||||
final int[] vars = new int[receiver + args.length];
|
||||
if (receiver != 0) {
|
||||
vars[0] = Type.OBJECT;
|
||||
}
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
vars[receiver + i] = kindOf(args[i]);
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
private static int frameKind(Object entry) {
|
||||
if (entry instanceof String || Opcodes.NULL.equals(entry) || Opcodes.UNINITIALIZED_THIS.equals(entry)) {
|
||||
return Type.OBJECT;
|
||||
} else if (Opcodes.INTEGER.equals(entry)) {
|
||||
return Type.INT;
|
||||
} else if (Opcodes.FLOAT.equals(entry)) {
|
||||
return Type.FLOAT;
|
||||
} else if (Opcodes.LONG.equals(entry)) {
|
||||
return Type.LONG;
|
||||
} else if (Opcodes.DOUBLE.equals(entry)) {
|
||||
return Type.DOUBLE;
|
||||
}
|
||||
return TOP;
|
||||
}
|
||||
|
||||
private static int[] toSlots(int[] vars, int count, int maxLocals) {
|
||||
final int[] slots = new int[maxLocals];
|
||||
Arrays.fill(slots, TOP);
|
||||
int slot = 0;
|
||||
for (int i = 0; i < count && slot < maxLocals; i++) {
|
||||
slots[slot] = vars[i];
|
||||
slot += wide(vars[i]) ? 2 : 1;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private static int storeKind(int opcode) {
|
||||
return switch (opcode) {
|
||||
case Opcodes.ISTORE -> Type.INT;
|
||||
case Opcodes.LSTORE -> Type.LONG;
|
||||
case Opcodes.FSTORE -> Type.FLOAT;
|
||||
case Opcodes.DSTORE -> Type.DOUBLE;
|
||||
case Opcodes.ASTORE -> Type.OBJECT;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
static int kindOf(Type type) {
|
||||
return switch (type.getSort()) {
|
||||
case Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> Type.INT;
|
||||
case Type.LONG -> Type.LONG;
|
||||
case Type.FLOAT -> Type.FLOAT;
|
||||
case Type.DOUBLE -> Type.DOUBLE;
|
||||
default -> Type.OBJECT;
|
||||
};
|
||||
}
|
||||
|
||||
private static String kindDescription(int kind) {
|
||||
return switch (kind) {
|
||||
case Type.INT -> "int-like value";
|
||||
case Type.LONG -> "long";
|
||||
case Type.FLOAT -> "float";
|
||||
case Type.DOUBLE -> "double";
|
||||
default -> "reference";
|
||||
};
|
||||
}
|
||||
|
||||
private static String withArticle(int kind) {
|
||||
return (kind == Type.INT ? "an " : "a ") + kindDescription(kind);
|
||||
}
|
||||
|
||||
private static String declaredList(List<Carried> carried) {
|
||||
final StringJoiner all = new StringJoiner(", ", "; the entry handler carries ", "").setEmptyValue("; the entry handler carries nothing");
|
||||
for (Carried local : carried) {
|
||||
all.add(local.toString());
|
||||
}
|
||||
return all.toString();
|
||||
}
|
||||
|
||||
static String exitRef(MethodNode exit) {
|
||||
return "exit handler '" + exit.name + "'";
|
||||
}
|
||||
|
||||
private record Carried(int slot, int kind, String id) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (this.id.isEmpty() ? "an unnamed " + kindDescription(this.kind) : "'" + this.id + "', " + withArticle(this.kind)) + " in slot " + this.slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.launch.GlobalProperties;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
|
||||
|
||||
public final class SurroundBootstrap {
|
||||
|
||||
private SurroundBootstrap() {
|
||||
}
|
||||
|
||||
private static final class Registration {
|
||||
|
||||
static {
|
||||
InjectionInfo.register(SurroundInjectionInfo.class);
|
||||
}
|
||||
|
||||
static void ensure() {
|
||||
}
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
Registration.ensure();
|
||||
if (GlobalProperties.get(GlobalProperties.Keys.INIT) != null && MixinEnvironment.getDefaultEnvironment().getActiveTransformer() instanceof IMixinTransformer) {
|
||||
SurroundApplicatorExtension.instance();
|
||||
}
|
||||
}
|
||||
}
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Handler;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Load;
|
||||
import com.llamalad7.mixinextras.utils.ASMUtils;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.FrameNode;
|
||||
import org.spongepowered.asm.lib.tree.IincInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnList;
|
||||
import org.spongepowered.asm.lib.tree.InsnNode;
|
||||
import org.spongepowered.asm.lib.tree.JumpInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.TryCatchBlockNode;
|
||||
import org.spongepowered.asm.lib.tree.TypeInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.VarInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.analysis.Analyzer;
|
||||
import org.spongepowered.asm.lib.tree.analysis.AnalyzerException;
|
||||
import org.spongepowered.asm.lib.tree.analysis.BasicInterpreter;
|
||||
import org.spongepowered.asm.lib.tree.analysis.BasicValue;
|
||||
import org.spongepowered.asm.lib.tree.analysis.Frame;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
import org.spongepowered.asm.service.MixinService;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
import org.spongepowered.asm.util.Bytecode.Visibility;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
final class SurroundCallSite {
|
||||
|
||||
private static final String REGION = "surround:region";
|
||||
private static final Type[] NOTHING = new Type[0];
|
||||
|
||||
record Region(LabelNode start, LabelNode end) {}
|
||||
|
||||
private SurroundCallSite() {
|
||||
}
|
||||
|
||||
static Type[] operands(MethodInsnNode call, String targetClass) {
|
||||
final Type[] args = Type.getArgumentTypes(call.desc);
|
||||
if (call.getOpcode() == Opcodes.INVOKESTATIC) {
|
||||
return args;
|
||||
}
|
||||
final Type[] all = new Type[args.length + 1];
|
||||
all[0] = Type.getObjectType(call.getOpcode() == Opcodes.INVOKESPECIAL ? targetClass : call.owner);
|
||||
System.arraycopy(args, 0, all, 1, args.length);
|
||||
return all;
|
||||
}
|
||||
|
||||
static void apply(ClassNode classNode, CallSiteSpec spec) {
|
||||
final InjectionNode node = spec.node();
|
||||
if (node == null) {
|
||||
final MethodNode target = spec.target();
|
||||
final MethodNode body = relocate(classNode, target);
|
||||
final MethodInsnNode call = (MethodInsnNode) ASMUtils.getInvokeInstruction(classNode, body);
|
||||
target.instructions.add(call);
|
||||
target.instructions.add(new InsnNode(spec.result().getOpcode(Opcodes.IRETURN)));
|
||||
final Type[] args = operands(call, classNode.name);
|
||||
final int[] argSlots = new int[args.length];
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
argSlots[i] = argSlots[i - 1] + args[i - 1].getSize();
|
||||
}
|
||||
emit(classNode, spec, target, call, null, Bytecode.isStatic(target) ? 0 : 1, argSlots);
|
||||
return;
|
||||
}
|
||||
final AbstractInsnNode champion = node.getCurrentTarget();
|
||||
final Region inner = node.<Region>getDecoration(REGION);
|
||||
final MethodNode owner = champion == null ? null : owner(classNode, inner == null ? champion : inner.start());
|
||||
if (owner == null) {
|
||||
MixinService.getService().getLogger("mixin").warn(spec.description() + " skipped: the surrounded call was removed by another injector");
|
||||
return;
|
||||
}
|
||||
if (!spec.entryDesc().equals(spec.entry().desc)) {
|
||||
throw cannotFollow(spec, "the entry handler's descriptor changed from " + spec.entryDesc() + " to " + spec.entry().desc + " after injection", null);
|
||||
}
|
||||
if (!(champion instanceof MethodInsnNode call)) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(champion, false) + ", which is not a method call", null);
|
||||
}
|
||||
final int offset = node.isReplaced() && call.getOpcode() != Opcodes.INVOKESTATIC ? 1 : 0;
|
||||
checkOperands(spec, call, operands(call, classNode.name), offset);
|
||||
final Exits exits = spec.exits();
|
||||
final Type result = spec.result();
|
||||
final Type actual = Type.getReturnType(call.desc);
|
||||
if ((exits.handles || exits.skipSlot >= 0 || exits.returnHandler != null) && (actual.getSize() != result.getSize() || actual.getSize() > 0 && SurroundBinding.kindOf(actual) != SurroundBinding.kindOf(result))) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", which returns " + actual.getClassName() + " where the surround supplies or replaces a " + result.getClassName(), null);
|
||||
}
|
||||
emit(classNode, spec, owner, call, inner, offset, null);
|
||||
}
|
||||
|
||||
private static void emit(ClassNode classNode, CallSiteSpec spec, MethodNode owner, MethodInsnNode call, Region inner, int offset, int[] argSlots) {
|
||||
final boolean slots = argSlots != null;
|
||||
final Type[] args = operands(call, classNode.name);
|
||||
final Exits exits = spec.exits();
|
||||
final boolean skips = exits.skipSlot >= 0;
|
||||
final Handler returnHandler = exits.returnHandler;
|
||||
final Type result = spec.result();
|
||||
final Type actual = Type.getReturnType(call.desc);
|
||||
|
||||
final MethodNode entry = spec.entry();
|
||||
final Type[] operands = spec.operands();
|
||||
final int argBase = Bytecode.isStatic(spec.target()) ? 0 : 1;
|
||||
final Type[] entryParams = Type.getArgumentTypes(entry.desc);
|
||||
final int entryOperands = entryParams.length - spec.entryLocals().length;
|
||||
final int firstNonArg = argBase + Bytecode.getArgsSize(entryParams);
|
||||
final MethodNode inlined = inlineable(entry);
|
||||
|
||||
final boolean restoresBelow = exits.handles || !inlined.tryCatchBlocks.isEmpty();
|
||||
final boolean relocated = owner != spec.target();
|
||||
owner.maxLocals = Math.max(Math.max(owner.maxLocals, spec.target().maxLocals), usedLocals(owner));
|
||||
int next = owner.maxLocals;
|
||||
final AbstractInsnNode anchor = inner == null ? call : inner.start();
|
||||
final Frame<BasicValue> frame = !slots && (restoresBelow || relocated && hasLocals(spec)) ? frameAt(owner, anchor, spec) : null;
|
||||
final int shift = relocated ? Bytecode.getFirstNonArgLocalIndex(owner) - Bytecode.getFirstNonArgLocalIndex(spec.target()) : 0;
|
||||
|
||||
final Type[] below = restoresBelow ? below(spec, frame, args.length) : NOTHING;
|
||||
final int spillFrom = slots || restoresBelow || skips ? 0 : offset;
|
||||
final boolean spill = slots || restoresBelow || skips || args.length > offset && (entryOperands > 0 || prefixed(spec));
|
||||
final int[] spillSlots = slots ? argSlots : new int[args.length];
|
||||
if (spill && !slots) {
|
||||
for (int i = spillFrom; i < args.length; i++) {
|
||||
spillSlots[i] = next;
|
||||
next += args[i].getSize();
|
||||
}
|
||||
}
|
||||
final int[] belowSlots = new int[below.length];
|
||||
for (int i = 0; i < below.length; i++) {
|
||||
belowSlots[i] = next;
|
||||
next += below[i].getSize();
|
||||
}
|
||||
final int entryBase = next;
|
||||
final int delta = entryBase - firstNonArg;
|
||||
final int excSlot = entryBase + Math.max(0, inlined.maxLocals - firstNonArg);
|
||||
final int resSlot = excSlot + 1;
|
||||
final boolean storesResult = returnHandler != null && result.getSize() > 0;
|
||||
|
||||
final int[] entryLocals = localSlots(spec, owner, anchor, frame, shift, spec.entryLocals(), entryParams, entryOperands);
|
||||
remapLocals(inlined, argBase, firstNonArg, entryParams, entryOperands, spillSlots, offset, entryLocals, delta);
|
||||
final Handler[] handlers = exits.handlers;
|
||||
final int[][] locals = new int[handlers.length][];
|
||||
for (Handler exit : handlers) {
|
||||
locals[exit.index] = localSlots(spec, owner, anchor, frame, shift, spec.locals()[exit.index], exit.params, exit.localStart);
|
||||
}
|
||||
final Emitter emitter = new Emitter(classNode, exits, operands, Arrays.copyOfRange(spillSlots, offset, offset + operands.length), locals, delta);
|
||||
|
||||
final LabelNode sStart = new LabelNode();
|
||||
final LabelNode tStart = inner == null || skips ? new LabelNode() : inner.start();
|
||||
final LabelNode tEnd = inner == null ? new LabelNode() : inner.end();
|
||||
final LabelNode sEnd = new LabelNode();
|
||||
final LabelNode join = skips ? new LabelNode() : null;
|
||||
|
||||
final InsnList head = new InsnList();
|
||||
head.add(sStart);
|
||||
if (spill && !slots) {
|
||||
for (int i = args.length - 1; i >= spillFrom; i--) {
|
||||
final int j = i - offset;
|
||||
if (j >= 0 && j < operands.length && operands[j].getSort() >= Type.ARRAY && !args[i].equals(operands[j])) {
|
||||
head.add(new TypeInsnNode(Opcodes.CHECKCAST, operands[j].getInternalName()));
|
||||
}
|
||||
head.add(new VarInsnNode(args[i].getOpcode(Opcodes.ISTORE), spillSlots[i]));
|
||||
}
|
||||
for (int i = below.length - 1; i >= 0; i--) {
|
||||
head.add(new VarInsnNode(below[i].getOpcode(Opcodes.ISTORE), belowSlots[i]));
|
||||
}
|
||||
}
|
||||
head.add(inlined.instructions);
|
||||
if (skips) {
|
||||
final LabelNode callPath = new LabelNode();
|
||||
head.add(new VarInsnNode(Opcodes.ILOAD, exits.skipSlot + delta));
|
||||
head.add(new JumpInsnNode(Opcodes.IFEQ, callPath));
|
||||
head.add(tStart);
|
||||
loadBelow(head, below, belowSlots);
|
||||
emitter.callSkipped(head);
|
||||
head.add(new JumpInsnNode(Opcodes.GOTO, join));
|
||||
head.add(callPath);
|
||||
}
|
||||
if (spill) {
|
||||
loadBelow(head, below, belowSlots);
|
||||
for (int i = spillFrom; i < args.length; i++) {
|
||||
head.add(new VarInsnNode(args[i].getOpcode(Opcodes.ILOAD), spillSlots[i]));
|
||||
}
|
||||
}
|
||||
if (inner == null && !skips) {
|
||||
head.add(tStart);
|
||||
}
|
||||
owner.instructions.insertBefore(anchor, head);
|
||||
|
||||
final InsnList tail = new InsnList();
|
||||
if (inner == null) {
|
||||
tail.add(tEnd);
|
||||
}
|
||||
final LabelNode retStart = returnHandler == null ? null : new LabelNode();
|
||||
final LabelNode retEnd = returnHandler == null ? null : new LabelNode();
|
||||
if (returnHandler != null) {
|
||||
tail.add(retStart);
|
||||
if (result.getSize() > 0) {
|
||||
if (result.getSort() >= Type.ARRAY && !actual.equals(result)) {
|
||||
tail.add(new TypeInsnNode(Opcodes.CHECKCAST, result.getInternalName()));
|
||||
}
|
||||
tail.add(new VarInsnNode(result.getOpcode(Opcodes.ISTORE), resSlot));
|
||||
}
|
||||
emitter.callReturn(tail, result, resSlot);
|
||||
tail.add(retEnd);
|
||||
}
|
||||
if (skips) {
|
||||
tail.add(join);
|
||||
}
|
||||
emitter.callFinally(tail);
|
||||
tail.add(new JumpInsnNode(Opcodes.GOTO, sEnd));
|
||||
final AbstractInsnNode beforeHandlers = tail.getLast();
|
||||
|
||||
final List<TryCatchBlockNode> blocks = new ArrayList<>(inlined.tryCatchBlocks);
|
||||
emitter.emitHandlers(tail, blocks, tStart, tEnd, retStart, retEnd, excSlot, below, belowSlots, sEnd);
|
||||
final AbstractInsnNode firstHandler = beforeHandlers.getNext();
|
||||
tail.add(sEnd);
|
||||
owner.instructions.insert(inner == null ? call : inner.end(), tail);
|
||||
|
||||
owner.tryCatchBlocks.addAll(insertionIndex(owner, sStart, sEnd), blocks);
|
||||
owner.maxLocals = Math.max(owner.maxLocals, storesResult ? resSlot + result.getSize() : excSlot + 1);
|
||||
owner.maxStack = Math.max(owner.maxStack, spec.target().maxStack) + Math.max(inlined.maxStack, exits.argsSize + 2);
|
||||
if (slots) {
|
||||
final LabelNode end = new LabelNode();
|
||||
owner.instructions.add(end);
|
||||
final List<LocalVariableNode> names = new ArrayList<>();
|
||||
nameParameters(names, classNode, spec, argBase, entryOperands, sStart, end);
|
||||
nameEntryLocals(names, inlined, owner.instructions, exits.carried, firstNonArg, delta, end);
|
||||
if (firstHandler != null) {
|
||||
names.add(new LocalVariableNode("surround$error", SurroundSpec.THROWABLE_DESC, null, (LabelNode) firstHandler, end, excSlot));
|
||||
}
|
||||
if (storesResult) {
|
||||
names.add(new LocalVariableNode("surround$result", result.getDescriptor(), null, retStart, retEnd, resSlot));
|
||||
}
|
||||
owner.localVariables = names;
|
||||
} else {
|
||||
spec.node().decorate(REGION, new Region(sStart, sEnd));
|
||||
}
|
||||
}
|
||||
|
||||
private static void nameParameters(List<LocalVariableNode> names, ClassNode classNode, CallSiteSpec spec, int argBase, int entryOperands, LabelNode start, LabelNode end) {
|
||||
final Type[] params = spec.operands();
|
||||
final MethodNode entry = spec.entry();
|
||||
if (argBase > 0) {
|
||||
names.add(new LocalVariableNode("this", Type.getObjectType(classNode.name).getDescriptor(), null, start, end, 0));
|
||||
}
|
||||
for (int i = 0, slot = argBase; i < params.length; slot += params[i++].getSize()) {
|
||||
final String name = i < entryOperands ? SurroundBinding.paramName(entry, i) : null;
|
||||
names.add(new LocalVariableNode(name != null ? name : "arg" + i, params[i].getDescriptor(), null, start, end, slot));
|
||||
}
|
||||
}
|
||||
|
||||
private static void nameEntryLocals(List<LocalVariableNode> names, MethodNode inlined, InsnList insns, Type[] carried, int firstNonArg, int delta, LabelNode end) {
|
||||
final LocalVariableNode[] furthest = new LocalVariableNode[carried.length];
|
||||
for (LocalVariableNode local : inlined.localVariables) {
|
||||
if (!isCarried(carried, local.index)) {
|
||||
continue;
|
||||
}
|
||||
final LocalVariableNode current = furthest[local.index];
|
||||
if (current == null || insns.indexOf(local.end) > insns.indexOf(current.end)) {
|
||||
furthest[local.index] = local;
|
||||
}
|
||||
}
|
||||
for (LocalVariableNode local : inlined.localVariables) {
|
||||
if (local.index < firstNonArg) {
|
||||
continue;
|
||||
}
|
||||
if (isCarried(carried, local.index) && local == furthest[local.index]) {
|
||||
final Type type = carried[local.index];
|
||||
names.add(new LocalVariableNode(local.name, type.getSort() >= Type.ARRAY ? local.desc : type.getDescriptor(), null, local.start, end, local.index + delta));
|
||||
} else {
|
||||
local.index += delta;
|
||||
names.add(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCarried(Type[] carried, int slot) {
|
||||
return slot < carried.length && carried[slot] != null;
|
||||
}
|
||||
|
||||
private static MethodNode inlineable(MethodNode entry) {
|
||||
final MethodNode copy = new MethodNode(entry.access, entry.name, entry.desc, null, null);
|
||||
entry.accept(copy);
|
||||
for (AbstractInsnNode insn = copy.instructions.getFirst(); insn != null; ) {
|
||||
final AbstractInsnNode next = insn.getNext();
|
||||
if (insn instanceof FrameNode) {
|
||||
copy.instructions.remove(insn);
|
||||
}
|
||||
insn = next;
|
||||
}
|
||||
AbstractInsnNode last = copy.instructions.getLast();
|
||||
while (last.getOpcode() < 0) {
|
||||
last = last.getPrevious();
|
||||
}
|
||||
copy.instructions.remove(last);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static MethodNode relocate(ClassNode classNode, MethodNode target) {
|
||||
final MethodNode body = new MethodNode(target.access & ~Opcodes.ACC_SYNCHRONIZED, unique(classNode, target.name + "$surround"), target.desc, null, null);
|
||||
Bytecode.setVisibility(body, Visibility.PRIVATE);
|
||||
body.instructions = target.instructions;
|
||||
body.instructions.resetLabels();
|
||||
body.tryCatchBlocks = target.tryCatchBlocks;
|
||||
body.localVariables = target.localVariables;
|
||||
body.visibleLocalVariableAnnotations = target.visibleLocalVariableAnnotations;
|
||||
body.invisibleLocalVariableAnnotations = target.invisibleLocalVariableAnnotations;
|
||||
body.maxStack = target.maxStack;
|
||||
body.maxLocals = target.maxLocals;
|
||||
|
||||
target.instructions = new InsnList();
|
||||
target.tryCatchBlocks = new ArrayList<>();
|
||||
target.visibleLocalVariableAnnotations = null;
|
||||
target.invisibleLocalVariableAnnotations = null;
|
||||
target.maxLocals = target.maxStack = Bytecode.getFirstNonArgLocalIndex(target);
|
||||
classNode.methods.add(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
private static String unique(ClassNode classNode, String base) {
|
||||
String name = base;
|
||||
for (int i = 1; ; name = base + "$" + i++) {
|
||||
final String candidate = name;
|
||||
if (classNode.methods.stream().noneMatch(method -> method.name.equals(candidate))) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasLocals(CallSiteSpec spec) {
|
||||
if (spec.entryLocals().length > 0) {
|
||||
return true;
|
||||
}
|
||||
for (int[] locals : spec.locals()) {
|
||||
if (locals.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Frame<BasicValue> frameAt(MethodNode owner, AbstractInsnNode anchor, CallSiteSpec spec) {
|
||||
final int index = owner.instructions.indexOf(anchor);
|
||||
for (owner.maxStack = Math.max(owner.maxStack, spec.target().maxStack) + 16; ; owner.maxStack <<= 1) {
|
||||
try {
|
||||
return new Analyzer<>(new BasicInterpreter()).analyze(owner.name, owner)[index];
|
||||
} catch (AnalyzerException e) {
|
||||
if (!(e.getCause() instanceof IndexOutOfBoundsException) || owner.maxStack > 0xFFFF) {
|
||||
throw cannotFollow(spec, "cannot analyze " + owner.name + owner.desc, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Type[] below(CallSiteSpec spec, Frame<BasicValue> frame, int args) {
|
||||
final int count = frame == null ? 0 : frame.getStackSize() - args;
|
||||
if (count <= 0) {
|
||||
return NOTHING;
|
||||
}
|
||||
final Type[] below = new Type[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
final BasicValue value = frame.getStack(i);
|
||||
final Type type = value.getType();
|
||||
if (type == null || type.getSort() == Type.VOID) {
|
||||
throw cannotFollow(spec, "stack value " + i + " below the call is " + value + ", which cannot be restored", null);
|
||||
}
|
||||
below[i] = value.isReference() ? Type.getObjectType(SurroundSpec.OBJECT) : type;
|
||||
}
|
||||
return below;
|
||||
}
|
||||
|
||||
private static int[] localSlots(CallSiteSpec spec, MethodNode owner, AbstractInsnNode anchor, Frame<BasicValue> frame, int shift, int[] resolved, Type[] params, int first) {
|
||||
if (resolved.length == 0 || owner == spec.target()) {
|
||||
return resolved;
|
||||
}
|
||||
final int firstNonArg = Bytecode.getFirstNonArgLocalIndex(spec.target());
|
||||
final int[] slots = new int[resolved.length];
|
||||
for (int i = 0; i < resolved.length; i++) {
|
||||
final int slot = resolved[i] < firstNonArg ? resolved[i] : resolved[i] + shift;
|
||||
checkLocal(spec, owner, anchor, frame, slot, params[first + i], resolved[i]);
|
||||
slots[i] = slot;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private static void checkLocal(CallSiteSpec spec, MethodNode owner, AbstractInsnNode anchor, Frame<BasicValue> frame, int slot, Type type, int resolved) {
|
||||
String found = null;
|
||||
if (owner.localVariables != null) {
|
||||
final int at = owner.instructions.indexOf(anchor);
|
||||
for (LocalVariableNode local : owner.localVariables) {
|
||||
if (local.index == slot && owner.instructions.indexOf(local.start) <= at && at < owner.instructions.indexOf(local.end) && !type.getDescriptor().equals(local.desc)) {
|
||||
found = Type.getType(local.desc).getClassName() + " '" + local.name + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found == null && frame != null) {
|
||||
final BasicValue value = slot < frame.getLocals() ? frame.getLocal(slot) : null;
|
||||
final Type actual = value == null ? null : value.getType();
|
||||
if (actual == null || actual.getSort() == Type.VOID || actual.getSize() != type.getSize() || SurroundBinding.kindOf(actual) != SurroundBinding.kindOf(type)) {
|
||||
found = value == null ? "nothing" : String.valueOf(value);
|
||||
}
|
||||
}
|
||||
if (found != null) {
|
||||
throw cannotFollow(spec, "@Surround.Local " + type.getClassName() + " resolved to local " + resolved + " at injection, but after the body moved to " + owner.name + owner.desc + " local " + slot + " holds " + found, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodNode owner(ClassNode classNode, AbstractInsnNode insn) {
|
||||
AbstractInsnNode first = insn;
|
||||
while (first.getPrevious() != null) {
|
||||
first = first.getPrevious();
|
||||
}
|
||||
final List<MethodNode> methods = classNode.methods;
|
||||
for (int i = 0, n = methods.size(); i < n; i++) {
|
||||
if (methods.get(i).instructions.getFirst() == first) {
|
||||
return methods.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void checkOperands(CallSiteSpec spec, MethodInsnNode call, Type[] args, int offset) {
|
||||
final Type[] operands = spec.operands();
|
||||
if (args.length - offset < operands.length) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", which takes fewer arguments than the surrounded call", null);
|
||||
}
|
||||
for (int i = 0; i < operands.length; i++) {
|
||||
if (SurroundBinding.kindOf(args[offset + i]) != SurroundBinding.kindOf(operands[i])) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", whose argument " + (offset + i) + " is " + args[offset + i].getClassName() + " where the surrounded call passed " + operands[i].getClassName(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalStateException cannotFollow(CallSiteSpec spec, String detail, Throwable cause) {
|
||||
return new IllegalStateException(spec.description() + " cannot be applied, another transformation left the site in a shape it cannot follow: " + detail, cause);
|
||||
}
|
||||
|
||||
private static boolean prefixed(CallSiteSpec spec) {
|
||||
for (Handler exit : spec.exits().handlers) {
|
||||
if (exit.localStart > exit.leading) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int usedLocals(MethodNode owner) {
|
||||
int max = Bytecode.getFirstNonArgLocalIndex(owner);
|
||||
for (AbstractInsnNode insn = owner.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn instanceof VarInsnNode varInsn) {
|
||||
final int opcode = insn.getOpcode();
|
||||
final boolean wide = opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE;
|
||||
max = Math.max(max, varInsn.var + (wide ? 2 : 1));
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
max = Math.max(max, iinc.var + 1);
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
private static void remapLocals(MethodNode inlined, int argBase, int firstNonArg, Type[] entryParams, int entryOperands, int[] spillSlots, int offset, int[] entryLocals, int delta) {
|
||||
final int[] paramSlots = new int[firstNonArg];
|
||||
for (int i = 0, slot = argBase; i < entryParams.length; i++) {
|
||||
paramSlots[slot] = i < entryOperands ? spillSlots[offset + i] : entryLocals[i - entryOperands];
|
||||
slot += entryParams[i].getSize();
|
||||
}
|
||||
for (AbstractInsnNode insn = inlined.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn instanceof VarInsnNode var) {
|
||||
var.var = remap(var.var, argBase, firstNonArg, paramSlots, delta);
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
iinc.var = remap(iinc.var, argBase, firstNonArg, paramSlots, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int remap(int var, int argBase, int firstNonArg, int[] paramSlots, int delta) {
|
||||
if (var < argBase) {
|
||||
return var;
|
||||
}
|
||||
if (var < firstNonArg) {
|
||||
return paramSlots[var];
|
||||
}
|
||||
return var + delta;
|
||||
}
|
||||
|
||||
private static int insertionIndex(MethodNode owner, LabelNode start, LabelNode end) {
|
||||
final List<TryCatchBlockNode> blocks = owner.tryCatchBlocks;
|
||||
final InsnList insns = owner.instructions;
|
||||
final int from = insns.indexOf(start);
|
||||
final int to = insns.indexOf(end);
|
||||
for (int i = 0, n = blocks.size(); i < n; i++) {
|
||||
final TryCatchBlockNode block = blocks.get(i);
|
||||
final int s = insns.indexOf(block.start);
|
||||
final int e = insns.indexOf(block.end);
|
||||
if (s < to && e > from && (s < from || e > to)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return blocks.size();
|
||||
}
|
||||
|
||||
private static void loadBelow(InsnList insns, Type[] below, int[] belowSlots) {
|
||||
for (int i = 0; i < below.length; i++) {
|
||||
insns.add(new VarInsnNode(below[i].getOpcode(Opcodes.ILOAD), belowSlots[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private record Emitter(ClassNode classNode, Exits exits, Type[] operands, int[] operandSlots, int[][] locals, int delta) {
|
||||
|
||||
private void invoke(InsnList insns, Handler exit, Type leading, int leadingSlot) {
|
||||
final int[] locals = this.locals[exit.index];
|
||||
if (!exit.isStatic) {
|
||||
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||
}
|
||||
if (leading != null) {
|
||||
insns.add(new VarInsnNode(leading.getOpcode(Opcodes.ILOAD), leadingSlot));
|
||||
}
|
||||
for (int i = 0, n = exit.localStart - exit.leading; i < n; i++) {
|
||||
insns.add(new VarInsnNode(this.operands[i].getOpcode(Opcodes.ILOAD), this.operandSlots[i]));
|
||||
}
|
||||
for (int i = exit.localStart; i < exit.carryStart; i++) {
|
||||
insns.add(new VarInsnNode(exit.params[i].getOpcode(Opcodes.ILOAD), locals[i - exit.localStart]));
|
||||
}
|
||||
for (Load load : exit.loads) {
|
||||
final Type type = load.type();
|
||||
insns.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), load.slot() + this.delta));
|
||||
if (type.getSort() >= Type.ARRAY && !SurroundSpec.OBJECT.equals(type.getInternalName())) {
|
||||
insns.add(new TypeInsnNode(Opcodes.CHECKCAST, type.getInternalName()));
|
||||
}
|
||||
}
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
insns.add(ASMUtils.getInvokeInstruction(this.classNode, exit.method));
|
||||
}
|
||||
|
||||
void callFinally(InsnList insns) {
|
||||
if (this.exits.finallyHandler != null) {
|
||||
this.invoke(insns, this.exits.finallyHandler, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void callSkipped(InsnList insns) {
|
||||
if (this.exits.skipped != null) {
|
||||
this.invoke(insns, this.exits.skipped, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void callReturn(InsnList insns, Type result, int resSlot) {
|
||||
this.invoke(insns, this.exits.returnHandler, result.getSize() > 0 ? result : null, resSlot);
|
||||
}
|
||||
|
||||
void emitHandlers(InsnList tail, List<TryCatchBlockNode> blocks, LabelNode start, LabelNode end, LabelNode retStart, LabelNode retEnd, int excSlot, Type[] below, int[] belowSlots, LabelNode handledExit) {
|
||||
final Handler[] handlers = this.exits.handlers;
|
||||
final boolean hasFinally = this.exits.finallyHandler != null;
|
||||
final LabelNode catchAll = hasFinally ? new LabelNode() : null;
|
||||
final List<TryCatchBlockNode> catchAlls = hasFinally ? new ArrayList<>() : null;
|
||||
for (int k = 0; k < this.exits.catches; k++) {
|
||||
final Handler exit = handlers[k];
|
||||
final LabelNode handler = new LabelNode();
|
||||
final LabelNode exitStart = new LabelNode();
|
||||
final LabelNode exitEnd = new LabelNode();
|
||||
|
||||
tail.add(handler);
|
||||
tail.add(new VarInsnNode(Opcodes.ASTORE, excSlot));
|
||||
if (exit.handles) {
|
||||
loadBelow(tail, below, belowSlots);
|
||||
}
|
||||
tail.add(exitStart);
|
||||
this.invoke(tail, exit, exit.caught(), excSlot);
|
||||
tail.add(exitEnd);
|
||||
|
||||
this.callFinally(tail);
|
||||
if (exit.handles) {
|
||||
tail.add(new JumpInsnNode(Opcodes.GOTO, handledExit));
|
||||
} else {
|
||||
tail.add(new VarInsnNode(Opcodes.ALOAD, excSlot));
|
||||
tail.add(new InsnNode(Opcodes.ATHROW));
|
||||
}
|
||||
|
||||
blocks.add(new TryCatchBlockNode(start, end, handler, exit.caught().getInternalName()));
|
||||
if (hasFinally) {
|
||||
catchAlls.add(new TryCatchBlockNode(exitStart, exitEnd, catchAll, null));
|
||||
}
|
||||
}
|
||||
if (hasFinally) {
|
||||
tail.add(catchAll);
|
||||
tail.add(new VarInsnNode(Opcodes.ASTORE, excSlot));
|
||||
this.callFinally(tail);
|
||||
tail.add(new VarInsnNode(Opcodes.ALOAD, excSlot));
|
||||
tail.add(new InsnNode(Opcodes.ATHROW));
|
||||
|
||||
if (retStart != null) {
|
||||
catchAlls.add(new TryCatchBlockNode(retStart, retEnd, catchAll, null));
|
||||
}
|
||||
catchAlls.add(new TryCatchBlockNode(start, end, catchAll, null));
|
||||
blocks.addAll(catchAlls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.code.Injector;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
|
||||
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
|
||||
import org.spongepowered.asm.mixin.transformer.MixinTargetContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@InjectionInfo.AnnotationType(Surround.class)
|
||||
@InjectionInfo.HandlerPrefix("surround")
|
||||
public final class SurroundInjectionInfo extends InjectionInfo {
|
||||
|
||||
boolean callSite;
|
||||
|
||||
public SurroundInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) {
|
||||
super(mixin, method, annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readInjectionPoints() {
|
||||
final List<AnnotationNode> ats = Annotations.<AnnotationNode>getValue(this.annotation, this.atKey, false);
|
||||
this.callSite = ats != null && !ats.isEmpty();
|
||||
if (this.callSite) {
|
||||
super.readInjectionPoints();
|
||||
return;
|
||||
}
|
||||
final List<AnnotationNode> slices = Annotations.<AnnotationNode>getValue(this.annotation, "slice", false);
|
||||
if (slices != null && !slices.isEmpty()) {
|
||||
throw this.reject("declares @Surround.slice, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseInjectionPoints(List<AnnotationNode> ats) {
|
||||
if (!this.callSite) {
|
||||
this.injectionPoints.add(new SurroundInjectionPoint());
|
||||
return;
|
||||
}
|
||||
for (int i = 0, n = ats.size(); i < n; i++) {
|
||||
final AnnotationNode at = ats.get(i);
|
||||
final String code = Annotations.<String>getValue(at, "value");
|
||||
if (!selectsCall(code)) {
|
||||
throw this.reject("has @At(\"" + code + "\"), which does not select a method call");
|
||||
}
|
||||
if (Annotations.<At.Shift>getValue(at, "shift", At.Shift.class, At.Shift.NONE) != At.Shift.NONE || Annotations.<Integer>getValue(at, "by", Integer.valueOf(0)).intValue() != 0) {
|
||||
throw this.reject("has @At(\"" + code + "\") with a non-default shift or by, which moves the match off the call");
|
||||
}
|
||||
}
|
||||
super.parseInjectionPoints(ats);
|
||||
}
|
||||
|
||||
private static boolean selectsCall(String atCode) {
|
||||
return switch (atCode) {
|
||||
case "HEAD", "RETURN", "TAIL", "CTOR_HEAD", "FIELD", "NEW", "CONSTANT", "JUMP", "INVOKE_ASSIGN", "LOAD", "STORE" -> false;
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
|
||||
private InvalidInjectionException reject(String message) {
|
||||
return new InvalidInjectionException(this, "@Surround " + this.getMethodName() + " " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Injector parseInjector(AnnotationNode injectAnnotation) {
|
||||
return new SurroundInjector(this);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnList;
|
||||
import org.spongepowered.asm.mixin.injection.InjectionPoint;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
final class SurroundInjectionPoint extends InjectionPoint {
|
||||
|
||||
@Override
|
||||
public boolean checkPriority(int targetPriority, int ownerPriority) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean find(String desc, InsnList insns, Collection<AbstractInsnNode> nodes) {
|
||||
return nodes.add(insns.getFirst());
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Handler;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Role;
|
||||
import com.llamalad7.mixinextras.utils.InjectorUtils;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableAnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.InjectionPoint;
|
||||
import org.spongepowered.asm.mixin.injection.code.Injector;
|
||||
import org.spongepowered.asm.mixin.injection.modify.InvalidImplicitDiscriminatorException;
|
||||
import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator;
|
||||
import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator.Context;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
import org.spongepowered.asm.mixin.injection.struct.Target;
|
||||
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
|
||||
import org.spongepowered.asm.mixin.transformer.ClassInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.ClassInfo.Traversal;
|
||||
import org.spongepowered.asm.mixin.transformer.MixinTargetContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
import org.spongepowered.asm.util.asm.MethodNodeEx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.exitRef;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.paramRef;
|
||||
|
||||
final class SurroundInjector extends Injector {
|
||||
|
||||
private final String id;
|
||||
private final boolean callSite;
|
||||
private final MixinTargetContext mixin;
|
||||
private Handler entry;
|
||||
private Exits exits;
|
||||
|
||||
SurroundInjector(SurroundInjectionInfo info) {
|
||||
super(info, "@Surround");
|
||||
this.id = info.getAnnotation().getValue("id", "");
|
||||
this.callSite = info.callSite;
|
||||
this.mixin = (MixinTargetContext) info.getMixin();
|
||||
SurroundApplicatorExtension.instance().declare(this.mixin.getTarget(), this.mixin.getClassName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(Target target, InjectionNode node) {
|
||||
this.checkTargetModifiers(target, true);
|
||||
try {
|
||||
if (this.callSite) {
|
||||
this.surroundCall(target, node);
|
||||
} else {
|
||||
final Type result = Type.getReturnType(target.method.desc);
|
||||
this.prepare(target.arguments, result, target.toString());
|
||||
this.offer(target, null, target.arguments, result, target.toString());
|
||||
}
|
||||
} catch (SurroundRejection e) {
|
||||
throw error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void surroundCall(Target target, InjectionNode node) {
|
||||
final AbstractInsnNode insn = node.getOriginalTarget();
|
||||
if (!(insn instanceof MethodInsnNode call)) {
|
||||
throw error("at must select a method call, not " + Bytecode.describeNode(insn, false) + " in " + target);
|
||||
}
|
||||
final String site = "call " + call.owner + "." + call.name + call.desc + " in " + target;
|
||||
if ("<init>".equals(call.name)) {
|
||||
throw error("at must select a method call, not the constructor " + site);
|
||||
}
|
||||
final Type[] operands = SurroundCallSite.operands(call, this.classNode.name);
|
||||
final Type result = Type.getReturnType(call.desc);
|
||||
this.prepare(operands, result, site);
|
||||
final boolean insideNew = isInsideNew(call);
|
||||
if (insideNew && this.methodNode.tryCatchBlocks != null && !this.methodNode.tryCatchBlocks.isEmpty()) {
|
||||
throw error("must not catch exceptions when it surrounds a call inside a constructor call's arguments (between NEW and <init>); move the try into a method it calls");
|
||||
}
|
||||
if (insideNew && this.exits.handles) {
|
||||
throw error("has a @Surround.Catch" + this.forId() + " that declares handle = true, but the call is inside a constructor call's arguments (between NEW and <init>), where the values below it cannot be restored");
|
||||
}
|
||||
this.offer(target, node, operands, result, site);
|
||||
}
|
||||
|
||||
private void offer(Target target, InjectionNode node, Type[] operands, Type result, String site) {
|
||||
final Handler[] handlers = this.exits.handlers;
|
||||
final int[][] locals = new int[handlers.length][];
|
||||
for (Handler exit : handlers) {
|
||||
locals[exit.index] = this.resolveLocals(target, node, exit);
|
||||
}
|
||||
final int[] entryLocals = this.resolveLocals(target, node, this.entry);
|
||||
this.info.addCallbackInvocation(this.methodNode);
|
||||
final String description = "@Surround " + this.mixin.getClassName() + "::" + this.info.getMethodName() + " surrounding " + site;
|
||||
SurroundApplicatorExtension.instance().offer(this.mixin.getTarget(), new CallSiteSpec(target.method, node, this.methodNode, this.methodNode.desc, operands, result, this.exits, description, entryLocals, locals));
|
||||
}
|
||||
|
||||
private static boolean isInsideNew(MethodInsnNode call) {
|
||||
int constructed = 0;
|
||||
for (AbstractInsnNode insn = call.getPrevious(); insn != null; insn = insn.getPrevious()) {
|
||||
if (insn.getOpcode() == Opcodes.INVOKESPECIAL && "<init>".equals(((MethodInsnNode) insn).name)) {
|
||||
constructed++;
|
||||
} else if (insn.getOpcode() == Opcodes.NEW) {
|
||||
if (constructed == 0) {
|
||||
return true;
|
||||
}
|
||||
constructed--;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int[] resolveLocals(Target target, InjectionNode node, Handler handler) {
|
||||
final int from = handler.localStart;
|
||||
final int[] slots = new int[handler.carryStart - from];
|
||||
for (int i = from; i < handler.carryStart; i++) {
|
||||
final Type type = handler.params[i];
|
||||
final LocalVariableDiscriminator discriminator = LocalVariableDiscriminator.parse(SurroundBinding.local(handler.method, i));
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
final Context context = InjectorUtils.getOrCreateLocalContext(target, node, this.info, type, discriminator.isArgsOnly());
|
||||
String detail;
|
||||
try {
|
||||
slots[i - from] = discriminator.findLocal(context);
|
||||
detail = slots[i - from] < 0 ? "no " + discriminator.toString(context) : null;
|
||||
} catch (InvalidImplicitDiscriminatorException e) {
|
||||
detail = e.getMessage();
|
||||
}
|
||||
if (detail != null) {
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, i) + " is @Surround.Local " + type.getClassName() + ", which matches no single local at the call: " + detail);
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private void prepare(Type[] expected, Type result, String against) {
|
||||
if (this.exits == null) {
|
||||
this.analyze(result);
|
||||
}
|
||||
this.checkArgPrefix(this.entry, expected, against);
|
||||
for (Handler exit : this.exits.handlers) {
|
||||
if (exit.role == Role.SKIPPED || exit.role == Role.RETURN || exit.handles) {
|
||||
final Type declared = Type.getReturnType(exit.method.desc);
|
||||
if (!declared.equals(result)) {
|
||||
throw error(exit.ref() + " must return " + result.getClassName() + " to match what it surrounds, not " + declared.getClassName());
|
||||
}
|
||||
}
|
||||
if (exit.role == Role.RETURN && exit.leading == 1 && (exit.params.length == 0 || !exit.params[0].equals(result))) {
|
||||
throw error(exit.ref() + " must declare the " + result.getClassName() + " result as its first parameter");
|
||||
}
|
||||
this.checkArgPrefix(exit, expected, against);
|
||||
}
|
||||
if (this.exits.skipped == null && this.exits.skipSlot >= 0 && result.getSize() > 0) {
|
||||
throw error("can skip a " + result.getClassName() + " result but has no @Surround.Skipped" + this.forId() + " to supply it");
|
||||
}
|
||||
}
|
||||
|
||||
private void analyze(Type result) {
|
||||
if (!this.returnType.equals(Type.VOID_TYPE)) {
|
||||
throw error("must return void; carry state out with @Surround.Carry locals instead");
|
||||
}
|
||||
if (Bytecode.hasFlag(this.methodNode, Opcodes.ACC_ABSTRACT)) {
|
||||
throw error("must not be abstract");
|
||||
}
|
||||
final Handler entry = new Handler(this.methodNode, Role.ENTRY, 0);
|
||||
if (this.hasStrippedSugar()) {
|
||||
throw error("one of its parameters is annotated @Local, @Share or @Cancellable" + SurroundBinding.SUGAR_HINT);
|
||||
}
|
||||
this.checkNoLocal(entry);
|
||||
final AbstractInsnNode soleReturn = SurroundBinding.soleReturn(this.methodNode);
|
||||
|
||||
final Set<String> ids = new LinkedHashSet<>();
|
||||
for (MethodNode method : this.mixin.getMixin().getClassNode(0).methods) {
|
||||
final AnnotationNode surround = Annotations.get(method.visibleAnnotations, SurroundSpec.SURROUND);
|
||||
if (surround == null) {
|
||||
continue;
|
||||
}
|
||||
final String declared = Annotations.<String>getValue(surround, "id", "");
|
||||
if (!ids.add(declared) && declared.equals(this.id)) {
|
||||
throw error("duplicate @Surround " + describeId(this.id) + "; ids must be unique per mixin");
|
||||
}
|
||||
}
|
||||
final List<Handler> handlers = new ArrayList<>();
|
||||
for (MethodNode method : this.mixin.getClassNode().methods) {
|
||||
Role role = null;
|
||||
AnnotationNode annotation = null;
|
||||
for (Role candidate : Role.EXITS) {
|
||||
final AnnotationNode found = Annotations.get(method.visibleAnnotations, candidate.annotation);
|
||||
if (found == null) {
|
||||
continue;
|
||||
}
|
||||
if (role != null) {
|
||||
throw error(exitRef(method) + " can be only one of @Surround.Catch, @Surround.Finally, @Surround.Skipped and @Surround.Return");
|
||||
}
|
||||
role = candidate;
|
||||
annotation = found;
|
||||
}
|
||||
if (role == null) {
|
||||
continue;
|
||||
}
|
||||
final String exitId = Annotations.<String>getValue(annotation, "value", "");
|
||||
if (!ids.contains(exitId)) {
|
||||
throw error(exitRef(method) + " " + noSurroundForId(exitId, ids));
|
||||
}
|
||||
if (this.id.equals(exitId)) {
|
||||
handlers.add(this.exitHandler(this.own(method), role, role == Role.CATCH || role == Role.RETURN && result.getSize() > 0 ? 1 : 0));
|
||||
}
|
||||
}
|
||||
handlers.sort(Comparator.comparing(handler -> handler.role));
|
||||
boolean skipped = false;
|
||||
for (int i = 0, n = handlers.size(); i < n; i++) {
|
||||
final Role role = handlers.get(i).role;
|
||||
skipped |= role == Role.SKIPPED;
|
||||
if (i > 0 && role != Role.CATCH && role == handlers.get(i - 1).role) {
|
||||
throw error("declares more than one @Surround." + role.name().charAt(0) + role.name().substring(1).toLowerCase(Locale.ROOT) + this.forId() + "; there can only be one");
|
||||
}
|
||||
}
|
||||
final Handler[] sorted = handlers.toArray(new Handler[0]);
|
||||
this.sortMostSpecificFirst(sorted);
|
||||
final List<LocalVariableAnnotationNode> annotations = this.methodNode.visibleLocalVariableAnnotations;
|
||||
final int[] kinds = annotations == null || annotations.isEmpty() ? null : SurroundBinding.kindsAtEnd(this.methodNode, soleReturn);
|
||||
final int skipSlot = SurroundBinding.skipSlot(this.methodNode, kinds);
|
||||
if (skipped && skipSlot < 0) {
|
||||
throw error("has a @Surround.Skipped" + this.forId() + " but declares no @Surround.Skip local, so nothing is ever skipped");
|
||||
}
|
||||
final Type[] carried = SurroundBinding.bind(this.methodNode, kinds, sorted);
|
||||
this.entry = entry;
|
||||
this.exits = new Exits(sorted, skipSlot, carried);
|
||||
}
|
||||
|
||||
// MixinExtras strips sugar params before parse; the annotation table can then be null.
|
||||
private boolean hasStrippedSugar() {
|
||||
final List<AnnotationNode>[] invisible = this.methodNode.invisibleParameterAnnotations;
|
||||
if (invisible != null) {
|
||||
return invisible.length != this.methodArgs.length;
|
||||
}
|
||||
final ClassInfo mixin = this.mixin.getClassInfo();
|
||||
final String desc = this.methodNode.desc;
|
||||
return mixin.findMethod(MethodNodeEx.getName(this.methodNode), desc, ClassInfo.INCLUDE_ALL) == null && mixin.findMethod(this.methodNode.name, desc, ClassInfo.INCLUDE_ALL) == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sanityCheck(Target target, List<InjectionPoint> injectionPoints) {
|
||||
super.sanityCheck(target, injectionPoints);
|
||||
if (this.isInterface) {
|
||||
throw error("cannot target an interface method " + target);
|
||||
}
|
||||
if (target.method.name.endsWith("init>")) {
|
||||
throw error("cannot target the initializer " + target);
|
||||
}
|
||||
if (Bytecode.hasFlag(target.method, Opcodes.ACC_ABSTRACT) || Bytecode.hasFlag(target.method, Opcodes.ACC_NATIVE)) {
|
||||
throw error("cannot target the abstract or native method " + target);
|
||||
}
|
||||
}
|
||||
|
||||
private Handler exitHandler(MethodNode method, Role role, int leading) {
|
||||
final Handler exit = new Handler(method, role, leading);
|
||||
if (exit.handles && !this.callSite) {
|
||||
throw error(exit.ref() + " declares handle = true, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
if (Bytecode.hasFlag(method, Opcodes.ACC_ABSTRACT)) {
|
||||
throw error(exit.ref() + " must not be abstract");
|
||||
}
|
||||
if ((role == Role.FINALLY || role == Role.CATCH && !exit.handles) && !Type.getReturnType(method.desc).equals(Type.VOID_TYPE)) {
|
||||
throw error(exit.ref() + " must return void; an exit handler cannot suppress or replace anything");
|
||||
}
|
||||
if (Bytecode.isStatic(method) != this.isStatic) {
|
||||
throw error(exit.ref() + " must be " + (this.isStatic ? "static" : "non-static") + " to match the entry handler" + this.forId());
|
||||
}
|
||||
if (role == Role.CATCH) {
|
||||
if (exit.params.length == 0) {
|
||||
throw error(exit.ref() + " must declare the Throwable it catches as its first parameter");
|
||||
}
|
||||
final Type caught = exit.caught();
|
||||
if (caught.getSort() != Type.OBJECT || !SurroundSpec.THROWABLE.equals(caught.getInternalName()) && !extendsClass(caught, SurroundSpec.THROWABLE)) {
|
||||
throw error(exit.ref() + " " + paramRef(method, 0) + " catches " + caught.getClassName() + ", which is not a java.lang.Throwable subtype");
|
||||
}
|
||||
}
|
||||
this.checkNoLocal(exit);
|
||||
return exit;
|
||||
}
|
||||
|
||||
private void checkNoLocal(Handler handler) {
|
||||
if (!this.callSite && handler.localStart < handler.carryStart) {
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, handler.localStart) + " is @Surround.Local, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkArgPrefix(Handler handler, Type[] expected, String against) {
|
||||
final Type[] params = handler.params;
|
||||
if (handler.localStart - handler.leading > expected.length) {
|
||||
throw error(handler.ref() + " has more parameters than " + against);
|
||||
}
|
||||
for (int i = handler.leading, arg = 0; i < handler.localStart; i++, arg++) {
|
||||
if (params[i].equals(expected[arg]) || this.callSite && isAssignable(params[i], expected[arg])) {
|
||||
continue;
|
||||
}
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, i) + " is " + params[i].getClassName() + ", which does not match the " + expected[arg].getClassName() + " operand of " + against);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAssignable(Type param, Type operand) {
|
||||
if (param.getSort() != Type.OBJECT || operand.getSort() != Type.OBJECT && operand.getSort() != Type.ARRAY) {
|
||||
return false;
|
||||
}
|
||||
if (SurroundSpec.OBJECT.equals(param.getInternalName())) {
|
||||
return true;
|
||||
}
|
||||
if (operand.getSort() != Type.OBJECT) {
|
||||
return false;
|
||||
}
|
||||
final ClassInfo info = ClassInfo.forName(operand.getInternalName());
|
||||
return info != null && info.findSuperClass(param.getInternalName(), Traversal.ALL, true) != null;
|
||||
}
|
||||
|
||||
private void sortMostSpecificFirst(Handler[] exits) {
|
||||
for (int k = 1; k < exits.length && exits[k].role == Role.CATCH; k++) {
|
||||
final Handler exit = exits[k];
|
||||
final Type caught = exit.caught();
|
||||
int at = k;
|
||||
for (int i = 0; i < k; i++) {
|
||||
final Type other = exits[i].caught();
|
||||
if (caught.equals(other)) {
|
||||
throw error(exit.ref() + " is a second @Surround.Catch for " + caught.getClassName() + this.forId() + "; each type may be caught once");
|
||||
}
|
||||
if (at == k && extendsClass(caught, other.getInternalName())) {
|
||||
at = i;
|
||||
}
|
||||
}
|
||||
System.arraycopy(exits, at, exits, at + 1, k - at);
|
||||
exits[at] = exit;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean extendsClass(Type type, String superName) {
|
||||
final ClassInfo info = ClassInfo.forName(type.getInternalName());
|
||||
return info != null && info.hasSuperClass(superName);
|
||||
}
|
||||
|
||||
private MethodNode own(MethodNode exit) {
|
||||
final MethodNode method = Bytecode.findMethod(this.classNode, exit.name, exit.desc);
|
||||
if (method == null) {
|
||||
throw new IllegalStateException(exit.name + exit.desc + " is missing from " + this.classNode.name);
|
||||
}
|
||||
if (!this.mixin.getClassName().equals(SurroundSpec.mergedBy(method))) {
|
||||
throw error(exitRef(exit) + " collides with " + exit.name + exit.desc + " in " + this.classNode.name + ", which is " + SurroundSpec.describeOwner(method));
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private String forId() {
|
||||
return this.id.isEmpty() ? "" : " for id '" + this.id + "'";
|
||||
}
|
||||
|
||||
private static String describeId(String id) {
|
||||
return id.isEmpty() ? "the default id" : "id '" + id + "'";
|
||||
}
|
||||
|
||||
static String noSurroundForId(String id, Collection<String> declared) {
|
||||
final StringJoiner ids = new StringJoiner(", ", "; declared ids are ", "").setEmptyValue("; this mixin has no @Surround at all");
|
||||
for (String other : declared) {
|
||||
ids.add(describeId(other));
|
||||
}
|
||||
return "refers to " + describeId(id) + " but no @Surround in this mixin declares it" + ids;
|
||||
}
|
||||
|
||||
private InvalidInjectionException error(String message) {
|
||||
return new InvalidInjectionException(this.info, "@Surround " + this.info.getMethodName() + " " + message);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
final class SurroundRejection extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
SurroundRejection(String message) {
|
||||
super(message, null, false, false);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
interface SurroundSpec {
|
||||
|
||||
String SURROUND = Type.getDescriptor(Surround.class);
|
||||
String CATCH = Type.getDescriptor(Surround.Catch.class);
|
||||
String FINALLY = Type.getDescriptor(Surround.Finally.class);
|
||||
String CARRY = Type.getDescriptor(Surround.Carry.class);
|
||||
String SKIP = Type.getDescriptor(Surround.Skip.class);
|
||||
String SKIPPED = Type.getDescriptor(Surround.Skipped.class);
|
||||
String RETURN = Type.getDescriptor(Surround.Return.class);
|
||||
String LOCAL = Type.getDescriptor(Surround.Local.class);
|
||||
String MIXIN_MERGED = Type.getDescriptor(MixinMerged.class);
|
||||
String OBJECT = Type.getInternalName(Object.class);
|
||||
String THROWABLE = Type.getInternalName(Throwable.class);
|
||||
String THROWABLE_DESC = Type.getDescriptor(Throwable.class);
|
||||
|
||||
static String mergedBy(MethodNode method) {
|
||||
final AnnotationNode merged = Annotations.get(method.visibleAnnotations, MIXIN_MERGED);
|
||||
return merged == null ? null : Annotations.<String>getValue(merged, "mixin");
|
||||
}
|
||||
|
||||
static String describeOwner(MethodNode method) {
|
||||
final String owner = mergedBy(method);
|
||||
return owner == null ? "not merged from a mixin" : "merged from " + owner;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Entity;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.ReportedException;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.TryCatchBlockNode;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.CALL;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.call;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.method;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.transformedNode;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CallSiteTest {
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRedirectedCallInsideJavacTryCatchesRethrowsInsideTheEnclosingTry() throws Exception {
|
||||
assertEquals(true, call(CALL, "render", new Entity("pig"), 1.0d, 2.0d, 3.0d, 0.5f, 0.25f, true));
|
||||
final Entity creeper = new Entity("creeper");
|
||||
creeper.explode = true;
|
||||
final InvocationTargetException thrown = assertThrows(InvocationTargetException.class, () -> call(CALL, "render", creeper, 1.0d, 2.0d, 3.0d, 0.5f, 0.25f, false));
|
||||
|
||||
final ReportedException outer = (ReportedException) thrown.getCause();
|
||||
assertEquals("Rendering entity (outer)", outer.getMessage());
|
||||
assertEquals("Rendering entity in world", outer.getCause().getMessage());
|
||||
assertSame(RuntimeException.class, outer.getCause().getCause().getClass());
|
||||
assertEquals("gpu", outer.getCause().getCause().getMessage());
|
||||
Trace.assertEvents("enterDoRender pig 1", "redirect pig", "doRender pig 1.0 0.25", "exitDoRender pig 1", "shadow pig", "boundingBox pig", "enterDoRender creeper 2", "redirect creeper", "doRender creeper 1.0 0.25", "caughtDoRender creeper gpu 2", "exitDoRender creeper 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theSurroundedCallMatchesTheGoldens() {
|
||||
MixinTestBootstrap.assertGolden(CALL, "valueSkip", "call-skip");
|
||||
MixinTestBootstrap.assertGolden(CALL, "valueBelow", "call-handle");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedSurroundsShareOneCallAndOrderTheirTryRangesInnerFirst() {
|
||||
final MethodNode target = method(transformedNode(CALL), "innerSkipOuterReturn");
|
||||
int calls = 0;
|
||||
for (AbstractInsnNode insn : target.instructions.toArray()) {
|
||||
calls += insn instanceof MethodInsnNode m && m.name.equals("twice") ? 1 : 0;
|
||||
}
|
||||
assertEquals(1, calls);
|
||||
final List<TryCatchBlockNode> blocks = target.tryCatchBlocks;
|
||||
boolean nests = false;
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
for (int j = 0; j < blocks.size(); j++) {
|
||||
final boolean encloses = encloses(target, blocks.get(i), blocks.get(j));
|
||||
nests |= encloses;
|
||||
assertTrue(!encloses || i > j, "block " + i + " encloses block " + j);
|
||||
}
|
||||
}
|
||||
assertTrue(nests, "no try block encloses another");
|
||||
}
|
||||
|
||||
private static boolean encloses(MethodNode target, TryCatchBlockNode outer, TryCatchBlockNode inner) {
|
||||
final int outerStart = target.instructions.indexOf(outer.start);
|
||||
final int outerEnd = target.instructions.indexOf(outer.end);
|
||||
final int innerStart = target.instructions.indexOf(inner.start);
|
||||
final int innerEnd = target.instructions.indexOf(inner.end);
|
||||
return outerStart <= innerStart && innerEnd <= outerEnd && (outerStart < innerStart || innerEnd < outerEnd);
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.utils.MixinInternals;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.Extensions;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.IExtension;
|
||||
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WRAP;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.of;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.scenario;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CompositionTest {
|
||||
|
||||
private static final Set<String> RUN_BEFORE = Set.of("LateInjectionApplicatorExtension", "SugarPostProcessingExtension", "WrapMethodApplicatorExtension");
|
||||
private static final String CHECK_CLASS = "ExtensionCheckClass";
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
private static Object wrap(String name, Object... args) throws Exception {
|
||||
return MixinTestBootstrap.call(WRAP, name, args);
|
||||
}
|
||||
|
||||
static Stream<Arguments> mixinExtras() {
|
||||
return Stream.of(
|
||||
scenario(WRAP, "the surround is outside @WrapMethod", "wrapped", of(3), of(6), "entry 3", "wrap-before 3", "body 3", "wrap-after 6", "finally"),
|
||||
scenario(WRAP, "the entry sees the operand before @WrapOperation shifts it", "originalOperands", of(5), of(210), "enter 5", "wrap 5", "twice 105", "exit 5"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("mixinExtras")
|
||||
@Tag("mixinextras")
|
||||
void mixinExtras(String method, String target, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
MixinTestBootstrap.assertScenario(target, method, inputs, outputs, events);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void aLocalFollowsTheBodyWhenAShareShiftsItsSlot() throws Exception {
|
||||
assertEquals(10, wrap("shifted", 3));
|
||||
Trace.assertEvents("wrap 3", "enterShifted 4 4 L4", "twice 4", "exitShifted 4 L4");
|
||||
MethodNode body = null;
|
||||
for (MethodNode method : MixinTestBootstrap.transformedNode(WRAP).methods) {
|
||||
body = method.name.startsWith("shifted$") ? method : body;
|
||||
}
|
||||
assertTrue(body != null && body.desc.startsWith("(IL"), "the body did not move and gain the share");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void ourExtensionRunsAfterMixinExtrasAndBeforeCheckClass() throws Exception {
|
||||
MixinTestBootstrap.transformer();
|
||||
final SurroundApplicatorExtension ours = SurroundApplicatorExtension.instance();
|
||||
MixinTestBootstrap.transform(WRAP);
|
||||
final Extensions extensions = MixinInternals.getExtensions();
|
||||
for (List<IExtension> list : List.of(extensions.getExtensions(), extensions.getActiveExtensions())) {
|
||||
final int at = assertSlot(list, ours);
|
||||
for (String name : RUN_BEFORE) {
|
||||
final int index = names(list).indexOf(name);
|
||||
assertTrue(index >= 0 && index < at, names(list).toString());
|
||||
assertTrue(list.get(index).getClass().getName().startsWith("com.llamalad7.mixinextras."));
|
||||
}
|
||||
}
|
||||
assertCheckClassActiveUnderVerify(extensions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnrenamedSurroundHandlerIsFatal() {
|
||||
final String mixin = "com.example.mixin.MixinLate";
|
||||
final MethodNode method = new MethodNode(Opcodes.ACC_PRIVATE, "surround$enter", "()V", null, null);
|
||||
method.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||
method.visitAnnotation(Type.getDescriptor(Surround.class), true).visitEnd();
|
||||
method.visitAnnotation(Type.getDescriptor(MixinMerged.class), true).visit("mixin", mixin);
|
||||
final ClassNode target = new ClassNode();
|
||||
target.version = Opcodes.V1_8;
|
||||
target.access = Opcodes.ACC_PUBLIC;
|
||||
target.name = "com/example/Target";
|
||||
target.superName = "java/lang/Object";
|
||||
target.methods.add(method);
|
||||
|
||||
final String message = assertThrows(IllegalStateException.class, () -> SurroundApplicatorExtension.checkHandlersRenamed(target, Set.of())).getMessage();
|
||||
assertTrue(message.startsWith("surround$enter()V in com/example/Target (merged from " + mixin + ") carries @Surround but was never parsed as an injector"), message);
|
||||
assertTrue(message.contains("SurroundBootstrap.init() must run before"), message);
|
||||
}
|
||||
|
||||
private static void assertCheckClassActiveUnderVerify(Extensions extensions) {
|
||||
final List<String> active = names(extensions.getActiveExtensions());
|
||||
assertEquals(Boolean.getBoolean("mixin.debug.verify"), active.contains(CHECK_CLASS), active.toString());
|
||||
}
|
||||
|
||||
private static List<String> names(List<IExtension> list) {
|
||||
final List<String> names = new ArrayList<>(list.size());
|
||||
for (IExtension extension : list) {
|
||||
names.add(extension.getClass().getSimpleName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private static int assertSlot(List<IExtension> list, IExtension ours) {
|
||||
final List<String> names = names(list);
|
||||
assertEquals(list.indexOf(ours), list.lastIndexOf(ours), "registered twice: " + names);
|
||||
final int at = list.indexOf(ours);
|
||||
assertTrue(at >= 0, "missing: " + names);
|
||||
for (String after : names.subList(at + 1, names.size())) {
|
||||
assertFalse(RUN_BEFORE.contains(after), after + " runs after us: " + names);
|
||||
}
|
||||
final int check = names.indexOf(CHECK_CLASS);
|
||||
assertTrue(check < 0 || check > at, CHECK_CLASS + " runs before us: " + names);
|
||||
return at;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import makamys.mixingasm.api.MixinSafeTransformer;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.commons.LocalVariablesSorter;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@MixinSafeTransformer
|
||||
public final class LocalSortingTransformer implements IClassTransformer {
|
||||
|
||||
public static final Set<String> SEEN = ConcurrentHashMap.newKeySet();
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] bytes) {
|
||||
SEEN.add(transformedName);
|
||||
if (bytes == null || ((bytes[6] & 0xFF) << 8 | bytes[7] & 0xFF) > Opcodes.V1_8) {
|
||||
return bytes;
|
||||
}
|
||||
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
new ClassReader(bytes).accept(new ClassVisitor(Opcodes.ASM5, writer) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
return new LocalVariablesSorter(Opcodes.ASM5, access, desc, super.visitMethod(access, name, desc, signature, exceptions)) {
|
||||
};
|
||||
}
|
||||
}, ClassReader.EXPAND_FRAMES);
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.MixinExtrasBootstrap;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraft.launchwrapper.LaunchClassLoader;
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.spongepowered.asm.launch.MixinBootstrap;
|
||||
import org.spongepowered.asm.lib.ClassReader;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.util.Textifier;
|
||||
import org.spongepowered.asm.lib.util.TraceClassVisitor;
|
||||
import org.spongepowered.asm.mixin.EnvironmentStateTweaker;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.Mixins;
|
||||
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public final class MixinTestBootstrap {
|
||||
|
||||
private MixinTestBootstrap() {
|
||||
}
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
static final IMixinTransformer TRANSFORMER = boot();
|
||||
}
|
||||
|
||||
public static IMixinTransformer transformer() {
|
||||
return Holder.TRANSFORMER;
|
||||
}
|
||||
|
||||
private static final Map<String, byte[]> TRANSFORMED = new HashMap<>();
|
||||
private static final Map<String, Class<?>> DEFINED = new HashMap<>();
|
||||
|
||||
public static byte[] transformedBytes(String binaryName) {
|
||||
return TRANSFORMED.computeIfAbsent(binaryName, name -> transformer().transformClassBytes(name, name, read(name)));
|
||||
}
|
||||
|
||||
public static Class<?> transform(String binaryName) {
|
||||
return DEFINED.computeIfAbsent(binaryName, name -> define(name, transformedBytes(name)));
|
||||
}
|
||||
|
||||
public static Object call(String target, String name, Object... args) throws Exception {
|
||||
final Class<?> owner = transform(target);
|
||||
for (Method method : owner.getMethods()) {
|
||||
if (method.getName().equals(name) && method.getParameterCount() == args.length) {
|
||||
final boolean isStatic = Modifier.isStatic(method.getModifiers());
|
||||
return method.invoke(isStatic ? null : owner.getDeclaredConstructor().newInstance(), args);
|
||||
}
|
||||
}
|
||||
throw new AssertionError("no " + name + " in " + target);
|
||||
}
|
||||
|
||||
static final String WHOLE = "com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget";
|
||||
static final String CALL = "com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget";
|
||||
static final String WRAP = "com.gtnewhorizons.angelica.experimental.surround.integration.WrapTarget";
|
||||
|
||||
static ClassNode transformedNode(String target) {
|
||||
return parse(transformedBytes(target));
|
||||
}
|
||||
|
||||
static ClassNode originalNode(String binaryName) {
|
||||
return parse(read(binaryName));
|
||||
}
|
||||
|
||||
private static ClassNode parse(byte[] bytes) {
|
||||
final ClassNode node = new ClassNode();
|
||||
new ClassReader(bytes).accept(node, 0);
|
||||
return node;
|
||||
}
|
||||
|
||||
static MethodNode method(ClassNode node, String name) {
|
||||
for (MethodNode method : node.methods) {
|
||||
if (method.name.equals(name)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("no method " + name + " in " + node.name);
|
||||
}
|
||||
|
||||
static void assertGolden(String target, String method, String golden) {
|
||||
final ClassNode full = transformedNode(target);
|
||||
final ClassNode slim = new ClassNode();
|
||||
slim.visit(full.version, full.access, full.name, null, full.superName, null);
|
||||
slim.methods.add(method(full, method));
|
||||
for (MethodNode candidate : full.methods) {
|
||||
if (candidate.name.startsWith(method + "$surround")) {
|
||||
slim.methods.add(candidate);
|
||||
}
|
||||
}
|
||||
final StringWriter out = new StringWriter();
|
||||
slim.accept(new TraceClassVisitor(null, new Textifier(), new PrintWriter(out)));
|
||||
final String stripped = out.toString().replaceAll("(?m)^ *LINENUMBER \\d+ L\\d+\\r?\\n", "");
|
||||
final String actual = normalize(stripped.replaceAll("\\b([a-z]+)\\$[a-z]{3,}[0-9a-f]{3,}\\$", "$1\\$\\$").replaceAll("\\bmd[0-9a-f]+\\$jvmdowngrader\\$", "md\\$jvmdowngrader\\$"));
|
||||
final Path file = Paths.get(System.getProperty("surround.golden.dir")).resolve(golden + ".asm");
|
||||
try {
|
||||
if (!Files.exists(file)) {
|
||||
Files.write(file, actual.getBytes(StandardCharsets.UTF_8));
|
||||
fail("Created golden " + file + ". Inspect it, then re-run.");
|
||||
}
|
||||
assertEquals(normalize(new String(Files.readAllBytes(file), StandardCharsets.UTF_8)), actual, "output changed; inspect the diff before updating " + file);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalize(String text) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
for (String line : text.replace("\r\n", "\n").split("\n", -1)) {
|
||||
result.append(line.replaceAll("\\s+$", "").replaceAll("(\\$jvmdowngrader\\$concat\\$[\\w$]*?\\$\\d+)\\$[0-9a-f]+\\b", "$1")).append('\n');
|
||||
}
|
||||
return result.toString().trim() + "\n";
|
||||
}
|
||||
|
||||
static final Object VOID = null;
|
||||
|
||||
static Arguments scenario(String target, String name, String method, Object[] inputs, Object[] outputs, String... events) {
|
||||
return Arguments.of(Named.of(name, method), target, inputs, outputs, events);
|
||||
}
|
||||
|
||||
static Object[] of(Object... values) {
|
||||
return values;
|
||||
}
|
||||
|
||||
static List<Object> args(Object... values) {
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
static void assertScenario(String target, String method, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
Trace.reset();
|
||||
for (int i = 0; i < inputs.length; i++) {
|
||||
final Object[] args = inputs[i] instanceof List<?> list ? list.toArray() : new Object[]{inputs[i]};
|
||||
if (outputs[i] instanceof Class<?> type && Throwable.class.isAssignableFrom(type)) {
|
||||
final InvocationTargetException thrown = assertThrows(InvocationTargetException.class, () -> call(target, method, args));
|
||||
assertEquals(outputs[i], thrown.getCause().getClass());
|
||||
} else {
|
||||
assertEquals(outputs[i], call(target, method, args));
|
||||
}
|
||||
}
|
||||
Trace.assertEvents(events);
|
||||
}
|
||||
|
||||
private static final boolean MIXINEXTRAS = Boolean.getBoolean("surround.test.mixinextras");
|
||||
private static final boolean ADDITIONS_FIRST = Boolean.getBoolean("surround.test.additionsFirst");
|
||||
private static final boolean OBF = Boolean.getBoolean("surround.test.obf");
|
||||
|
||||
private static IMixinTransformer boot() {
|
||||
Launch.blackboard = new HashMap<>();
|
||||
Launch.classLoader = new LaunchClassLoader(classpath());
|
||||
if (OBF) {
|
||||
Launch.classLoader.addTransformerExclusion(LocalSortingTransformer.class.getName());
|
||||
Launch.classLoader.registerTransformer(LocalSortingTransformer.class.getName());
|
||||
if (Launch.classLoader.getTransformers().stream().noneMatch(t -> t.getClass().getName().equals(LocalSortingTransformer.class.getName()))) {
|
||||
throw new IllegalStateException("LocalSortingTransformer was not registered");
|
||||
}
|
||||
}
|
||||
|
||||
if (!MIXINEXTRAS) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
MixinBootstrap.init();
|
||||
if (MIXINEXTRAS) {
|
||||
if (ADDITIONS_FIRST) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
MixinExtrasBootstrap.init();
|
||||
if (!ADDITIONS_FIRST) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
}
|
||||
Mixins.addConfiguration("mixins.surround.test.json");
|
||||
if (!OBF) {
|
||||
Mixins.addConfiguration(RejectionCases.compileConfig(MIXINEXTRAS));
|
||||
}
|
||||
if (MIXINEXTRAS) {
|
||||
Mixins.addConfiguration("mixins.surround.mixinextras.test.json");
|
||||
}
|
||||
|
||||
final EnvironmentStateTweaker tweaker = new EnvironmentStateTweaker();
|
||||
tweaker.injectIntoClassLoader(Launch.classLoader);
|
||||
tweaker.getLaunchArguments();
|
||||
|
||||
return active();
|
||||
}
|
||||
|
||||
private static IMixinTransformer active() {
|
||||
final Object transformer = MixinEnvironment.getCurrentEnvironment().getActiveTransformer();
|
||||
if (!(transformer instanceof IMixinTransformer mixinTransformer)) {
|
||||
throw new IllegalStateException("no active mixin transformer, got " + transformer);
|
||||
}
|
||||
return mixinTransformer;
|
||||
}
|
||||
|
||||
private static URL[] classpath() {
|
||||
final List<URL> urls = new ArrayList<>();
|
||||
for (String entry : System.getProperty("java.class.path").split(File.pathSeparator)) {
|
||||
try {
|
||||
urls.add(new File(entry).toURI().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalStateException(entry, e);
|
||||
}
|
||||
}
|
||||
return urls.toArray(new URL[urls.size()]);
|
||||
}
|
||||
|
||||
static byte[] read(String binaryName) {
|
||||
try {
|
||||
final byte[] bytes = Launch.classLoader.getClassBytes(binaryName);
|
||||
if (bytes == null) {
|
||||
throw new IllegalStateException("missing " + binaryName);
|
||||
}
|
||||
return bytes;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(binaryName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> define(String name, byte[] bytes) {
|
||||
try {
|
||||
return new DefiningClassLoader(name, bytes).loadClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DefiningClassLoader extends ClassLoader {
|
||||
|
||||
private final String name;
|
||||
private final byte[] bytes;
|
||||
|
||||
DefiningClassLoader(String name, byte[] bytes) {
|
||||
super(MixinTestBootstrap.class.getClassLoader());
|
||||
this.name = name;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
if (!this.name.equals(name)) {
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
Class<?> loaded = findLoadedClass(name);
|
||||
if (loaded == null) {
|
||||
loaded = defineClass(name, this.bytes, 0, this.bytes.length);
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(loaded);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
final class RejectionCases {
|
||||
|
||||
static final String PACKAGE = "rejection";
|
||||
static final String CONFIG = "mixins.surround.rejection.test.json";
|
||||
static final String ABS = "at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;abs(I)I\")";
|
||||
static final String WHOLE_ENTRY = "@Surround(method = \"run\") private void invalid$enter(int a) {}";
|
||||
static final String FINALLY = " @Surround.Finally private void invalid$always() {}";
|
||||
static final String RUN = cls("public int run(int a) { return Math.abs(a); }");
|
||||
static final String IN_NEW = cls("public int run(int a) { return new StringBuilder(Math.abs(a)).length(); }");
|
||||
|
||||
static final Case[] CASES = {
|
||||
new Case("Initializer", "@Surround invalid$enter cannot target the initializer", RUN, mixin("@Surround(method = \"<init>()V\") private void invalid$enter() {}")),
|
||||
new Case("Staticness", "must be non-static to match the entry handler", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private static void invalid$always() {}")),
|
||||
new Case("CarriedThrowable", "cannot mark its caught Throwable parameter @Surround.Carry", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " @Surround.Carry Throwable state = null; }" + " @Surround.Catch private void invalid$caught(@Surround.Carry Throwable e) {}")),
|
||||
new Case("LocalCall", "is @Surround.Local java.lang.String, which matches no single local at the call", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a, @Surround.Local String s) {}")),
|
||||
new Case("HandleInsideNew", "declares handle = true, but the call is inside a constructor call's arguments", IN_NEW, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Catch(handle = true) private int invalid$caught(Throwable error) { return 0; }")),
|
||||
new Case("SkippedRequired", "can skip a int result but has no @Surround.Skipped", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {" + " @Surround.Skip boolean skip = a > 0; }")),
|
||||
new Case("Shift", "@Surround invalid$enter has @At(\"INVOKE\") with a non-default shift or by, which moves the match off the call", RUN, mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;abs(I)I\", shift = At.Shift.AFTER)) private void invalid$enter() {}")),
|
||||
new Case("AbstractTarget", "cannot target the abstract or native method", "public abstract class $T { public abstract int run(int a); }", "@Mixin(value = $T.class, remap = false) public abstract class $M {" + " @Surround(method = \"run\") private void invalid$enter(int a) {} }"),
|
||||
new Case("InitAt", "at must select a method call, not the constructor call java/lang/StringBuilder.<init>()V", cls("public Object run(int a) { return new StringBuilder(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/StringBuilder;<init>()V\")) private void invalid$enter() {}")),
|
||||
new Case("CatchingEntry", "must not catch exceptions when it surrounds a call inside a constructor call's arguments", IN_NEW, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {" + " try { Math.abs(a); } catch (RuntimeException e) { Math.abs(0); } }" + FINALLY)),
|
||||
new Case("TooManyOperands", "entry handler has more parameters than call java/lang/Math.abs(I)I", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a, int b) {}")),
|
||||
new Case("SkipNotAssigned", "has a @Surround.Skip local in slot 2 that is not definitely assigned", cls("public void run(int a) {}"), mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " @Surround.Skip boolean skip; if (a > 0) { skip = true; String.valueOf(a); } }")),
|
||||
new Case("Collision", "exit handler 'surround$exit' collides with surround$exit()V in " + PACKAGE + "/Collision, which is merged from " + PACKAGE + ".mixin.CollisionMixin2", RUN, "@Mixin(value = $T.class, priority = 900, remap = false) public class $M {" + " @Surround(method = \"run\") private void surround$enter(int a) {}" + " @Surround.Finally private void surround$exit() {} }", "@Mixin(value = $T.class, priority = 1100, remap = false) public class $M {" + " private void surround$exit() {} }"),
|
||||
new Case("MultipleRoles", "can be only one of @Surround.Catch, @Surround.Finally, @Surround.Skipped and @Surround.Return", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch @Surround.Finally private void invalid$exit(Throwable e) {}")),
|
||||
new Case("DuplicateId", "duplicate @Surround the default id; ids must be unique per mixin", RUN, mixin(WHOLE_ENTRY + " @Surround(method = \"run\") private void invalid$enter2(int a) {}")),
|
||||
new Case("OrphanExit", "no @Surround in this mixin declares it", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally(\"typo\") private void invalid$always() {}")),
|
||||
new Case("CarryOnEntryParameter", "only the entry handler's own locals can be carried", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(@Surround.Carry int a) {}")),
|
||||
new Case("CarryIdNotDeclared", "asks for the carried local 'frist'", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry(\"first\") int first = 1; }" + " @Surround.Finally private void invalid$always(@Surround.Carry(\"frist\") int typo) {}")),
|
||||
new Case("NoCarryOfKind", "matches no carried local of that kind", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry int count = 1; }" + " @Surround.Finally private void invalid$always(@Surround.Carry float missing) {}")),
|
||||
new Case("AmbiguousCarry", "matches 2 carried locals of that kind", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry int first = 1; @Surround.Carry int second = 2; }" + " @Surround.Finally private void invalid$always(@Surround.Carry int either) {}")),
|
||||
new Case("CarryReadAsDifferentType", "but another exit handler reads it as java.lang.String", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry Object value = null; }" + " @Surround.Catch private void invalid$caught(java.io.IOException e, @Surround.Carry String value) {}" + " @Surround.Finally private void invalid$always(@Surround.Carry Integer value) {}")),
|
||||
new Case("AmbiguousWideLocal", "shares its scope with another", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry long first; long second; try { first = a; second = a + 1L; } catch (RuntimeException e) { throw e; } }")),
|
||||
new Case("DuplicateCarryId", "declares two @Surround.Carry locals named 'x'", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry(\"x\") int first = 1; @Surround.Carry(\"x\") long second = 2L; }")),
|
||||
new Case("CarryInsideType", "@Surround.Carry must annotate the local itself, not part of its type", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { java.util.List<@Surround.Carry String> l = null; }")),
|
||||
new Case("DuplicateSkip", "declares two @Surround.Skip locals", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Skip boolean first = a > 0; @Surround.Skip boolean second = a < 0; }")),
|
||||
new Case("SkipNotIntLike", "it must be boolean or int-like", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Skip long skip = a; }")),
|
||||
new Case("SkippedWithoutSkip", "but declares no @Surround.Skip local, so nothing is ever skipped", RUN, mixin(WHOLE_ENTRY + " @Surround.Skipped private int invalid$skipped(int a) { return 0; }")),
|
||||
new Case("CarryScopedEarly", "goes out of scope before the entry handler ends", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " { @Surround.Carry int scoped = a; String.valueOf(scoped); } int reused = a + 1; String.valueOf(reused); }" + " @Surround.Finally private void invalid$always(@Surround.Carry int value) {}")),
|
||||
new Case("EarlyReturn", "every path falls through to its end", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " if (a > 0) { return; } String.valueOf(a); }" + FINALLY)),
|
||||
new Case("PartialAfterInt", "is not definitely assigned when the entry handler ends", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " { int tmp = a + 5; String.valueOf(tmp); }" + " @Surround.Carry int flag; if (a > 0) { flag = 1; String.valueOf(flag); } }" + " @Surround.Finally private void invalid$always(@Surround.Carry int flag) {}")),
|
||||
new Case("AssignsParameter", "must not assign its parameters", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { a = a + 1; }" + FINALLY)),
|
||||
new Case("LocalOnWholeMethod", "is @Surround.Local, which only applies when @Surround.at selects a call", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private void invalid$always(@Surround.Local int b) {}")),
|
||||
new Case("IncrementsParameter", "must not assign its parameters", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { a++; }" + FINALLY)),
|
||||
new Case("NonCallAt", "@Surround invalid$enter has @At(\"HEAD\"), which does not select a method call", RUN, mixin("@Surround(method = \"run\", at = @At(\"HEAD\")) private void invalid$enter() {}")),
|
||||
new Case("CancellableExit", "parameter 0 ('ci') of 'invalid$always' is annotated @Cancellable, which a @Surround handler cannot resolve", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private void invalid$always(@Cancellable Object ci) {}")),
|
||||
new Case("DuplicateFinally", "declares more than one @Surround.Finally", RUN, mixin(WHOLE_ENTRY + FINALLY + " @Surround.Finally private void invalid$always2() {}")),
|
||||
new Case("ReturnWrongType", "must return java.lang.String to match what it surrounds, not int", cls("public String run(String s) { return s.trim(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/String;trim()Ljava/lang/String;\")) private void invalid$enter(String s) {}" + " @Surround.Return private int invalid$ret(int result) { return result; }")),
|
||||
new Case("ReturnResultNotFirst", "must declare the int result as its first parameter", RUN, mixin(WHOLE_ENTRY + " @Surround.Return private int invalid$ret(String wrong) { return 0; }")),
|
||||
new Case("ReturnCarriedResult", "cannot mark its result parameter @Surround.Carry", RUN, mixin(WHOLE_ENTRY + " @Surround.Return private int invalid$ret(@Surround.Carry int result) { return result; }")),
|
||||
new Case("NonVoidFinally", "must return void; an exit handler cannot suppress or replace anything", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private int invalid$always() { return 1; }")),
|
||||
new Case("NonVoidEntry", "must return void; carry state out with @Surround.Carry locals instead", RUN, mixin("@Surround(method = \"run\") private long invalid$enter(int a) { return 0L; }")),
|
||||
new Case("CatchNotThrowable", "parameter 0 ('notAnError') catches java.lang.String, which is not a java.lang.Throwable subtype", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$caught(String notAnError) {}")),
|
||||
new Case("DuplicateCatch", "is a second @Surround.Catch for java.io.IOException", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$first(java.io.IOException e) {} @Surround.Catch private void invalid$second(java.io.IOException e) {}")),
|
||||
new Case("CatchWithoutParameters", "must declare the Throwable it catches as its first parameter", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$caught() {}")),
|
||||
new Case("AbstractEntry", "must not be abstract", RUN, "@Mixin(value = $T.class, remap = false) public abstract class $M { @Surround(method = \"run\") protected abstract void invalid$enter(int a); }"),
|
||||
new Case("AbstractCatch", "must not be abstract", RUN, "@Mixin(value = $T.class, remap = false) public abstract class $M { " + WHOLE_ENTRY + " @Surround.Catch protected abstract void invalid$caught(Throwable t); }"),
|
||||
new Case("MisorderedLocalAfterCarry", "parameter 1 ('y') is out of order; declare operands first, then @Surround.Local, then @Surround.Carry parameters", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Finally private void invalid$always(@Surround.Carry int x, @Surround.Local int y) {}")),
|
||||
new Case("LocalAndCarry", "parameter 0 ('a') cannot be both @Surround.Local and @Surround.Carry", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Finally private void invalid$always(@Surround.Local @Surround.Carry int a) {}")),
|
||||
new Case("LocalCaught", "cannot mark its caught Throwable parameter @Surround.Local", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Catch private void invalid$caught(@Surround.Local Throwable e) {}")),
|
||||
new Case("HandleWrongReturn", "must return java.lang.String to match what it surrounds, not int", cls("public String run(String s) { return s.trim(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/String;trim()Ljava/lang/String;\")) private void invalid$enter(String s) {}" + " @Surround.Catch(handle = true) private int invalid$caught(Throwable e, String s) { return 0; }")),
|
||||
new Case("HandleWithoutAt", "declares handle = true, which only applies when @Surround.at selects a call", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch(handle = true) private void invalid$caught(Throwable e) {}")),
|
||||
new Case("SliceWithoutAt", "declares @Surround.slice, which only applies when @Surround.at selects a call", RUN, mixin("@Surround(method = \"run\", slice = @Slice(from = @At(\"HEAD\"))) private void invalid$enter(int a) {}")),
|
||||
new Case("EntryOperandMismatch", "parameter 0 ('notAnInt') is java.lang.String, which does not match the int operand of call java/lang/Math.abs(I)I", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(String notAnInt) {}")),
|
||||
new Case("OrphanWithoutSurround", "@Surround exit handler 'invalid$always' refers to the default id but no @Surround in this mixin declares it; this mixin has no @Surround at all", RUN, mixin(WHOLE_ENTRY), mixin("@Surround.Finally private void invalid$always() {}"))
|
||||
};
|
||||
|
||||
static final Case SUGAR = new Case("Sugar", "@Surround invalid$enter one of its parameters is annotated @Local, @Share or @Cancellable, which a @Surround handler cannot resolve", cls("public int run(int a) { int local = a + 1; return Math.abs(local); }"), mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int local, @Local(ordinal = 0) int a) {}"));
|
||||
|
||||
static final Case REQUIRE_ZERO_MISS = new Case("RequireZeroMiss", "", RUN, mixin("@Surround(method = \"run\", require = 0, at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;max(II)I\")) private void invalid$enter(int a, int b) {}" + FINALLY));
|
||||
|
||||
static String compileConfig(boolean mixinExtras) {
|
||||
final List<Case> cases = new ArrayList<>();
|
||||
if (mixinExtras) {
|
||||
cases.add(SUGAR);
|
||||
} else {
|
||||
cases.addAll(Arrays.asList(CASES));
|
||||
cases.add(REQUIRE_ZERO_MISS);
|
||||
}
|
||||
final List<Unit> units = new ArrayList<>();
|
||||
final List<String> mixins = new ArrayList<>();
|
||||
for (Case c : cases) {
|
||||
units.add(new Unit(PACKAGE + "." + c.name, "package " + PACKAGE + "; " + c.target.replace("$T", c.name)));
|
||||
for (int i = 0; i < c.mixins.length; i++) {
|
||||
final String name = c.name + "Mixin" + (i + 1);
|
||||
mixins.add('"' + name + '"');
|
||||
units.add(new Unit(PACKAGE + ".mixin." + name, "package " + PACKAGE + ".mixin; import " + PACKAGE + ".*;" + " import com.gtnewhorizons.angelica.experimental.surround.Surround;" + " import com.llamalad7.mixinextras.sugar.Cancellable;" + " import com.llamalad7.mixinextras.sugar.Local;" + " import org.spongepowered.asm.mixin.Mixin;" + " import org.spongepowered.asm.mixin.injection.At;" + " import org.spongepowered.asm.mixin.injection.Slice;" + c.mixins[i].replace("$T", c.name).replace("$M", name)));
|
||||
}
|
||||
}
|
||||
try {
|
||||
final Path dir = Paths.get("rejection-classes").toAbsolutePath();
|
||||
if (Files.exists(dir)) {
|
||||
try (Stream<Path> stale = Files.walk(dir)) {
|
||||
stale.sorted(Comparator.reverseOrder()).forEach(path -> path.toFile().delete());
|
||||
}
|
||||
}
|
||||
final Path sources = Files.createDirectories(dir.resolve("src"));
|
||||
final List<String> command = new ArrayList<>(Arrays.asList(System.getProperty("surround.javac"), "-classpath", System.getProperty("java.class.path"), "-d", dir.toString(), "-proc:none", "-g", "--release", "8", "-Xlint:-options"));
|
||||
for (Unit unit : units) {
|
||||
final Path file = sources.resolve(unit.className().replace('.', '/') + ".java");
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.write(file, unit.source().getBytes(StandardCharsets.UTF_8));
|
||||
command.add(file.toString());
|
||||
}
|
||||
final Process javac = new ProcessBuilder(command).redirectErrorStream(true).start();
|
||||
final String output = new String(javac.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
if (javac.waitFor() != 0) {
|
||||
throw new IllegalStateException(output);
|
||||
}
|
||||
Files.write(dir.resolve(CONFIG), ("{\"required\": true, \"minVersion\": \"0.8\", \"package\": \"" + PACKAGE + ".mixin\"," + " \"compatibilityLevel\": \"JAVA_8\", \"mixins\": " + mixins + "}").getBytes(StandardCharsets.UTF_8));
|
||||
Launch.classLoader.addURL(dir.toUri().toURL());
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
private static String cls(String members) {
|
||||
return "public class $T { " + members + " }";
|
||||
}
|
||||
|
||||
private static String mixin(String members) {
|
||||
return "@Mixin(value = $T.class, remap = false) public class $M { " + members + " }";
|
||||
}
|
||||
|
||||
private record Unit(String className, String source) {
|
||||
}
|
||||
|
||||
static final class Case {
|
||||
|
||||
final String name;
|
||||
final String expected;
|
||||
final String target;
|
||||
final String[] mixins;
|
||||
|
||||
Case(String name, String expected, String target, String... mixins) {
|
||||
this.name = name;
|
||||
this.expected = expected;
|
||||
this.target = target;
|
||||
this.mixins = mixins;
|
||||
}
|
||||
|
||||
void assertRejected() {
|
||||
final String messages = causeChain(transformExpectingFailure(PACKAGE + "." + this.name));
|
||||
assertTrue(messages.contains(this.expected), messages);
|
||||
}
|
||||
|
||||
private static Throwable transformExpectingFailure(String binaryName) {
|
||||
try {
|
||||
MixinTestBootstrap.transformer().transformClassBytes(binaryName, binaryName, MixinTestBootstrap.read(binaryName));
|
||||
} catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
throw new AssertionError(binaryName + " transformed without the expected failure");
|
||||
}
|
||||
|
||||
private static String causeChain(Throwable thrown) {
|
||||
final StringBuilder all = new StringBuilder();
|
||||
for (Throwable t = thrown; t != null; t = t.getCause()) {
|
||||
all.append(t).append('\n');
|
||||
}
|
||||
return all.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
class RejectionTest {
|
||||
|
||||
static Stream<Named<RejectionCases.Case>> rejections() {
|
||||
return Arrays.stream(RejectionCases.CASES).map(c -> Named.of(c.name, c));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("rejections")
|
||||
void anInvalidMixinIsRejectedAtLoad(RejectionCases.Case rejection) {
|
||||
rejection.assertRejected();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRequireZeroCallSurroundThatMatchesNoCallIsNotRejected() {
|
||||
assertDoesNotThrow(() -> MixinTestBootstrap.transform(RejectionCases.PACKAGE + "." + RejectionCases.REQUIRE_ZERO_MISS.name));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void mixinExtrasSugarOnACallSiteEntryHandlerIsRejected() {
|
||||
RejectionCases.SUGAR.assertRejected();
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.CALL;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.VOID;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WHOLE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.args;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.of;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.scenario;
|
||||
|
||||
class ScenarioTest {
|
||||
|
||||
static Stream<Arguments> scenarios() {
|
||||
return Stream.of(
|
||||
scenario(WHOLE, "a surround encloses the body and every other injector", "compute", of(7), of(14), "enter 7 1", "head 7", "body 7", "tail 14", "exit 1"),
|
||||
scenario(WHOLE, "the narrowest catch handler wins and finally runs when the body throws", "boom", of(args()), of(IllegalStateException.class), "enterBoom 1", "boomHead", "boom", "caught 1 boom", "exitBoom 1"),
|
||||
scenario(WHOLE, "an instance monitor covers the handlers and the body", "lockedInstance", of(7), of(14), "enterInstance true", "body true", "exitInstance true"),
|
||||
scenario(WHOLE, "a unique exit handler renamed on collision still binds", "run", of(args()), of(VOID), "enter", "run", "target exit", "handler exit"),
|
||||
scenario(WHOLE, "a lower priority mixin is the inner surround", "priority", of(5), of(60), "enterHigh", "enterLow", "body 5", "returnLow 5", "exitLow", "returnHigh 6", "exitHigh"),
|
||||
scenario(WHOLE, "static wide arguments", "wideBody", of(args(2.5d, 5L)), of(7.5d), "enter 1", "body", "exit 1"),
|
||||
scenario(WHOLE, "a throwing entry handler skips the body and the exit handler", "throwingEntry", of(5), of(IllegalArgumentException.class), "enterThrows 5"),
|
||||
scenario(WHOLE, "a throwing finally replaces the return value", "throwingFinally", of(args(9, 4)), of(UnsupportedOperationException.class), "enter 1", "gt", "exitThrows 1"),
|
||||
scenario(WHOLE, "pre-existing exception handlers still fire", "caught", of(4, -4), of(4, -1), "enter 1", "inner-finally", "exit 1", "enter 2", "caught neg", "inner-finally", "exit 2"),
|
||||
scenario(WHOLE, "three kinds bind by kind, not position", "carryThree", of(7), of(14), "enter 8 107 L7", "body 7", "exit 8 107 L7"),
|
||||
scenario(WHOLE, "frame naming an unbuilt object", "carryUninitialized", of(5), of(10), "enter pos", "body 5", "exit pos"),
|
||||
scenario(WHOLE, "entry handler's own try-catch", "carryGuarded", of(-3), of(-6), "recovered", "enter -1", "body -3", "exit -1"),
|
||||
scenario(WHOLE, "assigned in both branches at the end", "carrySplitAtEnd", of(-3), of(-6), "body -3", "exit 2"),
|
||||
scenario(WHOLE, "wide carry ahead of an int across a frame", "carryWideBeforeFrame", of(3), of(6), "positive", "body 3", "exit 3 4"),
|
||||
scenario(WHOLE, "array locals", "carryArrays", of(3), of(6), "body 3", "exit n3 3"),
|
||||
scenario(WHOLE, "widths and an uncarried local between carried ones survive the handler frame", "throwWide", of(3), of(IllegalStateException.class), "enter 4 1.5 3 s3", "body 3", "caught 4 1.5 3", "exit 4 1.5 3"),
|
||||
scenario(WHOLE, "a reference carry is cast to each exit handler's type and a throwing catch handler still runs finally", "throwObject", of(3), of(UnsupportedOperationException.class), "enter o3", "body 3", "caught 2 o3", "exit 2 o3"),
|
||||
scenario(WHOLE, "a reused slot carries the local's value", "reused", of(3), of(6), "tmp 8", "enter 10", "body 3", "exit 10"),
|
||||
scenario(CALL, "a static target takes static handlers around a static callee", "staticTarget", of(4), of(8), "enterStaticTarget 4", "twiceStatic 4", "exitStaticTarget 4 8"),
|
||||
scenario(CALL, "an interface callee is typed as the interface or a supertype", "interfaceCall", of(3), of(4), "enterInterface 3 true", "apply 3", "exitInterface true"),
|
||||
scenario(CALL, "a private callee's receiver is the target itself", "privateCall", of(3), of(4), "enterPrivate true 3", "priv 3", "exitPrivate true 3"),
|
||||
scenario(CALL, "every match is surrounded separately", "all", of(2), of(18), "enterAll 2 1", "twice 2", "exitAll 2 1", "enterAll 3 2", "twice 3", "exitAll 3 2", "enterAll 4 3", "twice 4", "exitAll 4 3"),
|
||||
scenario(CALL, "a slice restricts the matches", "sliced", of(2), of(10), "twice 2", "marker", "enterSliced 3", "twice 3", "exitSliced 3"),
|
||||
scenario(CALL, "a throwing entry handler runs no exit handler and skips the call", "throwingEntry", of(args()), of(IllegalArgumentException.class), "enterThrowingEntry 1"),
|
||||
scenario(CALL, "inject before, modify arg and inject after stay outside", "composed", of(5), of(210), "before 5", "modifyArg 5", "enterComposed 105", "twice 105", "exitComposed 105", "after 5"),
|
||||
scenario(WHOLE, "a void target skips without a skipped handler", "skippedVoid", of(-1, 1), of(VOID, VOID), "enterSkippedVoid -1", "exitSkippedVoid", "enterSkippedVoid 1", "skippedVoid 1", "exitSkippedVoid"),
|
||||
scenario(WHOLE, "an entry handler may call the target directly and then skip", "reentrant", of(1), of(24), "enterReentrant 11 false", "reentrant 11", "exitReentrant 11", "enterReentrant 1 true", "exitReentrant 1"),
|
||||
scenario(WHOLE, "return does not run after a skip and a reference skipped value is returned", "skipAndReturn", of("", "a", "x"), of(null, "a!?", IllegalStateException.class), "enterSkipAndReturn ", "skippedSkipAndReturn", "exitSkipAndReturn ", "enterSkipAndReturn a", "skipAndReturn a", "returnSkipAndReturn a!", "exitSkipAndReturn a", "enterSkipAndReturn x", "skipAndReturn x", "returnSkipAndReturn x!", "exitSkipAndReturn x"),
|
||||
scenario(WHOLE, "an entry handler may catch and rethrow around a direct call", "entryCatches", of(3, -1), of(6, IllegalStateException.class), "entryCatches 3", "exitEntryCatches false", "enterEntryCatches 3", "entryCatches 3", "exitEntryCatches true", "entryCatches -1", "exitEntryCatches false", "entryRethrow negative -1"),
|
||||
scenario(CALL, "a local is selected by name between operands and carries", "byName", of(3), of(49), "enterByName 23 1", "twice 13", "exitByName 13 23 1"),
|
||||
scenario(CALL, "wide locals bind", "wideLocals", of(3), of(31L), "enterWideLocals 30 1.5", "wide 30 1.5", "exitWideLocals 1.5 30"),
|
||||
scenario(CALL, "a handling catch replaces the result over the values below", "valueBelow", of(-3, 4), of(67, 84), "enterValueBelow -3 1", "explode -3", "handleValueBelow -3 1", "exitValueBelow -3 1", "enterValueBelow 4 2", "explode 4", "exitValueBelow 4 2"),
|
||||
scenario(CALL, "a skipped handler supplies the value of a skipped call", "valueSkip", of(-3, 3), of(-300, 6), "enterValueSkip -3 1", "skippedValueSkip -3 1", "exitValueSkip 1", "enterValueSkip 3 2", "twice 3", "exitValueSkip 2"),
|
||||
scenario(CALL, "a void return handler observes and does not run after a handled exception", "observed", of(2, -1), of(VOID, VOID), "enterObserved 2", "maybeBoom 2", "observe 2", "exitObserved 2", "enterObserved -1", "maybeBoom -1", "handleObserved -1", "exitObserved -1"),
|
||||
scenario(CALL, "a handled value bypasses the return handler", "returnAndHandle", of(4, -1), of(80, 7), "enterReturnAndHandle 4", "explode 4", "returnReturnAndHandle 8", "exitReturnAndHandle", "enterReturnAndHandle -1", "explode -1", "handleReturnAndHandle", "exitReturnAndHandle"),
|
||||
scenario(CALL, "the skip path is covered by catch and finally but not by return", "skipFinallyCatch", of(2, -3, 0), of(1004, 3, IllegalStateException.class), "enterSfc 2", "explode 2", "returnSfc 4", "exitSfc 2", "enterSfc -3", "skippedSfc -3", "exitSfc -3", "enterSfc 0", "skippedSfc 0", "caughtSfc zero", "exitSfc 0"),
|
||||
scenario(CALL, "an outer skip runs no inner handler and return handlers chain inner to outer", "nestedSkip", of(-2, 2), of(-1, 50), "enterNestedOuter -2", "skippedNestedOuter", "exitNestedOuter -2", "enterNestedOuter 2", "enterNestedInner 2", "twice 2", "returnNestedInner 4", "exitNestedInner 2", "returnNestedOuter 5", "exitNestedOuter 2"),
|
||||
scenario(CALL, "a skip skips the redirect handler inside", "redirectedSkip", of(-1, 3), of(0, 7), "enterRedirectedSkip -1", "exitRedirectedSkip -1", "enterRedirectedSkip 3", "redirectSkip 3", "twice 3", "exitRedirectedSkip 3"),
|
||||
scenario(CALL, "a final skip derived from a carry computed by a unique helper binds", "helperSkip", of(-4, 4), of(-1, 8), "part -4", "enterHelper -1", "skippedHelper", "exitHelper -1", "part 4", "enterHelper 4", "twice 4", "exitHelper 4"),
|
||||
scenario(CALL, "wide operands skip and return with an int skip flag", "wideSkip", of(args(-2L, 3.0d), args(5L, 2.5d)), of(-19L, 108L), "enterWideSkip -2 3.0", "skippedWideSkip -2 1.5", "exitWideSkip -2 3.0", "enterWideSkip 5 2.5", "wide 5 2.5", "returnWideSkip 7 1.25", "exitWideSkip 5 2.5"),
|
||||
scenario(CALL, "an entry-only call surround needs no exit handler even with a skip", "entryOnlySkip", of(1, -1), of(1, -1), "enterOnlySkip 1", "enterOnlySkip -1", "render -1"),
|
||||
scenario(CALL, "an entry-only whole-method surround", "entryOnlyWhole", of(args()), of(VOID), "enterOnlyWhole", "entryOnlyWhole"),
|
||||
scenario(CALL, "a skip restores the values below the call when the entry catches", "skipBelowRestored", of(-2, 3, 200), of(980, 1006, 1400), "enterSkipBelowRestored -2", "skippedSkipBelowRestored -2", "exitSkipBelowRestored -2", "enterSkipBelowRestored 3", "twice 3", "exitSkipBelowRestored 3", "entryCaughtSkipBelowRestored 200", "twice 200", "exitSkipBelowRestored 200"),
|
||||
scenario(CALL, "an inner skip completes normally for an outer return handler", "innerSkipOuterReturn", of(-2, 3), of(-199, 7), "enterOuterReturn -2", "enterInnerSkip -2", "skippedInnerSkip -2", "exitInnerSkip -2", "returnOuterReturn -200", "exitOuterReturn -2", "enterOuterReturn 3", "enterInnerSkip 3", "twice 3", "exitInnerSkip 3", "returnOuterReturn 6", "exitOuterReturn 3"),
|
||||
scenario(CALL, "a call inside a constructor call's arguments can be skipped", "skipInsideNew", of(-2, 3), of(-20, 6), "enterSkipInsideNew -2", "skippedSkipInsideNew -2", "exitSkipInsideNew -2", "enterSkipInsideNew 3", "twice 3", "exitSkipInsideNew 3"),
|
||||
scenario(CALL, "a call surround skips inside a body moved by a whole-method surround", "movedBody", of(-2, 3), of(990, 1008), "enterMovedWhole -2", "enterMovedCall -1 -2", "skippedMovedCall -1 -2", "exitMovedCall -1 -2", "exitMovedWhole -2", "enterMovedWhole 3", "enterMovedCall 4 3", "twice 4", "exitMovedCall 4 3", "exitMovedWhole 3"),
|
||||
scenario(WHOLE, "a branching entry handler around the whole method", "branching", of(-3), of(-6), "enterBranching negative", "body -3", "exitBranching negative")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("scenarios")
|
||||
void scenarios(String method, String target, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
MixinTestBootstrap.assertScenario(target, method, inputs, outputs, events);
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LineNumberNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WHOLE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.call;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class WholeMethodTest {
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneEntryHandlerCanSurroundSeveralTargets() throws Exception {
|
||||
call(WHOLE, "alpha");
|
||||
call(WHOLE, "beta");
|
||||
Trace.assertEvents("enterBoth 1", "alpha", "exitBoth 1", "enterBoth 2", "beta", "exitBoth 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theWrapperKeepsTheEntryHandlersLinesAndTheBodyKeepsItsOwn() {
|
||||
final ClassNode merged = MixinTestBootstrap.transformedNode(WHOLE);
|
||||
final List<Integer> entryLines = lines(bySuffix(merged, "$surround$enterBranching"));
|
||||
assertFalse(entryLines.isEmpty(), "fixture compiled without line numbers");
|
||||
assertEquals(entryLines, lines(MixinTestBootstrap.method(merged, "branching")));
|
||||
assertEquals(lines(MixinTestBootstrap.method(MixinTestBootstrap.originalNode(WHOLE), "branching")), lines(MixinTestBootstrap.method(merged, "branching$surround")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aReusedSlotNamesTheCarriedLocal() {
|
||||
final MethodNode wrapper = MixinTestBootstrap.method(MixinTestBootstrap.transformedNode(WHOLE), "reused");
|
||||
LabelNode last = null;
|
||||
for (AbstractInsnNode insn = wrapper.instructions.getLast(); last == null; insn = insn.getPrevious()) {
|
||||
last = insn instanceof LabelNode label ? label : null;
|
||||
}
|
||||
final List<String> toEnd = new ArrayList<>();
|
||||
for (LocalVariableNode local : wrapper.localVariables) {
|
||||
if (local.end == last) {
|
||||
toEnd.add(local.name);
|
||||
}
|
||||
}
|
||||
assertTrue(toEnd.contains("prev") && !toEnd.contains("tmp"), "locals live to the end: " + toEnd);
|
||||
}
|
||||
|
||||
@Test
|
||||
void theWrapperMatchesTheGoldens() {
|
||||
MixinTestBootstrap.assertGolden(WHOLE, "skipAndReturn", "whole-skip-return");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIfSystemProperty(named = "surround.test.obf", matches = "true")
|
||||
void theLaunchClassLoaderTransformersSeeTheMixinClass() throws Exception {
|
||||
MixinTestBootstrap.transform(WHOLE);
|
||||
final Set<?> seen = (Set<?>) Launch.classLoader.loadClass(LocalSortingTransformer.class.getName()).getField("SEEN").get(null);
|
||||
assertTrue(seen.contains("com.gtnewhorizons.angelica.experimental.surround.integration.mixin.MixinWholeTarget"), seen.toString());
|
||||
}
|
||||
|
||||
private static MethodNode bySuffix(ClassNode node, String suffix) {
|
||||
for (MethodNode method : node.methods) {
|
||||
if (method.name.endsWith(suffix)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
final List<String> names = new ArrayList<>();
|
||||
for (MethodNode method : node.methods) {
|
||||
names.add(method.name);
|
||||
}
|
||||
throw new AssertionError("no method ending " + suffix + " in " + names);
|
||||
}
|
||||
|
||||
private static List<Integer> lines(MethodNode method) {
|
||||
final List<Integer> lines = new ArrayList<>();
|
||||
for (AbstractInsnNode insn : method.instructions.toArray()) {
|
||||
if (insn instanceof LineNumberNode ln) {
|
||||
lines.add(ln.line);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
public class CallTarget {
|
||||
|
||||
public static final String CALLEE = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;";
|
||||
public static final String TWICE = CALLEE + "twice(I)I";
|
||||
|
||||
private final Callee callee = new Callee();
|
||||
private final IntUnaryOperator op = new Callee();
|
||||
private final Render renderer = new Render();
|
||||
|
||||
public static int staticTarget(int a) {
|
||||
return Callee.twiceStatic(a);
|
||||
}
|
||||
|
||||
public int interfaceCall(int a) {
|
||||
return this.op.applyAsInt(a);
|
||||
}
|
||||
|
||||
public int privateCall(int a) {
|
||||
return this.priv(a);
|
||||
}
|
||||
|
||||
private int priv(int a) {
|
||||
Trace.add("priv " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public int all(int a) {
|
||||
return this.callee.twice(a) + this.callee.twice(a + 1) + this.callee.twice(a + 2);
|
||||
}
|
||||
|
||||
public int sliced(int a) {
|
||||
final int first = this.callee.twice(a);
|
||||
Callee.marker();
|
||||
return first + this.callee.twice(a + 1);
|
||||
}
|
||||
|
||||
public void throwingEntry() {
|
||||
this.callee.twice(1);
|
||||
}
|
||||
|
||||
public int composed(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public boolean render(Entity entity, double x, double y, double z, float yaw, float partial, boolean flag) {
|
||||
Render render = null;
|
||||
try {
|
||||
render = this.renderer;
|
||||
if (render != null && entity != null) {
|
||||
try {
|
||||
render.doRender(entity, x, y, z, yaw, partial);
|
||||
} catch (Throwable throwable1) {
|
||||
throw new ReportedException("Rendering entity in world", throwable1);
|
||||
}
|
||||
try {
|
||||
render.doRenderShadowAndFire(entity, x, y, z, yaw, partial);
|
||||
} catch (Throwable throwable2) {
|
||||
throw new ReportedException("Post-rendering entity in world", throwable2);
|
||||
}
|
||||
if (flag) {
|
||||
Trace.add("boundingBox " + entity.name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Throwable throwable) {
|
||||
throw new ReportedException("Rendering entity (outer)", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public int byName(int a) {
|
||||
final int first = a + 10;
|
||||
final int second = a + 20;
|
||||
return this.callee.twice(first) + second;
|
||||
}
|
||||
|
||||
public long wideLocals(int a) {
|
||||
final long big = a * 10L;
|
||||
final double half = a / 2.0;
|
||||
return this.callee.wide(big, half);
|
||||
}
|
||||
|
||||
public int valueBelow(int a) {
|
||||
return a + this.callee.explode(a) * 10;
|
||||
}
|
||||
|
||||
public int valueSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public void observed(int a) {
|
||||
this.callee.maybeBoom(a);
|
||||
}
|
||||
|
||||
public int returnAndHandle(int a) {
|
||||
return this.callee.explode(a);
|
||||
}
|
||||
|
||||
public int skipFinallyCatch(int a) {
|
||||
return this.callee.explode(a);
|
||||
}
|
||||
|
||||
public int nestedSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int redirectedSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int helperSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public long wideSkip(long a, double b) {
|
||||
return this.callee.wide(a, b) + 1L;
|
||||
}
|
||||
|
||||
public int entryOnlySkip(int a) {
|
||||
this.callee.render(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public void entryOnlyWhole() {
|
||||
Trace.add("entryOnlyWhole");
|
||||
}
|
||||
|
||||
public int skipBelowRestored(int a) {
|
||||
return 1000 + this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int innerSkipOuterReturn(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int skipInsideNew(int a) {
|
||||
return new AtomicInteger(this.callee.twice(a)).get();
|
||||
}
|
||||
|
||||
public int movedBody(int a) {
|
||||
final int b = a + 1;
|
||||
return 1000 + this.callee.twice(b);
|
||||
}
|
||||
|
||||
public static class Callee implements IntUnaryOperator {
|
||||
|
||||
public int twice(int a) {
|
||||
Trace.add("twice " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static int twiceStatic(int a) {
|
||||
Trace.add("twiceStatic " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static void marker() {
|
||||
Trace.add("marker");
|
||||
}
|
||||
|
||||
public long wide(long a, double b) {
|
||||
Trace.add("wide " + a + " " + b);
|
||||
return a + (long) b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int applyAsInt(int a) {
|
||||
Trace.add("apply " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public int explode(int a) {
|
||||
Trace.add("explode " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("negative " + a);
|
||||
}
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void maybeBoom(int a) {
|
||||
Trace.add("maybeBoom " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("boom " + a);
|
||||
}
|
||||
}
|
||||
|
||||
public void render(int a) {
|
||||
Trace.add("render " + a);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Entity {
|
||||
|
||||
public final String name;
|
||||
public boolean explode;
|
||||
|
||||
public Entity(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Render {
|
||||
|
||||
public void doRender(Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("doRender " + entity.name + " " + x + " " + partial);
|
||||
if (entity.explode) {
|
||||
throw new RuntimeException("gpu");
|
||||
}
|
||||
}
|
||||
|
||||
public void doRenderShadowAndFire(Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("shadow " + entity.name);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReportedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ReportedException(String description, Throwable cause) {
|
||||
super(description, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
public class WholeTarget {
|
||||
|
||||
public static boolean guard;
|
||||
|
||||
public int compute(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void boom() {
|
||||
Trace.add("boom");
|
||||
throw new IllegalStateException("boom");
|
||||
}
|
||||
|
||||
public void alpha() {
|
||||
Trace.add("alpha");
|
||||
}
|
||||
|
||||
public void beta() {
|
||||
Trace.add("beta");
|
||||
}
|
||||
|
||||
public synchronized int lockedInstance(int a) {
|
||||
Trace.add("body " + Thread.holdsLock(this));
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Trace.add("run");
|
||||
exit();
|
||||
}
|
||||
|
||||
private void exit() {
|
||||
Trace.add("target exit");
|
||||
}
|
||||
|
||||
public int priority(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public int branching(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void skippedVoid(int a) {
|
||||
Trace.add("skippedVoid " + a);
|
||||
}
|
||||
|
||||
public int reentrant(int a) {
|
||||
Trace.add("reentrant " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public String skipAndReturn(String a) {
|
||||
Trace.add("skipAndReturn " + a);
|
||||
return a + "!";
|
||||
}
|
||||
|
||||
public static int entryCatches(int a) {
|
||||
Trace.add("entryCatches " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("negative " + a);
|
||||
}
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public int throwingFinally(int a, int b) {
|
||||
return pick(a, b);
|
||||
}
|
||||
|
||||
private static int pick(int a, int b) {
|
||||
if (a > b) {
|
||||
Trace.add("gt");
|
||||
return a;
|
||||
}
|
||||
if (a < b) {
|
||||
Trace.add("lt");
|
||||
return b;
|
||||
}
|
||||
Trace.add("eq");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static double wideBody(double a, long b) {
|
||||
Trace.add("body");
|
||||
return a + b;
|
||||
}
|
||||
|
||||
public void throwWide(int a) {
|
||||
thrown(a);
|
||||
}
|
||||
|
||||
public void throwObject(int a) {
|
||||
thrown(a);
|
||||
}
|
||||
|
||||
private static void thrown(int a) {
|
||||
Trace.add("body " + a);
|
||||
throw new IllegalStateException("boom " + a);
|
||||
}
|
||||
|
||||
public void throwingEntry(int a) {
|
||||
Trace.add("body " + a);
|
||||
}
|
||||
|
||||
public int caught(int a) {
|
||||
try {
|
||||
if (a < 0) {
|
||||
throw new IllegalArgumentException("neg");
|
||||
}
|
||||
return a;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Trace.add("caught " + e.getMessage());
|
||||
return -1;
|
||||
} finally {
|
||||
Trace.add("inner-finally");
|
||||
}
|
||||
}
|
||||
|
||||
public int carryThree(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryUninitialized(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryGuarded(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carrySplitAtEnd(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryWideBeforeFrame(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryArrays(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int reused(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
private static int carried(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static int risky(int a) {
|
||||
if (a < 0) {
|
||||
throw new IllegalArgumentException("neg");
|
||||
}
|
||||
return a * 3;
|
||||
}
|
||||
|
||||
public static Object asObject(Object value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
public class WrapTarget {
|
||||
|
||||
private final Callee callee = new Callee();
|
||||
|
||||
public int wrapped(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public int originalOperands(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int shifted(int a) {
|
||||
final int local = a + 1;
|
||||
final String label = "L" + local;
|
||||
return this.callee.twice(local) + label.length();
|
||||
}
|
||||
}
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Entity;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Render;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.CALLEE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.TWICE;
|
||||
|
||||
@Mixin(value = CallTarget.class, remap = false)
|
||||
public class MixinCallTarget {
|
||||
|
||||
private static final String WIDE = CALLEE + "wide(JD)J";
|
||||
private static final String EXPLODE = CALLEE + "explode(I)I";
|
||||
private static final String MAYBE_BOOM = CALLEE + "maybeBoom(I)V";
|
||||
private static final String DO_RENDER = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Render;" + "doRender(Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Entity;DDDFF)V";
|
||||
private static final long QUEUED = -1L;
|
||||
|
||||
@Surround(method = "staticTarget", at = @At(value = "INVOKE", target = CALLEE + "twiceStatic(I)I"), id = "staticTarget")
|
||||
private static void surround$enterStaticTarget(int a) {
|
||||
@Surround.Carry int doubled = a * 2;
|
||||
Trace.add("enterStaticTarget " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("staticTarget")
|
||||
private static void surround$exitStaticTarget(int a, @Surround.Carry int doubled) {
|
||||
Trace.add("exitStaticTarget " + a + " " + doubled);
|
||||
}
|
||||
|
||||
@Surround(method = "interfaceCall", at = @At(value = "INVOKE", target = "Ljava/util/function/IntUnaryOperator;applyAsInt(I)I"), id = "interface")
|
||||
private void surround$enterInterface(IntUnaryOperator op, int a) {
|
||||
Trace.add("enterInterface " + a + " " + (op instanceof Callee));
|
||||
}
|
||||
|
||||
@Surround.Finally("interface")
|
||||
private void surround$exitInterface(Object op) {
|
||||
Trace.add("exitInterface " + (op instanceof Callee));
|
||||
}
|
||||
|
||||
@Surround(method = "privateCall", at = @At(value = "INVOKE", target = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget;priv(I)I"), id = "private")
|
||||
private void surround$enterPrivate(CallTarget self, int a) {
|
||||
Trace.add("enterPrivate " + (self == (Object) this) + " " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("private")
|
||||
private void surround$exitPrivate(CallTarget self, int a) {
|
||||
Trace.add("exitPrivate " + (self == (Object) this) + " " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "all", at = @At(value = "INVOKE", target = TWICE), id = "all", require = 3)
|
||||
private void surround$enterAll(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterAll " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("all")
|
||||
private void surround$exitAll(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("exitAll " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "sliced", at = @At(value = "INVOKE", target = TWICE), slice = @Slice(from = @At(value = "INVOKE", target = CALLEE + "marker()V")), id = "sliced", require = 1, allow = 1)
|
||||
private void surround$enterSliced(Callee callee, int a) {
|
||||
Trace.add("enterSliced " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("sliced")
|
||||
private void surround$exitSliced(Callee callee, int a) {
|
||||
Trace.add("exitSliced " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "throwingEntry", at = @At(value = "INVOKE", target = TWICE), id = "throwingEntry")
|
||||
private void surround$enterThrowingEntry(Callee callee, int a) {
|
||||
Trace.add("enterThrowingEntry " + a);
|
||||
if (a > 0) {
|
||||
throw new IllegalArgumentException("entry");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Catch("throwingEntry")
|
||||
private void surround$caughtThrowingEntry(Throwable error) {
|
||||
Trace.add("caughtThrowingEntry");
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingEntry")
|
||||
private void surround$exitThrowingEntry() {
|
||||
Trace.add("exitThrowingEntry");
|
||||
}
|
||||
|
||||
@Inject(method = "composed", at = @At(value = "INVOKE", target = TWICE))
|
||||
private void surround$before(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("before " + a);
|
||||
}
|
||||
|
||||
@Inject(method = "composed", at = @At(value = "INVOKE", target = TWICE, shift = At.Shift.AFTER))
|
||||
private void surround$after(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("after " + a);
|
||||
}
|
||||
|
||||
@ModifyArg(method = "composed", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int surround$plusHundred(int a) {
|
||||
Trace.add("modifyArg " + a);
|
||||
return a + 100;
|
||||
}
|
||||
|
||||
@Surround(method = "composed", at = @At(value = "INVOKE", target = TWICE), id = "composed")
|
||||
private void surround$enterComposed(Callee callee, int a) {
|
||||
Trace.add("enterComposed " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("composed")
|
||||
private void surround$exitComposed(Callee callee, int a) {
|
||||
Trace.add("exitComposed " + a);
|
||||
}
|
||||
|
||||
@Redirect(method = "render", at = @At(value = "INVOKE", target = DO_RENDER))
|
||||
private void surround$redirectDoRender(Render render, Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("redirect " + entity.name);
|
||||
render.doRender(entity, x, y, z, yaw, partial);
|
||||
}
|
||||
|
||||
@Surround(method = "render", at = @At(value = "INVOKE", target = DO_RENDER), id = "render", require = 1)
|
||||
private void surround$enterDoRender(Render render, Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterDoRender " + entity.name + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch("render")
|
||||
private void surround$caughtDoRender(Throwable error, Render render, Entity entity, @Surround.Carry long token) {
|
||||
Trace.add("caughtDoRender " + entity.name + " " + error.getMessage() + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("render")
|
||||
private void surround$exitDoRender(Render render, Entity entity, @Surround.Carry long token) {
|
||||
Trace.add("exitDoRender " + entity.name + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "byName", at = @At(value = "INVOKE", target = TWICE), id = "byName")
|
||||
private void surround$enterByName(@Surround.Local(name = "second") int second) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterByName " + second + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("byName")
|
||||
private void surround$exitByName(Callee callee, int first, @Surround.Local(name = "second") int second, @Surround.Carry long token) {
|
||||
Trace.add("exitByName " + first + " " + second + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "wideLocals", at = @At(value = "INVOKE", target = WIDE), id = "wideLocals")
|
||||
private void surround$enterWideLocals(Callee callee, long a, double b, @Surround.Local long big, @Surround.Local double half) {
|
||||
Trace.add("enterWideLocals " + big + " " + half);
|
||||
}
|
||||
|
||||
@Surround.Finally("wideLocals")
|
||||
private void surround$exitWideLocals(@Surround.Local(index = 4) double half, @Surround.Local(index = 2) long big) {
|
||||
Trace.add("exitWideLocals " + half + " " + big);
|
||||
}
|
||||
|
||||
@Surround(method = "valueBelow", at = @At(value = "INVOKE", target = EXPLODE), id = "valueBelow")
|
||||
private void surround$enterValueBelow(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterValueBelow " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "valueBelow", handle = true)
|
||||
private int surround$handleValueBelow(IllegalStateException error, Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("handleValueBelow " + a + " " + token);
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Surround.Finally("valueBelow")
|
||||
private void surround$exitValueBelow(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("exitValueBelow " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "valueSkip", at = @At(value = "INVOKE", target = TWICE), id = "valueSkip")
|
||||
private void surround$enterValueSkip(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterValueSkip " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Skipped("valueSkip")
|
||||
private int surround$skippedValueSkip(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("skippedValueSkip " + a + " " + token);
|
||||
return a * 100;
|
||||
}
|
||||
|
||||
@Surround.Finally("valueSkip")
|
||||
private void surround$exitValueSkip(@Surround.Carry long token) {
|
||||
Trace.add("exitValueSkip " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "observed", at = @At(value = "INVOKE", target = MAYBE_BOOM), id = "observed")
|
||||
private void surround$enterObserved(Callee callee, int a) {
|
||||
Trace.add("enterObserved " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("observed")
|
||||
private void surround$observe(Callee callee, int a) {
|
||||
Trace.add("observe " + a);
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "observed", handle = true)
|
||||
private void surround$handleObserved(IllegalStateException error, Callee callee, int a) {
|
||||
Trace.add("handleObserved " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("observed")
|
||||
private void surround$exitObserved(Callee callee, int a) {
|
||||
Trace.add("exitObserved " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "returnAndHandle", at = @At(value = "INVOKE", target = EXPLODE), id = "returnAndHandle")
|
||||
private void surround$enterReturnAndHandle(Callee callee, int a) {
|
||||
Trace.add("enterReturnAndHandle " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("returnAndHandle")
|
||||
private int surround$returnReturnAndHandle(int result) {
|
||||
Trace.add("returnReturnAndHandle " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "returnAndHandle", handle = true)
|
||||
private int surround$handleReturnAndHandle(IllegalStateException error) {
|
||||
Trace.add("handleReturnAndHandle");
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Surround.Finally("returnAndHandle")
|
||||
private void surround$exitReturnAndHandle() {
|
||||
Trace.add("exitReturnAndHandle");
|
||||
}
|
||||
|
||||
@Surround(method = "skipFinallyCatch", at = @At(value = "INVOKE", target = EXPLODE), id = "sfc")
|
||||
private void surround$enterSfc(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a <= 0;
|
||||
Trace.add("enterSfc " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("sfc")
|
||||
private int surround$skippedSfc(Callee callee, int a) {
|
||||
Trace.add("skippedSfc " + a);
|
||||
if (a == 0) {
|
||||
throw new IllegalStateException("zero");
|
||||
}
|
||||
return -a;
|
||||
}
|
||||
|
||||
@Surround.Return("sfc")
|
||||
private int surround$returnSfc(int result, Callee callee, int a) {
|
||||
Trace.add("returnSfc " + result);
|
||||
return result + 1000;
|
||||
}
|
||||
|
||||
@Surround.Catch("sfc")
|
||||
private void surround$caughtSfc(IllegalStateException error, Callee callee, int a) {
|
||||
Trace.add("caughtSfc " + error.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("sfc")
|
||||
private void surround$exitSfc(Callee callee, int a) {
|
||||
Trace.add("exitSfc " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "nestedSkip", at = @At(value = "INVOKE", target = TWICE), id = "nestedInner")
|
||||
private void surround$enterNestedInner(Callee callee, int a) {
|
||||
Trace.add("enterNestedInner " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("nestedInner")
|
||||
private int surround$returnNestedInner(int result) {
|
||||
Trace.add("returnNestedInner " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("nestedInner")
|
||||
private void surround$exitNestedInner(Callee callee, int a) {
|
||||
Trace.add("exitNestedInner " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "nestedSkip", at = @At(value = "INVOKE", target = TWICE), id = "nestedOuter")
|
||||
private void surround$enterNestedOuter(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterNestedOuter " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("nestedOuter")
|
||||
private int surround$skippedNestedOuter() {
|
||||
Trace.add("skippedNestedOuter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Surround.Return("nestedOuter")
|
||||
private int surround$returnNestedOuter(int result) {
|
||||
Trace.add("returnNestedOuter " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("nestedOuter")
|
||||
private void surround$exitNestedOuter(Callee callee, int a) {
|
||||
Trace.add("exitNestedOuter " + a);
|
||||
}
|
||||
|
||||
@Redirect(method = "redirectedSkip", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int surround$redirectSkip(Callee callee, int a) {
|
||||
Trace.add("redirectSkip " + a);
|
||||
return callee.twice(a) + 1;
|
||||
}
|
||||
|
||||
@Surround(method = "redirectedSkip", at = @At(value = "INVOKE", target = TWICE), id = "redirectedSkip")
|
||||
private void surround$enterRedirectedSkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterRedirectedSkip " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("redirectedSkip")
|
||||
private int surround$skippedRedirectedSkip(Callee callee, int a) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Surround.Finally("redirectedSkip")
|
||||
private void surround$exitRedirectedSkip(Callee callee, int a) {
|
||||
Trace.add("exitRedirectedSkip " + a);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static long surround$part(int a) {
|
||||
Trace.add("part " + a);
|
||||
return a < 0 ? QUEUED : a;
|
||||
}
|
||||
|
||||
@Surround(method = "helperSkip", at = @At(value = "INVOKE", target = TWICE), id = "helper")
|
||||
private void surround$enterHelper(Callee callee, int a) {
|
||||
@Surround.Carry final long part = surround$part(a);
|
||||
@Surround.Skip final boolean queued = part == QUEUED;
|
||||
Trace.add("enterHelper " + part);
|
||||
}
|
||||
|
||||
@Surround.Skipped("helper")
|
||||
private int surround$skippedHelper(Callee callee, int a, @Surround.Carry long part) {
|
||||
Trace.add("skippedHelper");
|
||||
return (int) part;
|
||||
}
|
||||
|
||||
@Surround.Finally("helper")
|
||||
private void surround$exitHelper(@Surround.Carry long part) {
|
||||
Trace.add("exitHelper " + part);
|
||||
}
|
||||
|
||||
@Surround(method = "wideSkip", at = @At(value = "INVOKE", target = WIDE), id = "wideSkip")
|
||||
private void surround$enterWideSkip(Callee callee, long a, double b) {
|
||||
@Surround.Carry double halved = b / 2;
|
||||
@Surround.Skip int skip = a < 0 ? 1 : 0;
|
||||
Trace.add("enterWideSkip " + a + " " + b);
|
||||
}
|
||||
|
||||
@Surround.Skipped("wideSkip")
|
||||
private long surround$skippedWideSkip(Callee callee, long a, @Surround.Carry double halved) {
|
||||
Trace.add("skippedWideSkip " + a + " " + halved);
|
||||
return a * 10L;
|
||||
}
|
||||
|
||||
@Surround.Return("wideSkip")
|
||||
private long surround$returnWideSkip(long result, Callee callee, long a, double b, @Surround.Carry double halved) {
|
||||
Trace.add("returnWideSkip " + result + " " + halved);
|
||||
return result + 100L;
|
||||
}
|
||||
|
||||
@Surround.Finally("wideSkip")
|
||||
private void surround$exitWideSkip(Callee callee, long a, double b) {
|
||||
Trace.add("exitWideSkip " + a + " " + b);
|
||||
}
|
||||
|
||||
@Surround(method = "entryOnlySkip", at = @At(value = "INVOKE", target = CALLEE + "render(I)V"), id = "entryOnlySkip")
|
||||
private void surround$enterOnlySkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a > 0;
|
||||
Trace.add("enterOnlySkip " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "entryOnlyWhole", id = "entryOnlyWhole")
|
||||
private void surround$enterOnlyWhole() {
|
||||
Trace.add("enterOnlyWhole");
|
||||
}
|
||||
|
||||
@Surround(method = "skipBelowRestored", at = @At(value = "INVOKE", target = TWICE), id = "skipBelowRestored")
|
||||
private void surround$enterSkipBelowRestored(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
try {
|
||||
if (a > 100) {
|
||||
throw new IllegalArgumentException("big");
|
||||
}
|
||||
Trace.add("enterSkipBelowRestored " + a);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Trace.add("entryCaughtSkipBelowRestored " + a);
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipBelowRestored")
|
||||
private int surround$skippedSkipBelowRestored(Callee callee, int a) {
|
||||
Trace.add("skippedSkipBelowRestored " + a);
|
||||
return a * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("skipBelowRestored")
|
||||
private void surround$exitSkipBelowRestored(Callee callee, int a) {
|
||||
Trace.add("exitSkipBelowRestored " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "innerSkipOuterReturn", at = @At(value = "INVOKE", target = TWICE), id = "innerSkip")
|
||||
private void surround$enterInnerSkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterInnerSkip " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("innerSkip")
|
||||
private int surround$skippedInnerSkip(Callee callee, int a) {
|
||||
Trace.add("skippedInnerSkip " + a);
|
||||
return a * 100;
|
||||
}
|
||||
|
||||
@Surround.Finally("innerSkip")
|
||||
private void surround$exitInnerSkip(Callee callee, int a) {
|
||||
Trace.add("exitInnerSkip " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "innerSkipOuterReturn", at = @At(value = "INVOKE", target = TWICE), id = "outerReturn")
|
||||
private void surround$enterOuterReturn(Callee callee, int a) {
|
||||
Trace.add("enterOuterReturn " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("outerReturn")
|
||||
private int surround$returnOuterReturn(int result) {
|
||||
Trace.add("returnOuterReturn " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("outerReturn")
|
||||
private void surround$exitOuterReturn(Callee callee, int a) {
|
||||
Trace.add("exitOuterReturn " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "skipInsideNew", at = @At(value = "INVOKE", target = TWICE), id = "skipInsideNew")
|
||||
private void surround$enterSkipInsideNew(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterSkipInsideNew " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipInsideNew")
|
||||
private int surround$skippedSkipInsideNew(Callee callee, int a) {
|
||||
Trace.add("skippedSkipInsideNew " + a);
|
||||
return a * 10;
|
||||
}
|
||||
|
||||
@Surround.Catch("skipInsideNew")
|
||||
private void surround$caughtSkipInsideNew(Throwable error) {
|
||||
Trace.add("caughtSkipInsideNew");
|
||||
}
|
||||
|
||||
@Surround.Finally("skipInsideNew")
|
||||
private void surround$exitSkipInsideNew(Callee callee, int a) {
|
||||
Trace.add("exitSkipInsideNew " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "movedBody", id = "movedWhole")
|
||||
private void surround$enterMovedWhole(int a) {
|
||||
Trace.add("enterMovedWhole " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("movedWhole")
|
||||
private void surround$exitMovedWhole(int a) {
|
||||
Trace.add("exitMovedWhole " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "movedBody", at = @At(value = "INVOKE", target = TWICE), id = "movedCall")
|
||||
private void surround$enterMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterMovedCall " + b + " " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("movedCall")
|
||||
private int surround$skippedMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
Trace.add("skippedMovedCall " + b + " " + a);
|
||||
return b * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("movedCall")
|
||||
private void surround$exitMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
Trace.add("exitMovedCall " + b + " " + a);
|
||||
}
|
||||
}
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(value = WholeTarget.class, remap = false)
|
||||
public class MixinWholeTarget {
|
||||
|
||||
@Surround(method = "compute(I)I", id = "compute")
|
||||
private void surround$enter(int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("compute")
|
||||
private void surround$exit(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Inject(method = "compute(I)I", at = @At("HEAD"))
|
||||
private void surround$head(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("head " + a);
|
||||
}
|
||||
|
||||
@Inject(method = "compute(I)I", at = @At("RETURN"))
|
||||
private void surround$tail(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("tail " + cir.getReturnValue());
|
||||
}
|
||||
|
||||
@Surround(method = "boom()V", id = "boom")
|
||||
private void surround$enterBoom() {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterBoom " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch("boom")
|
||||
private void surround$caughtBoomBroad(Throwable e, @Surround.Carry long token) {
|
||||
Trace.add("caughtBroad " + token + " " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Catch("boom")
|
||||
private void surround$caughtBoom(IllegalStateException e, @Surround.Carry long token) {
|
||||
Trace.add("caught " + token + " " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("boom")
|
||||
private void surround$exitBoom(@Surround.Carry long token) {
|
||||
Trace.add("exitBoom " + token);
|
||||
}
|
||||
|
||||
@Inject(method = "boom()V", at = @At("HEAD"))
|
||||
private void surround$boomHead(CallbackInfo ci) {
|
||||
Trace.add("boomHead");
|
||||
}
|
||||
|
||||
@Surround(method = {"alpha()V", "beta()V"}, id = "both")
|
||||
private void surround$enterBoth() {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterBoth " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("both")
|
||||
private void surround$exitBoth(@Surround.Carry long token) {
|
||||
Trace.add("exitBoth " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "lockedInstance(I)I", id = "instance")
|
||||
private void surround$enterInstance(int a) {
|
||||
Trace.add("enterInstance " + Thread.holdsLock(this));
|
||||
}
|
||||
|
||||
@Surround.Finally("instance")
|
||||
private void surround$exitInstance() {
|
||||
Trace.add("exitInstance " + Thread.holdsLock(this));
|
||||
}
|
||||
|
||||
@Surround(method = "run()V", id = "run")
|
||||
private void surround$enterRun() {
|
||||
Trace.add("enter");
|
||||
}
|
||||
|
||||
@Unique
|
||||
@Surround.Finally("run")
|
||||
private void exit() {
|
||||
Trace.add("handler exit");
|
||||
}
|
||||
|
||||
@Surround(method = "priority(I)I", id = "priority")
|
||||
private void surround$enterLow(int a) {
|
||||
Trace.add("enterLow");
|
||||
}
|
||||
|
||||
@Surround.Return("priority")
|
||||
private int surround$returnLow(int result) {
|
||||
Trace.add("returnLow " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("priority")
|
||||
private void surround$exitLow() {
|
||||
Trace.add("exitLow");
|
||||
}
|
||||
|
||||
@Surround(method = "branching(I)I", id = "branching")
|
||||
private void surround$enterBranching(int a) {
|
||||
@Surround.Carry String sign = a < 0 ? "negative" : "positive";
|
||||
Trace.add("enterBranching " + sign);
|
||||
}
|
||||
|
||||
@Surround.Finally("branching")
|
||||
private void surround$exitBranching(int a, @Surround.Carry String sign) {
|
||||
Trace.add("exitBranching " + sign);
|
||||
}
|
||||
|
||||
@Surround(method = "skippedVoid(I)V", id = "skippedVoid")
|
||||
private void surround$enterSkippedVoid(int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterSkippedVoid " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("skippedVoid")
|
||||
private void surround$exitSkippedVoid() {
|
||||
Trace.add("exitSkippedVoid");
|
||||
}
|
||||
|
||||
@Surround(method = "reentrant(I)I", id = "reentrant")
|
||||
private void surround$enterReentrant(int a) {
|
||||
@Surround.Carry int direct = 0;
|
||||
@Surround.Skip boolean skip = false;
|
||||
if (!WholeTarget.guard) {
|
||||
WholeTarget.guard = true;
|
||||
direct = ((WholeTarget) (Object) this).reentrant(a + 10);
|
||||
WholeTarget.guard = false;
|
||||
skip = true;
|
||||
}
|
||||
Trace.add("enterReentrant " + a + " " + skip);
|
||||
}
|
||||
|
||||
@Surround.Skipped("reentrant")
|
||||
private int surround$skippedReentrant(int a, @Surround.Carry int direct) {
|
||||
return direct * 2;
|
||||
}
|
||||
|
||||
@Surround.Finally("reentrant")
|
||||
private void surround$exitReentrant(int a) {
|
||||
Trace.add("exitReentrant " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "skipAndReturn(Ljava/lang/String;)Ljava/lang/String;", id = "skipAndReturn")
|
||||
private void surround$enterSkipAndReturn(String a) {
|
||||
@Surround.Skip boolean skip = a.isEmpty();
|
||||
Trace.add("enterSkipAndReturn " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipAndReturn")
|
||||
private String surround$skippedSkipAndReturn(String a) {
|
||||
Trace.add("skippedSkipAndReturn");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Surround.Return("skipAndReturn")
|
||||
private String surround$returnSkipAndReturn(String result, String a) {
|
||||
Trace.add("returnSkipAndReturn " + result);
|
||||
if (result.equals("x!")) {
|
||||
throw new IllegalStateException("return");
|
||||
}
|
||||
return result + "?";
|
||||
}
|
||||
|
||||
@Surround.Catch("skipAndReturn")
|
||||
private void surround$caughtSkipAndReturn(Throwable e) {
|
||||
Trace.add("caughtSkipAndReturn " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("skipAndReturn")
|
||||
private void surround$exitSkipAndReturn(String a) {
|
||||
Trace.add("exitSkipAndReturn " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "entryCatches(I)I", id = "entryCatches")
|
||||
private static void surround$enterEntryCatches(int a) {
|
||||
@Surround.Carry final boolean direct = !WholeTarget.guard;
|
||||
if (direct) {
|
||||
WholeTarget.guard = true;
|
||||
try {
|
||||
WholeTarget.entryCatches(a);
|
||||
} catch (Throwable t) {
|
||||
WholeTarget.guard = false;
|
||||
Trace.add("entryRethrow " + t.getMessage());
|
||||
throw t;
|
||||
}
|
||||
Trace.add("enterEntryCatches " + a);
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("entryCatches")
|
||||
private static void surround$exitEntryCatches(@Surround.Carry boolean direct) {
|
||||
if (direct) {
|
||||
WholeTarget.guard = false;
|
||||
}
|
||||
Trace.add("exitEntryCatches " + direct);
|
||||
}
|
||||
|
||||
@Surround(method = "wideBody(DJ)D", id = "wideBody")
|
||||
private static void surround$enterWideBody(double a, long b) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("wideBody")
|
||||
private static void surround$exitWideBody(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "throwingEntry(I)V", id = "throwingEntry")
|
||||
private void surround$enterThrowingEntry(int a) {
|
||||
Trace.add("enterThrows " + a);
|
||||
if (a > 0) {
|
||||
throw new IllegalArgumentException("entry failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingEntry")
|
||||
private void surround$exitThrowingEntry() {
|
||||
Trace.add("exitNothing");
|
||||
}
|
||||
|
||||
@Surround(method = "throwingFinally(II)I", id = "throwingFinally")
|
||||
private void surround$enterThrowingFinally(int a, int b) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingFinally")
|
||||
private void surround$exitThrowingFinally(@Surround.Carry long token) {
|
||||
Trace.add("exitThrows " + token);
|
||||
throw new UnsupportedOperationException("finally failed");
|
||||
}
|
||||
|
||||
@Surround(method = "caught(I)I", id = "caught")
|
||||
private void surround$enterCaught(int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("caught")
|
||||
private void surround$exitCaught(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "carryThree(I)I", id = "carryThree")
|
||||
private void surround$enterCarryThree(int a) {
|
||||
@Surround.Carry int count = a + 1;
|
||||
@Surround.Carry long stamp = 100L + a;
|
||||
@Surround.Carry String label = "L" + a;
|
||||
Trace.add("enter " + count + " " + stamp + " " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryThree")
|
||||
private void surround$exitCarryThree(@Surround.Carry String label, @Surround.Carry long stamp, @Surround.Carry int count) {
|
||||
Trace.add("exit " + count + " " + stamp + " " + label);
|
||||
}
|
||||
|
||||
@Surround(method = "carryUninitialized(I)I", id = "carryUninitialized")
|
||||
private void surround$enterCarryUninitialized(int a) {
|
||||
@Surround.Carry String label = new String(a > 0 ? "pos" : "neg");
|
||||
Trace.add("enter " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryUninitialized")
|
||||
private void surround$exitCarryUninitialized(@Surround.Carry String label) {
|
||||
Trace.add("exit " + label);
|
||||
}
|
||||
|
||||
@Surround(method = "carryGuarded(I)I", id = "carryGuarded")
|
||||
private void surround$enterCarryGuarded(int a) {
|
||||
@Surround.Carry int state = 0;
|
||||
try {
|
||||
state = WholeTarget.risky(a);
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = -1;
|
||||
Trace.add("recovered");
|
||||
}
|
||||
Trace.add("enter " + state);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryGuarded")
|
||||
private void surround$exitCarryGuarded(@Surround.Carry int flag) {
|
||||
Trace.add("exit " + flag);
|
||||
}
|
||||
|
||||
@Surround(method = "carrySplitAtEnd(I)I", id = "carrySplitAtEnd")
|
||||
private void surround$enterCarrySplitAtEnd(int a) {
|
||||
@Surround.Carry int flag;
|
||||
if (a > 0) {
|
||||
flag = 1;
|
||||
} else {
|
||||
flag = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("carrySplitAtEnd")
|
||||
private void surround$exitCarrySplitAtEnd(@Surround.Carry int flag) {
|
||||
Trace.add("exit " + flag);
|
||||
}
|
||||
|
||||
@Surround(method = "carryWideBeforeFrame(I)I", id = "carryWideBeforeFrame")
|
||||
private void surround$enterCarryWideBeforeFrame(int a) {
|
||||
@Surround.Carry long stamp = a;
|
||||
@Surround.Carry int count = a + 1;
|
||||
if (a > 0) {
|
||||
Trace.add("positive");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("carryWideBeforeFrame")
|
||||
private void surround$exitCarryWideBeforeFrame(@Surround.Carry long stamp, @Surround.Carry int count) {
|
||||
Trace.add("exit " + stamp + " " + count);
|
||||
}
|
||||
|
||||
@Surround(method = "carryArrays(I)I", id = "carryArrays")
|
||||
private void surround$enterCarryArrays(int a) {
|
||||
@Surround.Carry("elements") int[] elements = {a};
|
||||
String @Surround.Carry("names") [] names = {"n" + a};
|
||||
}
|
||||
|
||||
@Surround.Finally("carryArrays")
|
||||
private void surround$exitCarryArrays(@Surround.Carry("names") String[] names, @Surround.Carry("elements") int[] elements) {
|
||||
Trace.add("exit " + names[0] + " " + elements[0]);
|
||||
}
|
||||
|
||||
@Surround(method = "reused(I)I", id = "reused")
|
||||
private void surround$enterReused(int a) {
|
||||
{
|
||||
int tmp = a + 5;
|
||||
Trace.add("tmp " + tmp);
|
||||
}
|
||||
@Surround.Carry long prev = a + 7;
|
||||
Trace.add("enter " + prev);
|
||||
}
|
||||
|
||||
@Surround.Finally("reused")
|
||||
private void surround$exitPrev(@Surround.Carry long prev) {
|
||||
Trace.add("exit " + prev);
|
||||
}
|
||||
|
||||
@Surround(method = "throwWide(I)V", id = "throwWide")
|
||||
private void surround$enterThrowWide(int a) {
|
||||
@Surround.Carry long first = a + 1L;
|
||||
String scratch = "s" + a;
|
||||
@Surround.Carry double second = a / 2.0d;
|
||||
@Surround.Carry int third = a;
|
||||
Trace.add("enter " + first + " " + second + " " + third + " " + scratch);
|
||||
}
|
||||
|
||||
@Surround.Catch("throwWide")
|
||||
private void surround$caughtThrowWide(Throwable e, @Surround.Carry long first, @Surround.Carry double second, @Surround.Carry int third) {
|
||||
Trace.add("caught " + first + " " + second + " " + third);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwWide")
|
||||
private void surround$exitThrowWide(@Surround.Carry double second, @Surround.Carry int third, @Surround.Carry long first) {
|
||||
Trace.add("exit " + first + " " + second + " " + third);
|
||||
}
|
||||
|
||||
@Surround(method = "throwObject(I)V", id = "throwObject")
|
||||
private void surround$enterThrowObject(int a) {
|
||||
@Surround.Carry Object prev = WholeTarget.asObject("o" + a);
|
||||
Trace.add("enter " + prev);
|
||||
}
|
||||
|
||||
@Surround.Catch("throwObject")
|
||||
private void surround$caughtThrowObject(IllegalStateException e, @Surround.Carry String prev) {
|
||||
Trace.add("caught " + prev.length() + " " + prev);
|
||||
throw new UnsupportedOperationException("replaced", e);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwObject")
|
||||
private void surround$exitThrowObject(@Surround.Carry String prev) {
|
||||
Trace.add("exit " + prev.length() + " " + prev);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(value = WholeTarget.class, priority = 1100, remap = false)
|
||||
public class MixinWholeTargetHigh {
|
||||
|
||||
@Surround(method = "priority(I)I")
|
||||
private void surround$enterHigh(int a) {
|
||||
Trace.add("enterHigh");
|
||||
}
|
||||
|
||||
@Surround.Return
|
||||
private int surround$returnHigh(int result) {
|
||||
Trace.add("returnHigh " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally
|
||||
private void surround$exitHigh() {
|
||||
Trace.add("exitHigh");
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixinextras;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WrapTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.TWICE;
|
||||
|
||||
@Mixin(value = WrapTarget.class, remap = false)
|
||||
public class MixinWrapTarget {
|
||||
|
||||
@Surround(method = "wrapped", id = "wrapped")
|
||||
private void surround$enterWrapped(int a) {
|
||||
Trace.add("entry " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("wrapped")
|
||||
private void surround$exitWrapped() {
|
||||
Trace.add("finally");
|
||||
}
|
||||
|
||||
@WrapMethod(method = "wrapped")
|
||||
private int wrapWrapped(int a, Operation<Integer> original) {
|
||||
Trace.add("wrap-before " + a);
|
||||
final int result = original.call(a);
|
||||
Trace.add("wrap-after " + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "originalOperands", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int wrapOperation$shiftArgument(Callee callee, int a, Operation<Integer> original) {
|
||||
Trace.add("wrap " + a);
|
||||
return original.call(callee, a + 100);
|
||||
}
|
||||
|
||||
@Surround(method = "originalOperands", at = @At(value = "INVOKE", target = TWICE), id = "originalOperands")
|
||||
private void surround$enterOriginalOperands(Callee callee, int a) {
|
||||
Trace.add("enter " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("originalOperands")
|
||||
private void surround$exitOriginalOperands(Callee callee, int a) {
|
||||
Trace.add("exit " + a);
|
||||
}
|
||||
|
||||
@WrapMethod(method = "shifted")
|
||||
private int wrapShifted(int a, Operation<Integer> original, @Share("seen") LocalIntRef seen) {
|
||||
seen.set(a);
|
||||
Trace.add("wrap " + a);
|
||||
return original.call(a);
|
||||
}
|
||||
|
||||
@Surround(method = "shifted", at = @At(value = "INVOKE", target = TWICE), id = "shifted")
|
||||
private void surround$enterShifted(Callee callee, int value, @Surround.Local(ordinal = 1) int local, @Surround.Local String label) {
|
||||
Trace.add("enterShifted " + value + " " + local + " " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("shifted")
|
||||
private void surround$exitShifted(@Surround.Local(name = "local") int local, @Surround.Local String label) {
|
||||
Trace.add("exitShifted " + local + " " + label);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.targets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
public final class Trace {
|
||||
|
||||
private static final List<String> EVENTS = new ArrayList<>();
|
||||
private static long counter;
|
||||
|
||||
private Trace() {
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
EVENTS.clear();
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
public static void add(String event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
|
||||
public static void assertEvents(String... expected) {
|
||||
assertArrayEquals(expected, EVENTS.toArray(new String[EVENTS.size()]), EVENTS.toString());
|
||||
}
|
||||
|
||||
public static long nextToken() {
|
||||
return ++counter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public valueBelow(I)I
|
||||
TRYCATCHBLOCK L0 L1 L2 java/lang/IllegalStateException
|
||||
TRYCATCHBLOCK L3 L4 L5 null
|
||||
TRYCATCHBLOCK L0 L1 L5 null
|
||||
L6
|
||||
ILOAD 1
|
||||
ALOAD 0
|
||||
GETFIELD com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.callee : Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;
|
||||
ILOAD 1
|
||||
ISTORE 3
|
||||
ASTORE 2
|
||||
ISTORE 4
|
||||
L7
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.nextToken ()J
|
||||
LSTORE 5
|
||||
L8
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.md$jvmdowngrader$concat$surround$enterValueBelow$1$3f (IJ)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 4
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
L0
|
||||
INVOKEVIRTUAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee.explode (I)I
|
||||
L1
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
GOTO L10
|
||||
L2
|
||||
FRAME FULL [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget I com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I I J] [java/lang/IllegalStateException]
|
||||
ASTORE 7
|
||||
ILOAD 4
|
||||
L3
|
||||
ALOAD 0
|
||||
ALOAD 7
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$handleValueBelow (Ljava/lang/IllegalStateException;Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)I
|
||||
L4
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
GOTO L10
|
||||
L5
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 7
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
ALOAD 7
|
||||
ATHROW
|
||||
L10
|
||||
FRAME FULL [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget I com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I I J] [I I]
|
||||
BIPUSH 10
|
||||
IMUL
|
||||
IADD
|
||||
IRETURN
|
||||
L11
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget; L6 L11 0
|
||||
LOCALVARIABLE a I L6 L11 1
|
||||
MAXSTACK = 7
|
||||
MAXLOCALS = 8
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public valueSkip(I)I
|
||||
TRYCATCHBLOCK L0 L1 L2 null
|
||||
L3
|
||||
ALOAD 0
|
||||
GETFIELD com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.callee : Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;
|
||||
ILOAD 1
|
||||
ISTORE 3
|
||||
ASTORE 2
|
||||
L4
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.nextToken ()J
|
||||
LSTORE 4
|
||||
L5
|
||||
ILOAD 3
|
||||
IFGE L6
|
||||
ICONST_1
|
||||
GOTO L7
|
||||
L6
|
||||
FRAME APPEND [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I J]
|
||||
ICONST_0
|
||||
L7
|
||||
FRAME SAME1 I
|
||||
ISTORE 6
|
||||
L8
|
||||
ILOAD 3
|
||||
LLOAD 4
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.md$jvmdowngrader$concat$surround$enterValueSkip$1$50 (IJ)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 6
|
||||
IFEQ L10
|
||||
L0
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$skippedValueSkip (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)I
|
||||
GOTO L1
|
||||
L10
|
||||
FRAME APPEND [I]
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
INVOKEVIRTUAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee.twice (I)I
|
||||
L1
|
||||
FRAME SAME1 I
|
||||
ALOAD 0
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueSkip (J)V
|
||||
GOTO L11
|
||||
L2
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 7
|
||||
ALOAD 0
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueSkip (J)V
|
||||
ALOAD 7
|
||||
ATHROW
|
||||
L11
|
||||
FRAME SAME1 I
|
||||
IRETURN
|
||||
L12
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget; L3 L12 0
|
||||
LOCALVARIABLE a I L3 L12 1
|
||||
MAXSTACK = 5
|
||||
MAXLOCALS = 8
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public skipAndReturn(Ljava/lang/String;)Ljava/lang/String;
|
||||
TRYCATCHBLOCK L0 L1 L2 java/lang/Throwable
|
||||
TRYCATCHBLOCK L3 L4 L5 null
|
||||
TRYCATCHBLOCK L1 L6 L5 null
|
||||
TRYCATCHBLOCK L0 L1 L5 null
|
||||
L7
|
||||
ALOAD 1
|
||||
INVOKEVIRTUAL java/lang/String.isEmpty ()Z
|
||||
ISTORE 2
|
||||
L8
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.md$jvmdowngrader$concat$surround$enterSkipAndReturn$1 (Ljava/lang/String;)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 2
|
||||
IFEQ L10
|
||||
L0
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$skippedSkipAndReturn (Ljava/lang/String;)Ljava/lang/String;
|
||||
GOTO L6
|
||||
L10
|
||||
FRAME APPEND [I]
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.skipAndReturn$surround (Ljava/lang/String;)Ljava/lang/String;
|
||||
L1
|
||||
ASTORE 4
|
||||
ALOAD 0
|
||||
ALOAD 4
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$returnSkipAndReturn (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
||||
L6
|
||||
FRAME SAME1 java/lang/String
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
GOTO L11
|
||||
L2
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 3
|
||||
L3
|
||||
ALOAD 0
|
||||
ALOAD 3
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$caughtSkipAndReturn (Ljava/lang/Throwable;)V
|
||||
L4
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
ALOAD 3
|
||||
ATHROW
|
||||
L5
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 3
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
ALOAD 3
|
||||
ATHROW
|
||||
L11
|
||||
FRAME SAME1 java/lang/String
|
||||
ARETURN
|
||||
L12
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget; L7 L12 0
|
||||
LOCALVARIABLE a Ljava/lang/String; L7 L12 1
|
||||
LOCALVARIABLE skip Z L8 L9 2
|
||||
LOCALVARIABLE surround$error Ljava/lang/Throwable; L2 L12 3
|
||||
LOCALVARIABLE surround$result Ljava/lang/String; L1 L6 4
|
||||
MAXSTACK = 3
|
||||
MAXLOCALS = 5
|
||||
|
||||
// access flags 0x2
|
||||
private skipAndReturn$surround(Ljava/lang/String;)Ljava/lang/String;
|
||||
L0
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.jvmdowngrader$concat$skipAndReturn$1 (Ljava/lang/String;)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L1
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.jvmdowngrader$concat$skipAndReturn$2 (Ljava/lang/String;)Ljava/lang/String;
|
||||
ARETURN
|
||||
L2
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget; L0 L2 0
|
||||
LOCALVARIABLE a Ljava/lang/String; L0 L2 1
|
||||
MAXSTACK = 1
|
||||
MAXLOCALS = 2
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "com.gtnewhorizons.angelica.experimental.surround.integration.mixinextras",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"MixinWrapTarget"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "com.gtnewhorizons.angelica.experimental.surround.integration.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"MixinCallTarget",
|
||||
"MixinWholeTarget",
|
||||
"MixinWholeTargetHigh"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
extensions.getByType<JavaPluginExtension>().apply {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "GTNH Maven"
|
||||
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
|
||||
}
|
||||
maven {
|
||||
name = "Minecraft Libraries"
|
||||
url = uri("https://libraries.minecraft.net")
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
dependencies {
|
||||
add("shadowImplementation", project(":experimental-surround-runtime")) { isTransitive = false }
|
||||
add("mixinAnnotationProcessor", project(":experimental-surround-ap"))
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
minimize {
|
||||
exclude(project(":experimental-surround-runtime"))
|
||||
}
|
||||
relocate("com.gtnewhorizons.angelica.experimental.surround", "com.gtnewhorizons.angelica.experimental.surround")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
include("experimental-surround-runtime", "experimental-surround-ap")
|
||||
project(":experimental-surround-runtime").projectDir = file("runtime")
|
||||
project(":experimental-surround-ap").projectDir = file("ap")
|
||||
+37
-2
@@ -46,6 +46,27 @@ tasks.withType<Jar>().configureEach {
|
||||
exclude("net/minecraft/**")
|
||||
}
|
||||
|
||||
val stubsElements by configurations.creating {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
attributes {
|
||||
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, Usage.JAVA_RUNTIME))
|
||||
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class.java, Category.LIBRARY))
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
outgoing {
|
||||
capability("${project.group}:glsm-stubs:${project.version}")
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
add(stubsElements.name, sourceSets["stubs"].java.classesDirectory) {
|
||||
type = "java-classes-directory"
|
||||
builtBy(tasks.named("stubsClasses"))
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Mojang"
|
||||
@@ -82,6 +103,11 @@ repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
val spirvTestRuntime by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// MC compile stubs
|
||||
compileOnly(sourceSets["stubs"].output)
|
||||
@@ -112,8 +138,10 @@ dependencies {
|
||||
testRuntimeOnly(libs.lwjgl3.spvc) { artifact { classifier = lwjglNatives } }
|
||||
testRuntimeOnly(libs.lwjgl3)
|
||||
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
|
||||
spirvTestRuntime(libs.lwjgl3.opengl)
|
||||
// @Lwjgl3Aware annotation
|
||||
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
|
||||
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
|
||||
compileOnly(libs.lombok) { isTransitive = false }
|
||||
annotationProcessor(libs.lombok)
|
||||
@@ -140,7 +168,6 @@ dependencies {
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.log4j.core)
|
||||
testImplementation(libs.celeritas.common) { isTransitive = false }
|
||||
testRuntimeOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
|
||||
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
|
||||
testRuntimeOnly(libs.commons.lang3)
|
||||
}
|
||||
@@ -156,6 +183,7 @@ dependencies {
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
outputs.cacheIf { true }
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
@@ -224,6 +252,9 @@ fun Test.configureGlsmJava8() {
|
||||
val extractNatives = rootProject.tasks.named("extractNatives2")
|
||||
dependsOn(extractNatives)
|
||||
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
|
||||
if (System.getProperty("os.name").lowercase().contains("mac")) {
|
||||
jvmArgs("-Dapple.awt.UIElement=true")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
@@ -272,6 +303,8 @@ val verifyTestsRan by tasks.registering {
|
||||
}
|
||||
tasks.check { dependsOn(verifyTestsRan) }
|
||||
|
||||
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
|
||||
|
||||
val spirvTest by tasks.registering(Test::class) {
|
||||
description = "Runs SpirvShaderTranslator* tests against native shaderc + spvc on Java 21."
|
||||
group = "verification"
|
||||
@@ -285,11 +318,12 @@ val spirvTest by tasks.registering(Test::class) {
|
||||
classpath = sourceSets["test"].runtimeClasspath.filter { file ->
|
||||
// Strip the LWJGL2 jars - they collide with LWJGL3's sealed org.lwjgl package.
|
||||
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
}.plus(spirvTestRuntime)
|
||||
|
||||
filter {
|
||||
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
|
||||
includeTestsMatching("com.gtnewhorizons.angelica.glsm.ffp.FFPUniformBlockSpirvLayoutTest")
|
||||
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvCompilerDiskCacheTest")
|
||||
}
|
||||
|
||||
// libshaderc.so / libspirv-cross.so are extracted by extractNatives3.
|
||||
@@ -299,6 +333,7 @@ val spirvTest by tasks.registering(Test::class) {
|
||||
"-Djava.library.path=${extractNatives3.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}",
|
||||
"--enable-native-access=ALL-UNNAMED"
|
||||
)
|
||||
loadLwjgl3NativesFrom(unpackLwjgl3Natives)
|
||||
|
||||
testLogging {
|
||||
showStandardStreams = true
|
||||
|
||||
@@ -17,9 +17,10 @@ public final class SystemProperties {
|
||||
|
||||
// Backend selection
|
||||
public static final String KEY_USE_SDL_GPU = "angelica.sdlgpu.enable";
|
||||
public static final boolean USE_SDL_GPU = Boolean.getBoolean(KEY_USE_SDL_GPU);
|
||||
public static final Boolean SDL_GPU_OVERRIDE = parseBooleanOverride(KEY_USE_SDL_GPU);
|
||||
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
|
||||
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
|
||||
|
||||
// Redirector
|
||||
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
|
||||
@@ -27,27 +28,49 @@ public final class SystemProperties {
|
||||
|
||||
// SDL-GPU
|
||||
public static final String KEY_SDL_GPU_DRIVER = "angelica.sdlgpu.driver";
|
||||
public static final String SDL_GPU_DRIVER = System.getProperty(KEY_SDL_GPU_DRIVER, "");
|
||||
public static final String SDL_GPU_DRIVER_OVERRIDE = parseStringOverride(KEY_SDL_GPU_DRIVER);
|
||||
public static final String MOLTENVK_DIR = System.getProperty("angelica.moltenvk.dir", "angelica" + File.separator + "natives" + File.separator + "moltenvk");
|
||||
public static final boolean SDL_GPU_DEBUG = Boolean.getBoolean("angelica.sdlgpu.debug");
|
||||
private static final SdlAssertionMode ENCODER_ASSERTIONS = parseEnum("angelica.sdlgpu.encoderAssertions", SdlAssertionMode.OFF, SdlAssertionMode.WARN, SdlAssertionMode.class);
|
||||
public static final boolean SDL_ENCODER_ASSERTIONS = ENCODER_ASSERTIONS != SdlAssertionMode.OFF;
|
||||
public static final boolean SDL_ENCODER_ASSERTIONS_FATAL = ENCODER_ASSERTIONS == SdlAssertionMode.FATAL;
|
||||
public static final boolean SDL_VERIFY_STATE_SYNC = Boolean.getBoolean("angelica.sdlgpu.verifyStateSync");
|
||||
public static final boolean SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK = Boolean.getBoolean("angelica.sdlgpu.verifyPerFrameUniformBlock");
|
||||
public static final int SDL_FRAMES_IN_FLIGHT = Integer.getInteger("angelica.sdlgpu.framesInFlight", 2);
|
||||
public static final boolean DISABLE_SDL_PRESENTER_THREAD = Boolean.getBoolean("angelica.sdlgpu.disablePresenterThread");
|
||||
public static final boolean SDL_DISABLE_IN_PASS_CLEAR = Boolean.getBoolean("angelica.sdlgpu.disableInPassClear");
|
||||
|
||||
// Tracy
|
||||
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
|
||||
public static final boolean TRACY_FINE_ZONES = Boolean.getBoolean("angelica.tracy.fineZones");
|
||||
public static final Boolean TRACY_OVERRIDE = parseBooleanOverride("angelica.tracy");
|
||||
public static final Boolean TRACY_FINE_ZONES_OVERRIDE = parseBooleanOverride("angelica.tracy.fineZones");
|
||||
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
|
||||
public static final int TRACY_MAX_SRC_LOCS = Math.max(16, Integer.getInteger("angelica.tracy.maxSrcLocs", 4096));
|
||||
public static final Integer TRACY_MAX_SRC_LOCS_OVERRIDE = Integer.getInteger("angelica.tracy.maxSrcLocs");
|
||||
|
||||
// Profiling
|
||||
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
|
||||
public static final String PROFILE_DIR = System.getProperty("angelica.profile.dir", "angelica" + File.separator + "profiles");
|
||||
public static final String PROFILE_OUTPUT = System.getProperty("angelica.profile.output", "");
|
||||
|
||||
// Flyby
|
||||
public static final String FLYBY_ROUTE = System.getProperty("angelica.flyby.route", "");
|
||||
public static final boolean FLYBY_WAIT_FOR_TRACY = Boolean.getBoolean("angelica.flyby.waitForTracy");
|
||||
public static final boolean FLYBY_WAIT_FOR_FOCUS = Boolean.getBoolean("angelica.flyby.waitForFocus");
|
||||
public static final int FLYBY_WARMUP_TICKS = Integer.getInteger("angelica.flyby.warmupTicks", 400);
|
||||
public static final int FLYBY_LENGTH = Integer.getInteger("angelica.flyby.length", 0); // Blocks for moving routes, ticks for stationary
|
||||
public static final double FLYBY_SPEED = parseDouble("angelica.flyby.speed"); // Travel speed in blocks per tick
|
||||
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
|
||||
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
|
||||
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
|
||||
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
|
||||
public static final Integer FLYBY_DIMENSION = Integer.getInteger("angelica.flyby.dimension");
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
|
||||
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
|
||||
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
|
||||
public static final float FLYBY_PITCH = parseFloat("angelica.flyby.pitch");
|
||||
public static final int FLYBY_SCREENSHOTS = Integer.getInteger("angelica.flyby.screenshots", 0);
|
||||
public static final boolean FLYBY_DEBUG_HUD = Boolean.getBoolean("angelica.flyby.debugHud");
|
||||
public static final String FLYBY_CRASH_TEST = System.getProperty("angelica.flyby.crashTest", "");
|
||||
|
||||
// Debug
|
||||
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
|
||||
@@ -55,18 +78,30 @@ public final class SystemProperties {
|
||||
public static final boolean DEBUG_F3_DETAIL = Boolean.getBoolean("angelica.debug.f3Detail");
|
||||
public static final boolean ENABLE_TEST_BLOCKS = Boolean.getBoolean("angelica.debug.testBlocks");
|
||||
public static final boolean DUMP_CLASS = Boolean.getBoolean("angelica.debug.dumpClass");
|
||||
public static final boolean DUMP_SHADERS = Boolean.getBoolean("angelica.debug.dumpShaders") || isDeobf();
|
||||
private static final boolean DUMP_SHADERS_PROP = Boolean.getBoolean("angelica.debug.dumpShaders");
|
||||
private static final boolean DEBUG_TOOLING_PROP = Boolean.getBoolean("angelica.debug.tooling");
|
||||
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
|
||||
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
|
||||
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
|
||||
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
|
||||
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
|
||||
public static final boolean WEATHER_REBUILD_ALWAYS = Boolean.getBoolean("angelica.debug.weatherRebuildAlways");
|
||||
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
|
||||
|
||||
// Set by us, read by celeritas
|
||||
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
|
||||
public static final String KEY_CELERITAS_LWJGL_SERVICE = "celeritas.lwjglService";
|
||||
|
||||
public static boolean dumpShaders() {
|
||||
return DUMP_SHADERS_PROP || isDeobf();
|
||||
}
|
||||
|
||||
public static boolean debugTooling() {
|
||||
return DEBUG_TOOLING_PROP || isDeobf();
|
||||
}
|
||||
|
||||
public static Path shaderDumpDir(String phase) {
|
||||
return DUMP_SHADERS ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
|
||||
return dumpShaders() ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
|
||||
}
|
||||
|
||||
public enum UnmappedGLMode {
|
||||
@@ -94,6 +129,31 @@ public final class SystemProperties {
|
||||
FATAL
|
||||
}
|
||||
|
||||
public enum FlybyPacing {
|
||||
UNCAPPED,
|
||||
CONFIGURED
|
||||
}
|
||||
|
||||
public enum FlybyCamera {
|
||||
FIRST_PERSON,
|
||||
THIRD_PERSON_BACK,
|
||||
THIRD_PERSON_FRONT
|
||||
}
|
||||
|
||||
public enum FlybyWeather {
|
||||
CLEAR,
|
||||
RAIN,
|
||||
THUNDER;
|
||||
|
||||
public boolean isRaining() {
|
||||
return this != CLEAR;
|
||||
}
|
||||
|
||||
public boolean isThundering() {
|
||||
return this == THUNDER;
|
||||
}
|
||||
}
|
||||
|
||||
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
|
||||
final String raw = System.getProperty(key);
|
||||
if (raw == null || raw.isEmpty()) return whenAbsent;
|
||||
@@ -105,6 +165,16 @@ public final class SystemProperties {
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean parseBooleanOverride(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
return raw == null || raw.isEmpty() ? null : Boolean.parseBoolean(raw);
|
||||
}
|
||||
|
||||
private static String parseStringOverride(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
return raw == null || raw.isEmpty() ? null : raw;
|
||||
}
|
||||
|
||||
private static double parseDouble(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
if (raw == null || raw.isEmpty()) return 0.0D;
|
||||
@@ -115,7 +185,17 @@ public final class SystemProperties {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDeobf() {
|
||||
private static float parseFloat(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
if (raw == null || raw.isEmpty()) return Float.NaN;
|
||||
try {
|
||||
return Float.parseFloat(raw);
|
||||
} catch (NumberFormatException e) {
|
||||
return Float.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDeobf() {
|
||||
return Launch.blackboard != null && Boolean.TRUE.equals(Launch.blackboard.get("fml.deobfuscatedEnvironment"));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
|
||||
public final class ClipPlaneBooleanState extends BooleanStateStack {
|
||||
|
||||
private final GLContextState owner;
|
||||
private final int index;
|
||||
|
||||
ClipPlaneBooleanState(GLContextState owner, int index, int glCap) {
|
||||
super(glCap);
|
||||
this.owner = owner;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnabledChanged() {
|
||||
if (isEnabled()) {
|
||||
owner.clipPlaneEnabledMask |= (1 << index);
|
||||
} else {
|
||||
owner.clipPlaneEnabledMask &= ~(1 << index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
@@ -139,12 +141,20 @@ public class CompatShaderTransformer {
|
||||
if (cached != null) {
|
||||
result = cached;
|
||||
} else {
|
||||
try {
|
||||
result = transformInternal(source, isFragment);
|
||||
final ShaderDiskCache.Key dk = ShaderDiskCache.isEnabled() ? diskKey(source, isFragment) : null;
|
||||
final String disk = dk != null ? ShaderDiskCache.getString(dk) : null;
|
||||
if (disk != null) {
|
||||
result = disk;
|
||||
cache.put(key, result);
|
||||
} catch (Exception e) {
|
||||
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
|
||||
result = fixupVersion(source);
|
||||
} else {
|
||||
try {
|
||||
result = transformInternal(source, isFragment);
|
||||
cache.put(key, result);
|
||||
if (dk != null) ShaderDiskCache.putString(dk, result);
|
||||
} catch (Exception e) {
|
||||
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
|
||||
result = fixupVersion(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,6 +174,14 @@ public class CompatShaderTransformer {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int minGlslVersion() {
|
||||
return RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330;
|
||||
}
|
||||
|
||||
static ShaderDiskCache.Key diskKey(String source, boolean isFragment) {
|
||||
return ShaderDiskCache.key("compat").str(source).b(isFragment).i(minGlslVersion());
|
||||
}
|
||||
|
||||
public static boolean isCoreShader(String source) {
|
||||
final Matcher vm = VERSION_PATTERN.matcher(source);
|
||||
if (vm.find()) {
|
||||
@@ -186,7 +204,7 @@ public class CompatShaderTransformer {
|
||||
declaredVersion = Integer.parseInt(versionMatcher.group(1));
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(declaredVersion, RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330);
|
||||
final int targetVersion = Math.max(declaredVersion, minGlslVersion());
|
||||
|
||||
// Pre-parse reserved word renaming - prevents ANTLR parse failures
|
||||
source = GlslTransformUtils.replaceTexture(source);
|
||||
@@ -439,12 +457,7 @@ public class CompatShaderTransformer {
|
||||
continue;
|
||||
}
|
||||
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
|
||||
declarators.add(decl.typeless_declaration());
|
||||
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
|
||||
declarators.addAll(list.typeless_declaration());
|
||||
}
|
||||
for (GLSLParser.Typeless_declarationContext d : declarators) {
|
||||
for (GLSLParser.Typeless_declarationContext d : ShaderIndex.declarators((GLSLParser.Init_declarator_listContext) decl.getParent())) {
|
||||
if (d.IDENTIFIER() == null) continue;
|
||||
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
|
||||
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
|
||||
|
||||
@@ -117,6 +117,7 @@ public class CompatUniformManager {
|
||||
|
||||
private static final class ProgramUniforms {
|
||||
final int[] locs;
|
||||
GLContextState owner;
|
||||
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
|
||||
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
|
||||
ProgramUniforms(int[] locs) { this.locs = locs; }
|
||||
@@ -161,7 +162,13 @@ public class CompatUniformManager {
|
||||
}
|
||||
|
||||
if (hasAny) {
|
||||
programUniforms.put(program, new ProgramUniforms(locs));
|
||||
final ProgramUniforms uniforms = new ProgramUniforms(locs);
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
programUniforms.put(program, uniforms);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
|
||||
}
|
||||
}
|
||||
@@ -171,16 +178,21 @@ public class CompatUniformManager {
|
||||
*
|
||||
* @return Whether the program needs emulation (FFP or Iris Program)
|
||||
*/
|
||||
public static boolean refreshCompatUniforms(int program) {
|
||||
public static boolean refreshCompatUniforms(int program, GLContextState glCtx) {
|
||||
final ProgramUniforms pu = programUniforms.get(program);
|
||||
if (pu == null) return false;
|
||||
final int[] locs = pu.locs;
|
||||
if (pu.owner != glCtx) {
|
||||
pu.owner = glCtx;
|
||||
pu.lastMvGen = pu.lastMvLinearGen = pu.lastProjGen = pu.lastTexMatGen = -1;
|
||||
pu.lastFragmentGen = pu.lastLightingGen = pu.lastClipPlaneGen = -1;
|
||||
}
|
||||
|
||||
// Matrix uniforms — skip if this program's storage already holds the current generation
|
||||
final int mvGen = GLStateManager.getMvGeneration();
|
||||
final int mvLinearGen = GLStateManager.getMvLinearGeneration();
|
||||
final int projGen = GLStateManager.getProjGeneration();
|
||||
final int texMatGen = GLStateManager.getTexMatrixGeneration();
|
||||
final int mvGen = glCtx.mvGeneration;
|
||||
final int mvLinearGen = glCtx.mvLinearGeneration;
|
||||
final int projGen = glCtx.projGeneration;
|
||||
final int texMatGen = glCtx.texMatrixGeneration;
|
||||
final boolean mvChanged = mvGen != pu.lastMvGen;
|
||||
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
|
||||
final boolean projChanged = projGen != pu.lastProjGen;
|
||||
@@ -194,14 +206,14 @@ public class CompatUniformManager {
|
||||
}
|
||||
|
||||
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
|
||||
final int fragGen = GLStateManager.getFragmentGeneration();
|
||||
final int fragGen = glCtx.fragmentGeneration;
|
||||
if (fragGen != pu.lastFragmentGen) {
|
||||
pu.lastFragmentGen = fragGen;
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
// Lighting-derived uniforms (scene color, light sources, material)
|
||||
final int litGen = GLStateManager.getLightingGeneration();
|
||||
final int litGen = glCtx.lightingGeneration;
|
||||
if (litGen != pu.lastLightingGen) {
|
||||
pu.lastLightingGen = litGen;
|
||||
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
|
||||
@@ -211,7 +223,7 @@ public class CompatUniformManager {
|
||||
|
||||
// Clip plane equations + enabled bool — uploaded when enable state or equations change
|
||||
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
final int cpGen = GLStateManager.getClipPlaneGeneration();
|
||||
final int cpGen = glCtx.clipPlaneGeneration;
|
||||
if (cpGen != pu.lastClipPlaneGen) {
|
||||
pu.lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
@@ -438,7 +450,12 @@ public class CompatUniformManager {
|
||||
}
|
||||
|
||||
public static void onDeleteProgram(int program) {
|
||||
programUniforms.remove(program);
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
programUniforms.remove(program);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasProgram(int program) {
|
||||
|
||||
@@ -59,7 +59,7 @@ public class DisplayListIDAllocator {
|
||||
numSetElements = 1;
|
||||
}
|
||||
|
||||
public int allocRange(int count) {
|
||||
public synchronized int allocRange(int count) {
|
||||
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
|
||||
if (count == 1) return allocSingle();
|
||||
return allocMultiple(count);
|
||||
@@ -132,7 +132,7 @@ public class DisplayListIDAllocator {
|
||||
return numElements;
|
||||
}
|
||||
|
||||
public void free(int id) {
|
||||
public synchronized void free(int id) {
|
||||
if (id <= 0) return; // ID 0 is reserved
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) return;
|
||||
@@ -147,7 +147,7 @@ public class DisplayListIDAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
public void freeRange(int base, int count) {
|
||||
public synchronized void freeRange(int base, int count) {
|
||||
if (count <= 0 || base <= 0) return;
|
||||
|
||||
int startIdx = base / 32;
|
||||
@@ -166,7 +166,7 @@ public class DisplayListIDAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAllocated(int id) {
|
||||
public synchronized boolean isAllocated(int id) {
|
||||
if (id <= 0) return false;
|
||||
int idx = id / 32;
|
||||
if (idx >= numSetElements) return false;
|
||||
|
||||
@@ -79,12 +79,13 @@ public class DisplayListManager {
|
||||
// Display list compilation state (current/active context)
|
||||
private static int glListMode = 0;
|
||||
private static int glListId = -1;
|
||||
private static int recordedStatePushes;
|
||||
private static final DisplayListCommand POP_STATE = GLStateManager::popState;
|
||||
private static CommandRecorder currentRecorder = null; // Command recorder (null when not recording)
|
||||
private static volatile Thread recordingThread = null; // Thread that started recording (for thread-safety)
|
||||
private static List<AccumulatedDraw> accumulatedDraws = null; // Accumulates quad draws for batching
|
||||
private static AccumulatedDraw pendingDraw = null;
|
||||
private static DisplayListCallback transformCallback = null;
|
||||
private static int relativeTransformType;
|
||||
|
||||
private static final int TRANSFORM_SCALE = 0x1; // Can be extracted to a glScale easily
|
||||
private static final int TRANSFORM_TRANSLATE = 0x2; // Can be extracted to a glTranslate easily
|
||||
@@ -93,7 +94,7 @@ public class DisplayListManager {
|
||||
private static StackTraceElement[] compilationStackTrace = null; // For logging: captured at glNewList()
|
||||
|
||||
// Debug logging: track sources of MULT_MATRIX commands and draw origins; only populated when LOG_DISPLAY_LIST_COMPILATION is true
|
||||
private static List<String> pendingTransformOps = null; // Ops accumulated since last flush
|
||||
private static List<List<String>> pendingTransformOps = null; // Ops per matrix slot since its last flush
|
||||
private static List<List<String>> multMatrixSources = null; // Source ops for each MULT_MATRIX in raw buffer
|
||||
private static List<String> drawRangeSources = null; // Source type for each DRAW_RANGE in final buffer
|
||||
|
||||
@@ -114,9 +115,10 @@ public class DisplayListManager {
|
||||
List<AccumulatedDraw> draws,
|
||||
DisplayListCallback callback,
|
||||
StackTraceElement[] stackTrace,
|
||||
int recordedStatePushes,
|
||||
|
||||
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
|
||||
List<String> pendingOps,
|
||||
List<List<String>> pendingOps,
|
||||
List<List<String>> matrixSources,
|
||||
List<String> drawSources
|
||||
) {}
|
||||
@@ -141,7 +143,7 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
public static String describeCurrentCompilation() {
|
||||
if (currentRecorder == null) {
|
||||
if (!isRecording()) {
|
||||
return "(not compiling)";
|
||||
}
|
||||
final String mode = glListMode == GL11.GL_COMPILE_AND_EXECUTE ? "GL_COMPILE_AND_EXECUTE" : "GL_COMPILE";
|
||||
@@ -150,16 +152,20 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
public static boolean isCompileAndExecute() {
|
||||
return glListMode == GL11.GL_COMPILE_AND_EXECUTE;
|
||||
return isRecording() && glListMode == GL11.GL_COMPILE_AND_EXECUTE;
|
||||
}
|
||||
|
||||
public static CommandRecorder pauseRecording() {
|
||||
if (Thread.currentThread() != recordingThread) return null;
|
||||
final CommandRecorder r = currentRecorder;
|
||||
currentRecorder = null;
|
||||
return r;
|
||||
}
|
||||
|
||||
public static void resumeRecording(CommandRecorder r) {
|
||||
if (Thread.currentThread() != recordingThread) {
|
||||
throw new IllegalStateException("Cannot resume another thread's display list compilation");
|
||||
}
|
||||
currentRecorder = r;
|
||||
}
|
||||
|
||||
@@ -168,7 +174,9 @@ public class DisplayListManager {
|
||||
|
||||
static void flushAll() {
|
||||
drawBarrier();
|
||||
flushMatrix();
|
||||
for (int slot = 0; slot < DisplayListCallback.SLOT_COUNT; slot++) {
|
||||
flushMatrix(slot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,13 +191,21 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit accumulated transform as MultMatrix if non-identity, then reset.
|
||||
* Emit the current matrix target's accumulated transform as MultMatrix if non-identity, then reset.
|
||||
*/
|
||||
static void flushMatrix() {
|
||||
if (transformCallback.isIdentity()) {
|
||||
flushMatrix(transformCallback.getCurrentSlot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit one matrix target's accumulated transform as MultMatrix if non-identity, then reset.
|
||||
*/
|
||||
private static void flushMatrix(int slot) {
|
||||
if (transformCallback.isIdentity(slot)) {
|
||||
// Clear pending ops even if we don't emit - they were no-ops (identity)
|
||||
transformCallback.reset(slot);
|
||||
if (pendingTransformOps != null) {
|
||||
pendingTransformOps.clear();
|
||||
pendingTransformOps.get(slot).clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -197,44 +213,60 @@ public class DisplayListManager {
|
||||
|
||||
// Save pending transform ops for logging (before we clear them)
|
||||
if (multMatrixSources != null && pendingTransformOps != null) {
|
||||
if (pendingTransformOps.isEmpty()) {
|
||||
final List<String> ops = pendingTransformOps.get(slot);
|
||||
if (ops.isEmpty()) {
|
||||
GLStateManager.LOGGER.warn("flushMatrix: non-identity transform with no tracked ops");
|
||||
multMatrixSources.add(Collections.singletonList("(unknown source)"));
|
||||
} else {
|
||||
multMatrixSources.add(new ArrayList<>(pendingTransformOps));
|
||||
multMatrixSources.add(new ArrayList<>(ops));
|
||||
}
|
||||
pendingTransformOps.clear();
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
// Record the collapsed MultMatrix command (for playback)
|
||||
// Always put a draw barrier BEFORE flushing the matrix (the transforms are already baked into the current draw)
|
||||
drawBarrier();
|
||||
if (relativeTransformType == TRANSFORM_SCALE) {
|
||||
currentRecorder.writeScale(transformCallback.getScale(transformVector));
|
||||
} else if (relativeTransformType == TRANSFORM_TRANSLATE) {
|
||||
currentRecorder.writeTranslate(transformCallback.getTranslation(transformVector));
|
||||
syncStreamMatrixMode(transformCallback.getSlotMatrixMode(slot));
|
||||
final Matrix4f pending = transformCallback.getReadMatrix(slot);
|
||||
final int type = transformCallback.getTransformType(slot);
|
||||
if (type == TRANSFORM_SCALE) {
|
||||
currentRecorder.writeScale(transformCallback.getScale(slot, transformVector));
|
||||
} else if (type == TRANSFORM_TRANSLATE) {
|
||||
currentRecorder.writeTranslate(transformCallback.getTranslation(slot, transformVector));
|
||||
} else {
|
||||
currentRecorder.writeMultMatrix(transformCallback.getReadMatrix());
|
||||
currentRecorder.writeMultMatrix(pending);
|
||||
}
|
||||
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
|
||||
GLStateManager.applyMultMatrix(transformCallback.getReadMatrix());
|
||||
GLStateManager.applyMultMatrix(pending);
|
||||
}
|
||||
|
||||
|
||||
// Reset to identity - we're now synchronized with GL
|
||||
resetRelativeTransform();
|
||||
transformCallback.reset(slot);
|
||||
}
|
||||
|
||||
private static void syncStreamMatrixMode(int mode) {
|
||||
if (transformCallback.getStreamMatrixMode() == 0 && !transformCallback.explicitMatrixMode) {
|
||||
return;
|
||||
}
|
||||
if (transformCallback.getStreamMatrixMode() == mode) {
|
||||
return;
|
||||
}
|
||||
drawBarrier();
|
||||
transformCallback.setStreamMatrixMode(mode);
|
||||
currentRecorder.writeMatrixMode(mode);
|
||||
}
|
||||
|
||||
public static int getRecordingListId() {
|
||||
return glListId;
|
||||
return Thread.currentThread() == recordingThread ? glListId : -1;
|
||||
}
|
||||
|
||||
public static int getListMode() {
|
||||
return glListMode;
|
||||
return Thread.currentThread() == recordingThread ? glListMode : 0;
|
||||
}
|
||||
|
||||
public static void trackDrawRangeSource(String source) {
|
||||
if (drawRangeSources != null) {
|
||||
if (Thread.currentThread() == recordingThread && drawRangeSources != null) {
|
||||
drawRangeSources.add(source);
|
||||
}
|
||||
}
|
||||
@@ -307,6 +339,16 @@ public class DisplayListManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void recordSecondaryColor(float r, float g, float b) {
|
||||
drawBarrier();
|
||||
currentRecorder.writeSecondaryColor(r, g, b);
|
||||
}
|
||||
|
||||
public static void recordVertexAttrib(int index, float x, float y, float z, float w) {
|
||||
drawBarrier();
|
||||
currentRecorder.writeVertexAttrib(index, x, y, z, w);
|
||||
}
|
||||
|
||||
public static void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
|
||||
drawBarrier();
|
||||
currentRecorder.writeColorMask(r, g, b, a);
|
||||
@@ -363,14 +405,37 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
public static void recordMatrixMode(int mode) {
|
||||
flushMatrix(); // Matrix barrier: flush and reset
|
||||
final int previous = transformCallback.getMatrixMode();
|
||||
if (!transformCallback.explicitMatrixMode) {
|
||||
// Pending transforms still inherit the caller's target until this explicit selection.
|
||||
flushMatrix();
|
||||
}
|
||||
transformCallback.explicitMatrixMode = true;
|
||||
if (previous == mode) {
|
||||
// The initial target and the target after a nested list are unknown at playback.
|
||||
syncStreamMatrixMode(mode);
|
||||
return;
|
||||
}
|
||||
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
|
||||
// Live GL follows the app's matrix mode, so a pending delta can only be replayed onto GL
|
||||
// while its own target is still current.
|
||||
flushAll();
|
||||
} else {
|
||||
// MODELVIEW and PROJECTION share the vertex delta, so a delta belonging to one of them has to
|
||||
// be flushed before the other starts adding to it.
|
||||
final int slot = DisplayListCallback.slotOf(mode);
|
||||
if (transformCallback.getSlotMatrixMode(slot) != mode) {
|
||||
flushMatrix(slot);
|
||||
}
|
||||
}
|
||||
transformCallback.setMatrixMode(mode);
|
||||
currentRecorder.writeMatrixMode(mode);
|
||||
}
|
||||
|
||||
public static void recordPushMatrix() {
|
||||
drawBarrier();
|
||||
// Flush any pending delta, then record push.
|
||||
flushMatrix();
|
||||
syncStreamMatrixMode(transformCallback.getMatrixMode());
|
||||
currentRecorder.writePushMatrix();
|
||||
}
|
||||
|
||||
@@ -378,6 +443,7 @@ public class DisplayListManager {
|
||||
// Discard any transformations & flush any pending draw operations
|
||||
drawBarrier();
|
||||
resetRelativeTransform();
|
||||
syncStreamMatrixMode(transformCallback.getMatrixMode());
|
||||
currentRecorder.writePopMatrix();
|
||||
}
|
||||
|
||||
@@ -427,6 +493,8 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
public static void recordActiveTexture(int texture) {
|
||||
drawBarrier();
|
||||
flushMatrix(DisplayListCallback.SLOT_TEXTURE);
|
||||
currentRecorder.writeActiveTexture(texture);
|
||||
}
|
||||
|
||||
@@ -435,15 +503,50 @@ public class DisplayListManager {
|
||||
currentRecorder.writeUseProgram(program);
|
||||
}
|
||||
|
||||
// PushAttrib saves state but doesn't change it - not a draw barrier
|
||||
private record AttribState(int matrixMode, boolean restoresTexture) {}
|
||||
|
||||
public static void recordPushAttrib(int mask) {
|
||||
final boolean savesMode = (mask & GL11.GL_TRANSFORM_BIT) != 0;
|
||||
if (savesMode) syncStreamMatrixMode(transformCallback.getMatrixMode());
|
||||
transformCallback.attribStates.push(new AttribState(
|
||||
savesMode ? transformCallback.getStreamMatrixMode() : -1, (mask & GL11.GL_TEXTURE_BIT) != 0));
|
||||
currentRecorder.writePushAttrib(mask);
|
||||
}
|
||||
|
||||
// PopAttrib restores potentially any state - draw barrier
|
||||
public static void recordPopAttrib() {
|
||||
drawBarrier();
|
||||
final AttribState restored = transformCallback.attribStates.poll();
|
||||
final int restoredMode = restored == null ? 0 : restored.matrixMode();
|
||||
if (restoredMode != -1) flushAll();
|
||||
else {
|
||||
drawBarrier();
|
||||
if (restored.restoresTexture()) flushMatrix(DisplayListCallback.SLOT_TEXTURE);
|
||||
}
|
||||
currentRecorder.writePopAttrib();
|
||||
if (restoredMode != -1) {
|
||||
transformCallback.setMatrixMode(restoredMode == 0 ? GL11.GL_MODELVIEW : restoredMode);
|
||||
transformCallback.setStreamMatrixMode(restoredMode);
|
||||
transformCallback.explicitMatrixMode = restoredMode != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static int virtualStateDepth(RecordMode mode) {
|
||||
return mode == RecordMode.COMPILE ? recordedStatePushes : 0;
|
||||
}
|
||||
|
||||
public static void recordPushState(StateSet set) {
|
||||
currentRecorder.writeComplexCommand(set.pushCommand);
|
||||
recordedStatePushes++;
|
||||
}
|
||||
|
||||
public static boolean recordPopStateIfPending() {
|
||||
if (recordedStatePushes > 0) {
|
||||
drawBarrier();
|
||||
currentRecorder.writeComplexCommand(POP_STATE);
|
||||
recordedStatePushes--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void recordFogf(int pname, float param) {
|
||||
@@ -544,7 +647,10 @@ public class DisplayListManager {
|
||||
public static void recordCallList(int listId) {
|
||||
// Flush all pending draws & transformations
|
||||
flushAll();
|
||||
syncStreamMatrixMode(transformCallback.getMatrixMode());
|
||||
currentRecorder.writeCallList(listId);
|
||||
transformCallback.setStreamMatrixMode(0);
|
||||
transformCallback.explicitMatrixMode = false;
|
||||
}
|
||||
|
||||
public static void recordDrawBuffer(int mode) {
|
||||
@@ -567,6 +673,19 @@ public class DisplayListManager {
|
||||
currentRecorder.writeComplexCommand(cmd);
|
||||
}
|
||||
|
||||
public static void recordStateCommand(DisplayListCommand cmd) {
|
||||
drawBarrier();
|
||||
currentRecorder.writeComplexCommand(cmd);
|
||||
}
|
||||
|
||||
public static void recordStateCommandIfChanged(DisplayListCommand cmd) {
|
||||
if (!currentRecorder.isLastCommand(cmd)) recordStateCommand(cmd);
|
||||
}
|
||||
|
||||
public static void recordStateCommandOnce(DisplayListCommand cmd) {
|
||||
if (currentRecorder.markRecordedOnce(cmd)) recordStateCommand(cmd);
|
||||
}
|
||||
|
||||
public static void recordIndexedDrawCapture(IndexedDrawCapture capture) {
|
||||
flushAll(); //TODO
|
||||
currentRecorder.writeIndexedDrawCapture(capture);
|
||||
@@ -575,12 +694,14 @@ public class DisplayListManager {
|
||||
public static void recordLoadMatrix(Matrix4f matrix) {
|
||||
drawBarrier();
|
||||
resetRelativeTransform();
|
||||
syncStreamMatrixMode(transformCallback.getMatrixMode());
|
||||
currentRecorder.writeLoadMatrix(matrix);
|
||||
}
|
||||
|
||||
public static void recordLoadIdentity() {
|
||||
drawBarrier();
|
||||
resetRelativeTransform();
|
||||
syncStreamMatrixMode(transformCallback.getMatrixMode());
|
||||
currentRecorder.writeLoadIdentity();
|
||||
|
||||
}
|
||||
@@ -618,11 +739,10 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
public static void applyMatrixTranslation(float x, float y, float z) {
|
||||
transformCallback.translate(x, y, z);
|
||||
relativeTransformType |= TRANSFORM_TRANSLATE;
|
||||
transformCallback.translate(x, y, z, TRANSFORM_TRANSLATE);
|
||||
|
||||
if (pendingTransformOps != null) {
|
||||
pendingTransformOps.add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z));
|
||||
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z));
|
||||
}
|
||||
|
||||
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
|
||||
@@ -631,11 +751,10 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
public static void applyMatrixScale(float x, float y, float z) {
|
||||
transformCallback.scale(x, y, z);
|
||||
relativeTransformType |= TRANSFORM_SCALE;
|
||||
transformCallback.scale(x, y, z, TRANSFORM_SCALE);
|
||||
|
||||
if (pendingTransformOps != null) {
|
||||
pendingTransformOps.add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z));
|
||||
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z));
|
||||
}
|
||||
|
||||
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
|
||||
@@ -649,11 +768,10 @@ public class DisplayListManager {
|
||||
* Requires the angle to be in radians & the coordinates to be normalized.
|
||||
*/
|
||||
public static void applyMatrixRotation(float rad, float x, float y, float z) {
|
||||
transformCallback.rotate(rad, x, y, z);
|
||||
relativeTransformType |= TRANSFORM_COMPLEX;
|
||||
transformCallback.rotate(rad, x, y, z, TRANSFORM_COMPLEX);
|
||||
|
||||
if (pendingTransformOps != null) {
|
||||
pendingTransformOps.add(String.format("glRotatef(%.4f, %.4f, %.4f, %.4f)", Math.toDegrees(rad), x, y, z));
|
||||
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glRotatef(%.4f, %.4f, %.4f, %.4f)", Math.toDegrees(rad), x, y, z));
|
||||
}
|
||||
|
||||
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
|
||||
@@ -671,11 +789,10 @@ public class DisplayListManager {
|
||||
* @param matrix The matrix to multiply
|
||||
*/
|
||||
public static void updateRelativeTransform(Matrix4f matrix) {
|
||||
transformCallback.multMatrix(matrix);
|
||||
relativeTransformType |= TRANSFORM_COMPLEX;
|
||||
transformCallback.multMatrix(matrix, TRANSFORM_COMPLEX);
|
||||
|
||||
if (pendingTransformOps != null) {
|
||||
pendingTransformOps.add("glMultMatrixf(...)");
|
||||
pendingTransformOps.get(transformCallback.getCurrentSlot()).add("glMultMatrixf(...)");
|
||||
}
|
||||
|
||||
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
|
||||
@@ -689,11 +806,10 @@ public class DisplayListManager {
|
||||
public static void updateRelativeTransformOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
orthoFrustumTemp.identity().ortho((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
|
||||
|
||||
transformCallback.multMatrix(orthoFrustumTemp);
|
||||
relativeTransformType |= TRANSFORM_COMPLEX;
|
||||
transformCallback.multMatrix(orthoFrustumTemp, TRANSFORM_COMPLEX);
|
||||
|
||||
if (pendingTransformOps != null) {
|
||||
pendingTransformOps.add(String.format("glOrtho(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
|
||||
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glOrtho(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
|
||||
}
|
||||
|
||||
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
|
||||
@@ -704,11 +820,10 @@ public class DisplayListManager {
|
||||
public static void updateRelativeTransformFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
orthoFrustumTemp.identity().frustum((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
|
||||
|
||||
transformCallback.multMatrix(orthoFrustumTemp);
|
||||
relativeTransformType |= TRANSFORM_COMPLEX;
|
||||
transformCallback.multMatrix(orthoFrustumTemp, TRANSFORM_COMPLEX);
|
||||
|
||||
if (pendingTransformOps != null) {
|
||||
pendingTransformOps.add(String.format("glFrustum(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
|
||||
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glFrustum(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
|
||||
}
|
||||
|
||||
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
|
||||
@@ -722,8 +837,8 @@ public class DisplayListManager {
|
||||
* subsequent transforms are relative to that loaded matrix (i.e., start from identity).
|
||||
*/
|
||||
static void resetRelativeTransform() {
|
||||
transformCallback.setIdentity();
|
||||
relativeTransformType = 0;
|
||||
transformCallback.reset(transformCallback.getCurrentSlot());
|
||||
if (pendingTransformOps != null) pendingTransformOps.get(transformCallback.getCurrentSlot()).clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -734,8 +849,11 @@ public class DisplayListManager {
|
||||
* @return true if the display list exists, false otherwise
|
||||
*/
|
||||
public static boolean displayListExists(int list) {
|
||||
if (displayListCache.containsKey(list)) {
|
||||
return true;
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
if (displayListCache.containsKey(list)) return true;
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
// Check VBOManager for GTNHLib compatibility (negative IDs)
|
||||
if (list < -1) {
|
||||
@@ -751,7 +869,12 @@ public class DisplayListManager {
|
||||
* @return The CompiledDisplayList, or null if not found
|
||||
*/
|
||||
public static CompiledDisplayList getDisplayList(int list) {
|
||||
return displayListCache.get(list);
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
return displayListCache.get(list);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -760,78 +883,113 @@ public class DisplayListManager {
|
||||
* GL_COMPILE: Commands are recorded only (not executed), GLSM cache unchanged
|
||||
* GL_COMPILE_AND_EXECUTE: Commands are recorded AND executed (GLSM cache updated)
|
||||
*/
|
||||
public static void glNewList(int list, int mode) {
|
||||
// Handle nested compilation - push current context onto stack
|
||||
public static synchronized void glNewList(int list, int mode) {
|
||||
final Thread recorder = recordingThread;
|
||||
if (recorder != null && recorder != Thread.currentThread()) {
|
||||
throw new IllegalStateException("glNewList(" + list + ") on " + Thread.currentThread().getName() + " while " + recorder.getName() + " is compiling a display list");
|
||||
}
|
||||
final boolean isNested = glListMode > 0;
|
||||
final int captureDepth = TessellatorManager.getDirectCaptureDepth();
|
||||
boolean pushed = false;
|
||||
if (!isNested) recordingThread = Thread.currentThread();
|
||||
try {
|
||||
if (isNested) {
|
||||
flushAll();
|
||||
final CompilationContext parentContext = new CompilationContext(
|
||||
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
|
||||
compilationStackTrace, recordedStatePushes, pendingTransformOps, multMatrixSources, drawRangeSources
|
||||
);
|
||||
compilationStack.push(parentContext);
|
||||
pushed = true;
|
||||
}
|
||||
|
||||
if (isNested) {
|
||||
flushAll();
|
||||
// Nested display list compilation violates OpenGL spec, but some of our optimizations require it
|
||||
// Save current compilation context and start fresh for nested list
|
||||
final CompilationContext parentContext = new CompilationContext(
|
||||
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
|
||||
compilationStackTrace, pendingTransformOps, multMatrixSources, drawRangeSources
|
||||
);
|
||||
compilationStack.push(parentContext);
|
||||
glListId = list;
|
||||
glListMode = mode;
|
||||
currentRecorder = new CommandRecorder();
|
||||
accumulatedDraws = new ArrayList<>(8);
|
||||
transformCallback = new DisplayListCallback();
|
||||
recordedStatePushes = 0;
|
||||
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
|
||||
|
||||
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
|
||||
pendingTransformOps = new ArrayList<>();
|
||||
for (int slot = 0; slot < DisplayListCallback.SLOT_COUNT; slot++) {
|
||||
pendingTransformOps.add(new ArrayList<>());
|
||||
}
|
||||
multMatrixSources = new ArrayList<>();
|
||||
drawRangeSources = new ArrayList<>();
|
||||
} else {
|
||||
pendingTransformOps = null;
|
||||
multMatrixSources = null;
|
||||
drawRangeSources = null;
|
||||
}
|
||||
|
||||
TessellatorManager.startCapturingDirect(transformCallback);
|
||||
} catch (RuntimeException | Error failure) {
|
||||
try {
|
||||
if (TessellatorManager.getDirectCaptureDepth() > captureDepth) TessellatorManager.stopCapturingDirect();
|
||||
} catch (RuntimeException | Error cleanup) {
|
||||
failure.addSuppressed(cleanup);
|
||||
}
|
||||
try {
|
||||
if (pushed) {
|
||||
final CommandRecorder parent = compilationStack.peek().recorder();
|
||||
try {
|
||||
if (currentRecorder != null && currentRecorder != parent) currentRecorder.delete();
|
||||
} finally {
|
||||
popCompilationContext();
|
||||
}
|
||||
} else if (!isNested) {
|
||||
try {
|
||||
if (currentRecorder != null) currentRecorder.delete();
|
||||
} finally {
|
||||
resetCompilationState();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException | Error cleanup) {
|
||||
failure.addSuppressed(cleanup);
|
||||
}
|
||||
throw failure;
|
||||
}
|
||||
|
||||
// Initialize fresh context for this (possibly nested) list
|
||||
glListId = list;
|
||||
glListMode = mode;
|
||||
recordingThread = Thread.currentThread(); // Track which thread is recording
|
||||
currentRecorder = new CommandRecorder(); // Create command recorder
|
||||
accumulatedDraws = new ArrayList<>(8); // Fewer draws than commands typically
|
||||
transformCallback = new DisplayListCallback();
|
||||
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
|
||||
|
||||
// Initialize debug logging fields (only when logging enabled)
|
||||
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
|
||||
pendingTransformOps = new ArrayList<>();
|
||||
multMatrixSources = new ArrayList<>();
|
||||
drawRangeSources = new ArrayList<>();
|
||||
} else {
|
||||
pendingTransformOps = null;
|
||||
multMatrixSources = null;
|
||||
drawRangeSources = null;
|
||||
}
|
||||
|
||||
TessellatorManager.startCapturingDirect(transformCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* End display list compilation and build optimized/unoptimized versions.
|
||||
*/
|
||||
public static void glEndList() {
|
||||
public static synchronized void glEndList() {
|
||||
final Thread recorder = recordingThread;
|
||||
if (recorder != null && recorder != Thread.currentThread()) {
|
||||
throw new IllegalStateException("glEndList on " + Thread.currentThread().getName() + " while " + recorder.getName() + " is compiling a display list");
|
||||
}
|
||||
if (glListMode == 0) {
|
||||
GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!");
|
||||
return;
|
||||
}
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_GL_LIST_COMPILE);
|
||||
try {
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
finishCurrentList();
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
// Non-null here means finishCurrentList threw before transferring or releasing
|
||||
// ownership of the recorder.
|
||||
if (currentRecorder != null) {
|
||||
currentRecorder.delete();
|
||||
try {
|
||||
if (currentRecorder != null) currentRecorder.delete();
|
||||
} finally {
|
||||
popCompilationContext();
|
||||
}
|
||||
// Must always run, even when compilation fails - a leaked context leaves
|
||||
// isRecording() true forever and every guarded GL call throws afterward.
|
||||
popCompilationContext();
|
||||
}
|
||||
}
|
||||
|
||||
private static void finishCurrentList() {
|
||||
// Stop compiling mode (works for both root and nested lists now)
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
boolean unbalancedStatePush = false;
|
||||
while (recordPopStateIfPending()) unbalancedStatePush = true;
|
||||
if (unbalancedStatePush) {
|
||||
GLStateManager.warnOnce("unbalanced-state-push", "Display list {} ended with unpopped internal state pushes - closing them", glListId);
|
||||
}
|
||||
|
||||
flushAll();
|
||||
|
||||
// Reset back to MODELVIEW (default state)
|
||||
if (transformCallback.getMatrixMode() != GL11.GL_MODELVIEW) {
|
||||
recordMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
transformCallback.setMatrixMode(GL11.GL_MODELVIEW);
|
||||
syncStreamMatrixMode(GL11.GL_MODELVIEW);
|
||||
|
||||
final CompiledDisplayList compiled;
|
||||
// Create CompiledDisplayList with both unoptimized and optimized versions
|
||||
@@ -841,7 +999,12 @@ public class DisplayListManager {
|
||||
if (hasCommands) {
|
||||
DisplayListVBO compiledBuffers = null;
|
||||
try {
|
||||
compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
|
||||
final CommandRecorder pausedVbo = pauseRecording();
|
||||
try {
|
||||
compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
|
||||
} finally {
|
||||
resumeRecording(pausedVbo);
|
||||
}
|
||||
final IndexedDrawBatchBuilder indexedBuilder = commandBuffer.getIndexedDraws();
|
||||
final List<IndexedDrawBatch> indexedBatches;
|
||||
if (indexedBuilder.isEmpty()) {
|
||||
@@ -862,7 +1025,7 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
final DisplayListCommand[] complexObjects = commandBuffer.getComplexObjects();
|
||||
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches);
|
||||
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches, GLStateManager.ctx());
|
||||
currentRecorder = null;
|
||||
} catch (Exception e) {
|
||||
GLStateManager.LOGGER.error("Encountered a fatal issue while trying to compile a display list.");
|
||||
@@ -911,6 +1074,7 @@ public class DisplayListManager {
|
||||
accumulatedDraws = parentContext.draws;
|
||||
transformCallback = parentContext.callback;
|
||||
compilationStackTrace = parentContext.stackTrace;
|
||||
recordedStatePushes = parentContext.recordedStatePushes;
|
||||
pendingTransformOps = parentContext.pendingOps;
|
||||
multMatrixSources = parentContext.matrixSources;
|
||||
drawRangeSources = parentContext.drawSources;
|
||||
@@ -926,7 +1090,6 @@ public class DisplayListManager {
|
||||
/** Clear all root-level compilation state back to "not recording". */
|
||||
private static void resetCompilationState() {
|
||||
currentRecorder = null;
|
||||
recordingThread = null;
|
||||
accumulatedDraws = null;
|
||||
pendingDraw = null;
|
||||
transformCallback = null;
|
||||
@@ -936,9 +1099,14 @@ public class DisplayListManager {
|
||||
drawRangeSources = null;
|
||||
glListId = -1;
|
||||
glListMode = 0;
|
||||
recordedStatePushes = 0;
|
||||
recordingThread = null;
|
||||
}
|
||||
|
||||
public static void abortCompilation() {
|
||||
public static synchronized void abortCompilation() {
|
||||
if (recordingThread != null && recordingThread != Thread.currentThread()) {
|
||||
throw new IllegalStateException("Cannot abort another thread's display list compilation");
|
||||
}
|
||||
if (glListMode == 0 && compilationStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -966,7 +1134,7 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
public static void abortIfLeaked() {
|
||||
if (glListMode != 0 || !compilationStack.isEmpty()) {
|
||||
if (Thread.currentThread() == recordingThread && (glListMode != 0 || !compilationStack.isEmpty())) {
|
||||
GLStateManager.warnOnce("frame-leak", "Display list {} left open across a frame boundary - aborting to recover", glListId);
|
||||
abortCompilation();
|
||||
}
|
||||
@@ -1002,7 +1170,7 @@ public class DisplayListManager {
|
||||
* Execute a compiled display list.
|
||||
*/
|
||||
public static void glCallList(int list) {
|
||||
if (currentRecorder != null) {
|
||||
if (isRecording()) {
|
||||
recordCallList(list);
|
||||
|
||||
if (getListMode() != GL11.GL_COMPILE) {
|
||||
@@ -1316,12 +1484,32 @@ public class DisplayListManager {
|
||||
|
||||
private static final class DisplayListCallback extends VertexTransformCallback {
|
||||
|
||||
static final int SLOT_VERTEX = 0; // GL_MODELVIEW + GL_PROJECTION, feeds into gl_Position
|
||||
static final int SLOT_TEXTURE = 1;
|
||||
static final int SLOT_COLOR = 2;
|
||||
static final int SLOT_OTHER = 3;
|
||||
static final int SLOT_COUNT = 4;
|
||||
|
||||
static int slotOf(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_MODELVIEW, GL11.GL_PROJECTION -> SLOT_VERTEX;
|
||||
case GL11.GL_TEXTURE -> SLOT_TEXTURE;
|
||||
case GL11.GL_COLOR -> SLOT_COLOR;
|
||||
default -> SLOT_OTHER;
|
||||
};
|
||||
}
|
||||
|
||||
private int matrixMode = GL11.GL_MODELVIEW;
|
||||
|
||||
private int streamMatrixMode;
|
||||
private boolean explicitMatrixMode;
|
||||
private final Deque<AttribState> attribStates = new ArrayDeque<>();
|
||||
|
||||
private final Matrix3f normalMatrix = new Matrix3f();
|
||||
|
||||
private final Matrix4f transformMatrix = new Matrix4f();
|
||||
private final Matrix4f[] transformMatrices = { new Matrix4f(), new Matrix4f(), new Matrix4f(), new Matrix4f() };
|
||||
private final int[] transformTypes = new int[SLOT_COUNT];
|
||||
private final int[] slotModes = { GL11.GL_MODELVIEW, GL11.GL_TEXTURE, GL11.GL_COLOR, 0 };
|
||||
|
||||
private DirectTessellator bakedOwner;
|
||||
private int bakedVertices;
|
||||
@@ -1333,49 +1521,69 @@ public class DisplayListManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void scale(float x, float y, float z) {
|
||||
transformMatrix.scale(x, y, z);
|
||||
private Matrix4f begin(int typeBits) {
|
||||
final int slot = getCurrentSlot();
|
||||
transformTypes[slot] |= typeBits;
|
||||
slotModes[slot] = matrixMode;
|
||||
return transformMatrices[slot];
|
||||
}
|
||||
|
||||
public void scale(float x, float y, float z, int typeBits) {
|
||||
begin(typeBits).scale(x, y, z);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public void translate(float x, float y, float z) {
|
||||
transformMatrix.translate(x, y, z);
|
||||
public void translate(float x, float y, float z, int typeBits) {
|
||||
begin(typeBits).translate(x, y, z);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public void rotate(float rad, float x, float y, float z) {
|
||||
transformMatrix.rotate(rad, x, y, z);
|
||||
public void rotate(float rad, float x, float y, float z, int typeBits) {
|
||||
begin(typeBits).rotate(rad, x, y, z);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public void multMatrix(Matrix4f matrix4f) {
|
||||
transformMatrix.mul(matrix4f);
|
||||
public void multMatrix(Matrix4f matrix4f, int typeBits) {
|
||||
begin(typeBits).mul(matrix4f);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public void setIdentity() {
|
||||
transformMatrix.identity();
|
||||
markDirty();
|
||||
public void reset(int slot) {
|
||||
transformMatrices[slot].identity();
|
||||
transformTypes[slot] = 0;
|
||||
if (slot == SLOT_VERTEX) {
|
||||
markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3f getScale(Vector3f dest) {
|
||||
return transformMatrix.getScale(dest);
|
||||
public Vector3f getScale(int slot, Vector3f dest) {
|
||||
final Matrix4f matrix = transformMatrices[slot];
|
||||
return dest.set(matrix.m00(), matrix.m11(), matrix.m22());
|
||||
}
|
||||
|
||||
public Vector3f getTranslation(Vector3f dest) {
|
||||
return transformMatrix.getTranslation(dest);
|
||||
public Vector3f getTranslation(int slot, Vector3f dest) {
|
||||
return transformMatrices[slot].getTranslation(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: If you apply transformations to this matrix, make sure to call markDirty() afterward!
|
||||
*/
|
||||
public Matrix4f getReadMatrix() {
|
||||
return transformMatrix;
|
||||
public Matrix4f getReadMatrix(int slot) {
|
||||
return transformMatrices[slot];
|
||||
}
|
||||
|
||||
public int getTransformType(int slot) {
|
||||
return transformTypes[slot];
|
||||
}
|
||||
|
||||
/** The GL matrix target a slot's pending delta belongs to. */
|
||||
public int getSlotMatrixMode(int slot) {
|
||||
return slotModes[slot];
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
if (matrixMode == GL11.GL_MODELVIEW) {
|
||||
NormalHelper.getNormalMatrix(transformMatrix, normalMatrix);
|
||||
NormalHelper.getNormalMatrix(transformMatrices[SLOT_VERTEX], normalMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1384,15 +1592,26 @@ public class DisplayListManager {
|
||||
*/
|
||||
public void setMatrixMode(int mode) {
|
||||
this.matrixMode = mode;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public int getMatrixMode() {
|
||||
return this.matrixMode;
|
||||
}
|
||||
|
||||
public boolean isIdentity() {
|
||||
return MatrixHelper.isIdentity(transformMatrix);
|
||||
public int getCurrentSlot() {
|
||||
return slotOf(matrixMode);
|
||||
}
|
||||
|
||||
public int getStreamMatrixMode() {
|
||||
return this.streamMatrixMode;
|
||||
}
|
||||
|
||||
public void setStreamMatrixMode(int mode) {
|
||||
this.streamMatrixMode = mode;
|
||||
}
|
||||
|
||||
public boolean isIdentity(int slot) {
|
||||
return MatrixHelper.isIdentity(transformMatrices[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1405,10 +1624,12 @@ public class DisplayListManager {
|
||||
@Override
|
||||
public boolean onDraw(CallbackTessellator tessellator) {
|
||||
if (!tessellator.isEmpty()) {
|
||||
if (!isIdentity()) {
|
||||
if (!isIdentity(SLOT_VERTEX) || !isIdentity(SLOT_TEXTURE) || !isIdentity(SLOT_COLOR)) {
|
||||
final int baked = bakedOwner == tessellator ? bakedVertices : 0;
|
||||
if (bakedVertices == 0) {
|
||||
flushMatrix();
|
||||
flushMatrix(SLOT_VERTEX);
|
||||
flushMatrix(SLOT_TEXTURE);
|
||||
flushMatrix(SLOT_COLOR);
|
||||
} else if (baked != tessellator.vertexCount) {
|
||||
GLStateManager.warnOnce("dl-mixed-baked-raw", "Display list {}: draw of {} vertices carries {} transformed ones - the rest lose the pending transform", glListId, tessellator.vertexCount, baked);
|
||||
}
|
||||
@@ -1432,22 +1653,22 @@ public class DisplayListManager {
|
||||
|
||||
@Override
|
||||
protected Matrix4f getMVPMatrix() {
|
||||
return matrixMode <= GL11.GL_PROJECTION ? transformMatrix : null;
|
||||
return transformTypes[SLOT_VERTEX] != 0 ? transformMatrices[SLOT_VERTEX] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matrix4f getTextureMatrix() {
|
||||
return matrixMode == GL11.GL_TEXTURE ? transformMatrix : null;
|
||||
return transformTypes[SLOT_TEXTURE] != 0 ? transformMatrices[SLOT_TEXTURE] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matrix4f getColorMatrix() {
|
||||
return matrixMode == GL11.GL_COLOR ? transformMatrix : null;
|
||||
return transformTypes[SLOT_COLOR] != 0 ? transformMatrices[SLOT_COLOR] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matrix3f getNormalMatrix() {
|
||||
return matrixMode == GL11.GL_MODELVIEW ? normalMatrix : null; // GL_PROJECTION doesn't transform normals
|
||||
return transformTypes[SLOT_VERTEX] != 0 && slotModes[SLOT_VERTEX] == GL11.GL_MODELVIEW ? normalMatrix : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,13 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Feature {
|
||||
@@ -23,52 +18,49 @@ public class Feature {
|
||||
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
|
||||
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
|
||||
|
||||
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
|
||||
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final long[] NO_MEMBERS = new long[0];
|
||||
|
||||
static List<IStateStack<?>> maskToFeatures(int mask) {
|
||||
if(maskToFeaturesMap.containsKey(mask)) {
|
||||
return maskToFeaturesMap.get(mask);
|
||||
}
|
||||
private static final Int2ObjectMap<long[]> maskToMembersMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
final Set<IStateStack<?>> features = new HashSet<>();
|
||||
|
||||
for(int attrib : Feature.supportedAttribs) {
|
||||
if((mask & attrib) == attrib) {
|
||||
features.addAll(getFeatures(attrib));
|
||||
}
|
||||
}
|
||||
|
||||
final List<IStateStack<?>> asList = new ArrayList<>(features);
|
||||
|
||||
maskToFeaturesMap.put(mask, asList);
|
||||
return asList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only non-BooleanStateStack instances for the given mask.
|
||||
* These use traditional push/pop without global depth tracking.
|
||||
*/
|
||||
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
|
||||
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
|
||||
static long[] maskToMembers(int mask) {
|
||||
final long[] cached = maskToMembersMap.get(mask);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final List<IStateStack<?>> all = maskToFeatures(mask);
|
||||
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
|
||||
for (int i = 0; i < all.size(); i++) {
|
||||
final IStateStack<?> stack = all.get(i);
|
||||
if (!(stack instanceof BooleanStateStack)) {
|
||||
nonBooleans.add(stack);
|
||||
long[] result = NO_MEMBERS;
|
||||
for (int attrib : Feature.supportedAttribs) {
|
||||
if ((mask & attrib) == attrib) {
|
||||
result = union(result, attribToMembers.get(attrib));
|
||||
}
|
||||
}
|
||||
cached = nonBooleans.toArray(new IStateStack<?>[0]);
|
||||
maskToNonBooleanStacksMap.put(mask, cached);
|
||||
return cached;
|
||||
|
||||
maskToMembersMap.put(mask, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
|
||||
private static long[] union(long[] a, long[] b) {
|
||||
if (b == null || b.length == 0) return a;
|
||||
if (a.length == 0) return b.clone();
|
||||
final long[] result = new long[Math.max(a.length, b.length)];
|
||||
System.arraycopy(a, 0, result, 0, a.length);
|
||||
for (int i = 0; i < b.length; i++) result[i] |= b[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long[] toMembers(Set<IStateStack<?>> stacks) {
|
||||
if (stacks.isEmpty()) return NO_MEMBERS;
|
||||
int max = 0;
|
||||
for (IStateStack<?> s : stacks) max = Math.max(max, ((CowStateStack<?>) s).stackId());
|
||||
final long[] bits = new long[(max >> 6) + 1];
|
||||
for (IStateStack<?> s : stacks) {
|
||||
final int id = ((CowStateStack<?>) s).stackId();
|
||||
bits[id >> 6] |= 1L << id;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
private static final Int2ObjectMap<long[]> attribToMembers = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
/**
|
||||
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
|
||||
@@ -84,6 +76,8 @@ public class Feature {
|
||||
}
|
||||
|
||||
static {
|
||||
final Int2ObjectMap<Set<IStateStack<?>>> attribToFeatures = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
|
||||
GLStateManager.getAlphaTest()
|
||||
, GLStateManager.getAlphaState()
|
||||
@@ -99,8 +93,10 @@ public class Feature {
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
|
||||
GLStateManager.getColor()
|
||||
, GLStateManager.getSecondaryColor() // Current secondary color
|
||||
, ShaderManager.getNormalStack() // Current normal vector
|
||||
, ShaderManager.getTexCoordStack() // Current texture coordinates
|
||||
, GLStateManager.getEdgeFlagState()
|
||||
// Current color index
|
||||
// Current raster position
|
||||
// GL_CURRENT_RASTER_POSITION_VALID flag
|
||||
@@ -122,6 +118,7 @@ public class Feature {
|
||||
, GLStateManager.getBlendMode()
|
||||
, GLStateManager.getColorLogicOpState()
|
||||
, GLStateManager.getColorMaterial()
|
||||
, GLStateManager.getColorSumState()
|
||||
, GLStateManager.getCullState()
|
||||
, GLStateManager.getDepthTest()
|
||||
, GLStateManager.getDitherState()
|
||||
@@ -162,6 +159,8 @@ public class Feature {
|
||||
, GLStateManager.getPolygonOffsetPointState()
|
||||
, GLStateManager.getPolygonOffsetLineState()
|
||||
, GLStateManager.getPolygonOffsetFillState()
|
||||
, GLStateManager.getPointSpriteState()
|
||||
, GLStateManager.getVertexProgramTwoSideState()
|
||||
, GLStateManager.getPolygonSmoothState()
|
||||
, GLStateManager.getPolygonStippleState()
|
||||
, GLStateManager.getRescaleNormalState()
|
||||
@@ -182,6 +181,7 @@ public class Feature {
|
||||
enableBits.add(GLStateManager.getTextures().getTexture1DStates(i));
|
||||
enableBits.add(GLStateManager.getTextures().getTextureUnitStates(i));
|
||||
enableBits.add(GLStateManager.getTextures().getTexture3DStates(i));
|
||||
enableBits.add(GLStateManager.getTextures().getTextureCubeMapStates(i));
|
||||
}
|
||||
|
||||
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
@@ -189,14 +189,29 @@ public class Feature {
|
||||
|
||||
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
|
||||
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
|
||||
// GL_MAP1_x enable bits, where x is a map type
|
||||
// GL_MAP2_x enable bits, where x is a map type
|
||||
// 1D grid endpoints and divisions
|
||||
// 2D grid endpoints and divisions
|
||||
// GL_AUTO_NORMAL enable bit
|
||||
GLStateManager.getAutoNormalState()
|
||||
, GLStateManager.getMap1Color4State()
|
||||
, GLStateManager.getMap1IndexState()
|
||||
, GLStateManager.getMap1NormalState()
|
||||
, GLStateManager.getMap1TextureCoord1State()
|
||||
, GLStateManager.getMap1TextureCoord2State()
|
||||
, GLStateManager.getMap1TextureCoord3State()
|
||||
, GLStateManager.getMap1TextureCoord4State()
|
||||
, GLStateManager.getMap1Vertex3State()
|
||||
, GLStateManager.getMap1Vertex4State()
|
||||
, GLStateManager.getMap2Color4State()
|
||||
, GLStateManager.getMap2IndexState()
|
||||
, GLStateManager.getMap2NormalState()
|
||||
, GLStateManager.getMap2TextureCoord1State()
|
||||
, GLStateManager.getMap2TextureCoord2State()
|
||||
, GLStateManager.getMap2TextureCoord3State()
|
||||
, GLStateManager.getMap2TextureCoord4State()
|
||||
, GLStateManager.getMap2Vertex3State()
|
||||
, GLStateManager.getMap2Vertex4State()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
|
||||
GLStateManager.getFogMode()
|
||||
, GLStateManager.getColorSumState()
|
||||
, GLStateManager.getFogState()
|
||||
// ^^ Fog density
|
||||
// ^^ Linear fog start
|
||||
@@ -205,13 +220,7 @@ public class Feature {
|
||||
// ^^ GL_FOG_MODE value
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_HINT_BIT, ImmutableSet.of(
|
||||
// GL_PERSPECTIVE_CORRECTION_HINT setting
|
||||
// GL_POINT_SMOOTH_HINT setting
|
||||
// GL_LINE_SMOOTH_HINT setting
|
||||
// GL_POLYGON_SMOOTH_HINT setting
|
||||
// GL_FOG_HINT setting
|
||||
// GL_GENERATE_MIPMAP_HINT setting
|
||||
// GL_TEXTURE_COMPRESSION_HINT setting
|
||||
GLStateManager.getHintState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of(
|
||||
GLStateManager.getColorMaterial()
|
||||
@@ -255,10 +264,10 @@ public class Feature {
|
||||
// GL_LIST_BASE setting
|
||||
));
|
||||
attribToFeatures.put(GL13.GL_MULTISAMPLE_BIT, ImmutableSet.of(
|
||||
// GL_MULTISAMPLE enable bit
|
||||
// GL_SAMPLE_ALPHA_TO_COVERAGE flag
|
||||
// GL_SAMPLE_ALPHA_TO_ONE flag
|
||||
// GL_SAMPLE_COVERAGE flag
|
||||
GLStateManager.getMultisampleState()
|
||||
, GLStateManager.getSampleAlphaToCoverageState()
|
||||
, GLStateManager.getSampleAlphaToOneState()
|
||||
, GLStateManager.getSampleCoverageState()
|
||||
// GL_SAMPLE_COVERAGE_VALUE value
|
||||
// GL_SAMPLE_COVERAGE_INVERT value
|
||||
));
|
||||
@@ -275,6 +284,7 @@ public class Feature {
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
|
||||
GLStateManager.getPointSmoothState()
|
||||
, GLStateManager.getPointSpriteState()
|
||||
, GLStateManager.getPointState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
|
||||
@@ -291,7 +301,7 @@ public class Feature {
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
|
||||
GLStateManager.getScissorTest()
|
||||
// Scissor box
|
||||
, GLStateManager.getScissorState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
|
||||
GLStateManager.getStencilTest()
|
||||
@@ -317,6 +327,10 @@ public class Feature {
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
textureAttribs.add(GLStateManager.getTextures().getTextureUnitBindings(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTexEnvState(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTexture1DStates(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTextureUnitStates(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTexture3DStates(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTextureCubeMapStates(i));
|
||||
}
|
||||
|
||||
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
@@ -324,22 +338,24 @@ public class Feature {
|
||||
|
||||
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
|
||||
|
||||
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
|
||||
final Set<IStateStack<?>> transformAttribs = new HashSet<>(ImmutableSet.of(
|
||||
// Coefficients of the six clipping planes
|
||||
|
||||
|
||||
GLStateManager.getMatrixMode()
|
||||
, GLStateManager.getNormalizeState()
|
||||
, GLStateManager.getRescaleNormalState()
|
||||
));
|
||||
for(int i = 0; i < GLStateManager.getClipPlaneStates().length; i++) {
|
||||
transformAttribs.add(GLStateManager.getClipPlaneStates()[i]);
|
||||
}
|
||||
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, transformAttribs);
|
||||
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
|
||||
|
||||
|
||||
GLStateManager.getViewportState()
|
||||
));
|
||||
|
||||
for (Int2ObjectMap.Entry<Set<IStateStack<?>>> entry : attribToFeatures.int2ObjectEntrySet()) {
|
||||
attribToMembers.put(entry.getIntKey(), toMembers(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<IStateStack<?>> getFeatures(int attrib) {
|
||||
return attribToFeatures.getOrDefault(attrib, Collections.emptySet());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -48,6 +48,10 @@ public class FeedbackManager {
|
||||
return renderMode == GL11.GL_FEEDBACK;
|
||||
}
|
||||
|
||||
public static int getRenderMode() {
|
||||
return renderMode;
|
||||
}
|
||||
|
||||
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
|
||||
@@ -126,30 +130,34 @@ public class FeedbackManager {
|
||||
feedbackVertex(winX, winY, winZ, winW);
|
||||
}
|
||||
|
||||
private static int positionStride() {
|
||||
final int s = VAOManager.getAttribEffectiveStride(0);
|
||||
private static int positionStride(GLContextState glCtx) {
|
||||
final int s = glCtx.vaos.attribEffectiveStride(0);
|
||||
return (s != 0) ? s : 12;
|
||||
}
|
||||
|
||||
private static long positionOffset() {
|
||||
return VAOManager.getAttribOffset(0);
|
||||
private static long positionOffset(GLContextState glCtx) {
|
||||
return glCtx.vaos.attribOffset(0);
|
||||
}
|
||||
|
||||
public static void processDrawArrays(int mode, int first, int count) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
final long posOffset = positionOffset();
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (!glCtx.vaos.isAttribEnabled(0)) return;
|
||||
final int stride = positionStride(glCtx);
|
||||
final long posOffset = positionOffset(glCtx);
|
||||
final int byteCount = count * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
readPositionBuffer((long) first * stride + posOffset, readbackBuf);
|
||||
readPositionBuffer((long) first * stride + posOffset, readbackBuf, glCtx);
|
||||
|
||||
emitAllPrimitives(mode, count, i -> i, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (!glCtx.vaos.isAttribEnabled(0)) return;
|
||||
final int stride = positionStride(glCtx);
|
||||
|
||||
final int elementSize = GLTypes.sizeBytes(type);
|
||||
final int indexByteCount = indexCount * elementSize;
|
||||
@@ -165,12 +173,12 @@ public class FeedbackManager {
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
final long posOffset = positionOffset();
|
||||
final long posOffset = positionOffset(glCtx);
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
|
||||
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
|
||||
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
|
||||
@@ -204,7 +212,9 @@ public class FeedbackManager {
|
||||
|
||||
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (!glCtx.vaos.isAttribEnabled(0)) return;
|
||||
final int stride = positionStride(glCtx);
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -213,7 +223,7 @@ public class FeedbackManager {
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
readVerticesFromVBO(minIdx, maxIdx, stride);
|
||||
readVerticesFromVBO(minIdx, maxIdx, stride, glCtx);
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
|
||||
}
|
||||
@@ -245,18 +255,26 @@ public class FeedbackManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
|
||||
final long posOffset = positionOffset();
|
||||
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride, GLContextState glCtx) {
|
||||
final long posOffset = positionOffset(glCtx);
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
|
||||
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
|
||||
}
|
||||
|
||||
private static void readPositionBuffer(long offset, ByteBuffer destination) {
|
||||
final int positionVbo = VAOManager.getAttribVBO(0);
|
||||
final int previousVbo = GLStateManager.getBoundVBO();
|
||||
private static void readPositionBuffer(long offset, ByteBuffer destination, GLContextState glCtx) {
|
||||
final VAOManager.Attrib position = glCtx.vaos.attrib(0);
|
||||
if (position != null && position.clientPointer != null) {
|
||||
final ByteBuffer source = position.clientPointer.duplicate();
|
||||
source.position(source.position() + (int) offset);
|
||||
if (source.remaining() > destination.remaining()) source.limit(source.position() + destination.remaining());
|
||||
destination.duplicate().put(source);
|
||||
return;
|
||||
}
|
||||
final int positionVbo = glCtx.vaos.attribVBO(0);
|
||||
final int previousVbo = glCtx.boundVBO;
|
||||
|
||||
if (positionVbo != 0 && positionVbo != previousVbo) {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, positionVbo);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Color4Stack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ColorMaskStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowDepths;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowDispatch;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.DepthStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.IntegerStateStack;
|
||||
@@ -16,31 +21,56 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntStack;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingVaos;
|
||||
import org.joml.Matrix4d;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class GLContextState {
|
||||
public class GLContextState {
|
||||
private final List<IStateStack<?>> allStacksBuilder = new ArrayList<>();
|
||||
public IStateStack<?>[] allStacks;
|
||||
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
|
||||
|
||||
private BooleanStateStack track(BooleanStateStack s) {
|
||||
s.setStackId(StackIdAllocator.nextId());
|
||||
allBooleanStates.add(s);
|
||||
allStacksBuilder.add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
private <T extends IStateStack<?>> T member(T s) {
|
||||
allStacksBuilder.add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -61,6 +91,7 @@ class GLContextState {
|
||||
public int colorGeneration;
|
||||
// clip plane equation changes
|
||||
public int clipPlaneGeneration;
|
||||
public int clipPlaneEnabledMask;
|
||||
public float overlayR = 0.0f;
|
||||
public float overlayG = 0.0f;
|
||||
public float overlayB = 0.0f;
|
||||
@@ -74,13 +105,31 @@ class GLContextState {
|
||||
public boolean dirtyNormalAttrib;
|
||||
public boolean dirtyTexCoordAttrib;
|
||||
public boolean dirtyLightmapAttrib = true;
|
||||
public long entityIds = InstancedAttribs.DEFAULT_ENTITY_IDS;
|
||||
public boolean dirtyEntityIdsAttrib = true;
|
||||
public boolean unit23TexCoordSetDuringDraw = false;
|
||||
public int maxBoundTextureUnit = 0;
|
||||
public int maxBoundImageUnit = 0;
|
||||
public final IntStack attribs = new IntArrayList(GLStateManager.MAX_ATTRIB_STACK_DEPTH);
|
||||
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public RetainedState retainedOwner;
|
||||
public int attribDepth = 0;
|
||||
@SuppressWarnings("unchecked")
|
||||
public final List<IStateStack<?>>[] modifiedAtDepth = new List[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
|
||||
public boolean savesState(int id) {
|
||||
for (int i = attribDepth - 1; i >= 0; i--) {
|
||||
if (attribSets[i].contains(id)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int batchStateGeneration;
|
||||
public final int[] savedBatchStateGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public CowStateStack<?>[] stackById;
|
||||
public CowDepths[] depthsById;
|
||||
public int[] restoreBitById;
|
||||
public int[] restoreUnitById;
|
||||
public byte[] kindById;
|
||||
public int[][] modifiedIds;
|
||||
public int[] modifiedCount;
|
||||
public final int[] savedMvGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedMvLinearGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedProjGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
@@ -90,25 +139,31 @@ class GLContextState {
|
||||
public final int[] savedColorGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedNormalGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedTexCoordGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final IntegerStateStack activeTextureUnit = new IntegerStateStack(0);
|
||||
public final IntegerStateStack activeTextureUnit = member(new IntegerStateStack(0, StackIdAllocator.nextId()).restoreBit(StateSet.R_ACTIVE_UNIT));
|
||||
public int clientActiveTextureUnit = 0;
|
||||
public final IntegerStateStack shadeModelState = new IntegerStateStack(GL11.GL_SMOOTH);
|
||||
public final IntegerStateStack shadeModelState = member(new IntegerStateStack(GL11.GL_SMOOTH, StackIdAllocator.nextId()));
|
||||
public final TextureUnitArray textures = new TextureUnitArray();
|
||||
public final ImageUnitArray imageUnits = new ImageUnitArray();
|
||||
public final BlendStateStack blendState = new BlendStateStack();
|
||||
public final SamplerUnitArray samplerUnits = new SamplerUnitArray();
|
||||
public final BlendStateStack blendState = member(new BlendStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
|
||||
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
|
||||
public final DepthStateStack depthState = new DepthStateStack();
|
||||
public final BooleanStateStack rasterizerDiscard = track(new BooleanStateStack(GL30.GL_RASTERIZER_DISCARD));
|
||||
public final DepthStateStack depthState = member(new DepthStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
|
||||
public final FogStateStack fogState = new FogStateStack();
|
||||
public final FogStateStack fogState = member(new FogStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack fogMode = track(new BooleanStateStack(GL11.GL_FOG, false, true));
|
||||
public final Color4Stack color = new Color4Stack();
|
||||
public final Color4Stack clearColor = new Color4Stack(new Color4(0.0F, 0.0F, 0.0F, 0.0F));
|
||||
public final ColorMaskStack colorMask = new ColorMaskStack();
|
||||
public final IntegerStateStack drawBuffer = new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER);
|
||||
public final IntegerStateStack logicOpMode = new IntegerStateStack(GL11.GL_COPY);
|
||||
public final Color4Stack color = member(new Color4Stack(StackIdAllocator.nextId()));
|
||||
// GL_CURRENT_SECONDARY_COLOR - alpha is always 1 and unused, the color sum only adds RGB
|
||||
public final Color4Stack secondaryColor = member(new Color4Stack(StackIdAllocator.nextId(), new Color4(0.0F, 0.0F, 0.0F, 1.0F)).restoreBit(StateSet.R_SECONDARY_COLOR));
|
||||
public final BooleanStateStack colorSumState = track(new BooleanStateStack(GL14.GL_COLOR_SUM, false, true));
|
||||
public final Color4Stack clearColor = member(new Color4Stack(StackIdAllocator.nextId(), new Color4(0.0F, 0.0F, 0.0F, 0.0F)).restoreBit(StateSet.R_CLEAR_COLOR));
|
||||
public final ColorMaskStack colorMask = member(new ColorMaskStack(StackIdAllocator.nextId()));
|
||||
public final IntegerStateStack drawBuffer = member(new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER, StackIdAllocator.nextId()).restoreBit(StateSet.R_DRAW_BUFFER));
|
||||
public final IntegerStateStack logicOpMode = member(new IntegerStateStack(GL11.GL_COPY, StackIdAllocator.nextId()).restoreBit(StateSet.R_LOGIC_OP));
|
||||
public final BooleanStateStack cullState = track(new BooleanStateStack(GL11.GL_CULL_FACE));
|
||||
public final AlphaStateStack alphaState = new AlphaStateStack();
|
||||
public final AlphaState restoredAlphaScratch = new AlphaState();
|
||||
public final AlphaStateStack alphaState = member(new AlphaStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack alphaTest = track(new BooleanStateStack(GL11.GL_ALPHA_TEST, false, true));
|
||||
public final BooleanStateStack lightingState = track(new BooleanStateStack(GL11.GL_LIGHTING, false, true));
|
||||
public final BooleanStateStack rescaleNormalState = track(new BooleanStateStack(GL12.GL_RESCALE_NORMAL, false, true));
|
||||
@@ -118,6 +173,9 @@ class GLContextState {
|
||||
public final BooleanStateStack lineSmoothState = track(new BooleanStateStack(GL11.GL_LINE_SMOOTH));
|
||||
public final BooleanStateStack lineStippleState = track(new BooleanStateStack(GL11.GL_LINE_STIPPLE, false, true));
|
||||
public final BooleanStateStack pointSmoothState = track(new BooleanStateStack(GL11.GL_POINT_SMOOTH, false, true));
|
||||
public final BooleanStateStack pointSpriteState = track(new BooleanStateStack(GL20.GL_POINT_SPRITE, false, true));
|
||||
public final BooleanStateStack vertexProgramTwoSideState = track(new BooleanStateStack(GL20.GL_VERTEX_PROGRAM_TWO_SIDE, false, true));
|
||||
public final BooleanStateStack edgeFlagState = track(new BooleanStateStack(GL11.GL_EDGE_FLAG, true, true));
|
||||
public final BooleanStateStack polygonSmoothState = track(new BooleanStateStack(GL11.GL_POLYGON_SMOOTH));
|
||||
public final BooleanStateStack polygonStippleState = track(new BooleanStateStack(GL11.GL_POLYGON_STIPPLE, false, true));
|
||||
public final BooleanStateStack multisampleState = track(new BooleanStateStack(GL13.GL_MULTISAMPLE, true));
|
||||
@@ -129,10 +187,11 @@ class GLContextState {
|
||||
public final BooleanStateStack polygonOffsetPointState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_POINT));
|
||||
public final BooleanStateStack polygonOffsetLineState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_LINE));
|
||||
public final BooleanStateStack polygonOffsetFillState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_FILL));
|
||||
public final LineStateStack lineState = new LineStateStack();
|
||||
public final PointStateStack pointState = new PointStateStack();
|
||||
public final PolygonStateStack polygonState = new PolygonStateStack();
|
||||
public final StencilStateStack stencilState = new StencilStateStack();
|
||||
public final LineStateStack lineState = member(new LineStateStack(StackIdAllocator.nextId()));
|
||||
public final PointStateStack pointState = member(new PointStateStack(StackIdAllocator.nextId()));
|
||||
public final IntegerStateStack hintState = member(new IntegerStateStack(0, StackIdAllocator.nextId()).restoreBit(StateSet.R_HINT));
|
||||
public final PolygonStateStack polygonState = member(new PolygonStateStack(StackIdAllocator.nextId()));
|
||||
public final StencilStateStack stencilState = member(new StencilStateStack(StackIdAllocator.nextId()));
|
||||
public int stencilBitMask = 0xFFFFFFFF;
|
||||
public final BooleanStateStack autoNormalState = track(new BooleanStateStack(GL11.GL_AUTO_NORMAL, false, true));
|
||||
public final BooleanStateStack map1Color4State = track(new BooleanStateStack(GL11.GL_MAP1_COLOR_4, false, true));
|
||||
@@ -157,47 +216,113 @@ class GLContextState {
|
||||
public final ClipPlaneState clipPlaneState = new ClipPlaneState();
|
||||
public final FloatBuffer queryScratch = BufferUtils.createFloatBuffer(16);
|
||||
public final DoubleBuffer queryScratchDouble = BufferUtils.createDoubleBuffer(16);
|
||||
public final MatrixModeStack matrixMode = new MatrixModeStack();
|
||||
public final MatrixModeStack matrixMode = member(new MatrixModeStack(StackIdAllocator.nextId()));
|
||||
public final Matrix4fStack modelViewMatrix = new Matrix4fStack(GLStateManager.MAX_MODELVIEW_STACK_DEPTH);
|
||||
public final Matrix4fStack projectionMatrix = new Matrix4fStack(GLStateManager.MAX_PROJECTION_STACK_DEPTH);
|
||||
public final BooleanStateStack[] lightStates = new BooleanStateStack[8];
|
||||
public final LightStateStack[] lightDataStates = new LightStateStack[8];
|
||||
public final BooleanStateStack colorMaterial = track(new BooleanStateStack(GL11.GL_COLOR_MATERIAL, false, true));
|
||||
public final IntegerStateStack colorMaterialFace = new IntegerStateStack(GL11.GL_FRONT_AND_BACK);
|
||||
public final IntegerStateStack colorMaterialParameter = new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE);
|
||||
public final LightModelStateStack lightModel = new LightModelStateStack();
|
||||
public final MaterialStateStack frontMaterial = new MaterialStateStack(GL11.GL_FRONT);
|
||||
public final MaterialStateStack backMaterial = new MaterialStateStack(GL11.GL_BACK);
|
||||
public final ViewPortStateStack viewportState = new ViewPortStateStack();
|
||||
public final IntegerStateStack colorMaterialFace = member(new IntegerStateStack(GL11.GL_FRONT_AND_BACK, StackIdAllocator.nextId()));
|
||||
public final IntegerStateStack colorMaterialParameter = member(new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE, StackIdAllocator.nextId()));
|
||||
public final LightModelStateStack lightModel = member(new LightModelStateStack(StackIdAllocator.nextId()));
|
||||
public final MaterialStateStack frontMaterial = member(new MaterialStateStack(GL11.GL_FRONT, StackIdAllocator.nextId()));
|
||||
public final MaterialStateStack backMaterial = member(new MaterialStateStack(GL11.GL_BACK, StackIdAllocator.nextId()));
|
||||
public final ViewPortStateStack viewportState = member(new ViewPortStateStack(StackIdAllocator.nextId()));
|
||||
public final ScissorStateStack scissorState = member(new ScissorStateStack(StackIdAllocator.nextId()));
|
||||
public int activeProgram = 0;
|
||||
public final IntegerStateStack programStack = member(new IntegerStateStack(0, StackIdAllocator.nextId()));
|
||||
public int listBase = 0;
|
||||
public int boundVBO;
|
||||
public int boundVAO;
|
||||
public int boundPixelUnpackBuffer;
|
||||
public int boundPixelPackBuffer;
|
||||
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
|
||||
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public int boundCopyReadBuffer;
|
||||
public int boundCopyWriteBuffer;
|
||||
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
|
||||
public final int[] uniformBufferBindings = new int[GLStateManager.INDEXED_BUFFER_SLOTS];
|
||||
public final int[] storageBufferBindings = new int[GLStateManager.INDEXED_BUFFER_SLOTS];
|
||||
public PixelStoreState pixelUnpackState = PixelStoreState.DEFAULT;
|
||||
public PixelStoreState pixelPackState = PixelStoreState.DEFAULT;
|
||||
public final int[] clientAttribSavedMask = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final PixelStoreState[] clientAttribSavedPixelUnpack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final PixelStoreState[] clientAttribSavedPixelPack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final int[] clientAttribSavedUnpackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final int[] clientAttribSavedPackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final VAOManager.ClientArrays[] clientAttribSavedArrays = new VAOManager.ClientArrays[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public int clientAttribStackPointer = 0;
|
||||
public final boolean[] restoreUnitChanged = new boolean[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged;
|
||||
public int restoreChangedMask;
|
||||
public long restoreUnitChangedMask;
|
||||
public final BlendState vanillaBlendBefore = new BlendState();
|
||||
public final BlendState vanillaBlendAfter = new BlendState();
|
||||
public boolean vanillaBlendEnabledBefore;
|
||||
public int drawFramebuffer = 0;
|
||||
public int readFramebuffer = 0;
|
||||
public int texGenGeneration;
|
||||
public int defaultVAO;
|
||||
public final VAOManager vaos = new VAOManager();
|
||||
public final StreamingVaos streamingVaos = new StreamingVaos();
|
||||
public int fontVao;
|
||||
public boolean lockBindCallback;
|
||||
public int foreignDrawDepth;
|
||||
public int internalGlDepth;
|
||||
public int internalDrawDepth;
|
||||
public int savedFilterTextureId;
|
||||
public int savedFilterMin = -1;
|
||||
public int savedFilterMag = -1;
|
||||
public float savedFilterAniso = -1.0f;
|
||||
public boolean lineQuadsActive = false;
|
||||
public boolean lineStippleActive = false;
|
||||
public Instancing ffpInstancing = Instancing.NONE;
|
||||
public float lastBrightnessX;
|
||||
public float lastBrightnessY;
|
||||
public final Matrix4f multMatrix = new Matrix4f();
|
||||
public final Matrix4d conversionMatrix4d = new Matrix4d();
|
||||
public final Matrix4f conversionMatrix4f = new Matrix4f();
|
||||
public final Matrix4f gluMatrix = new Matrix4f();
|
||||
public final Matrix4f gluMatrix2 = new Matrix4f();
|
||||
public final Vector4f gluVec4 = new Vector4f();
|
||||
public final FloatBuffer gluBuffer = BufferUtils.createFloatBuffer(16);
|
||||
public final float[] texGenTempPlane = new float[4];
|
||||
public final IntBuffer shaderBuf = BufferUtils.createIntBuffer(8);
|
||||
public final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
|
||||
public final ShaderManager ffp = new ShaderManager();
|
||||
|
||||
GLContextState() {
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) modifiedAtDepth[i] = new ArrayList<>();
|
||||
allStacksBuilder.addAll(textures.idStacks());
|
||||
for (int i = 0; i < lightStates.length; i++) {
|
||||
lightStates[i] = track(new BooleanStateStack(GL11.GL_LIGHT0 + i, false, true));
|
||||
}
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
clipPlaneStates[i] = track(new BooleanStateStack(GL11.GL_CLIP_PLANE0 + i));
|
||||
clipPlaneStates[i] = track(new ClipPlaneBooleanState(this, i, GL11.GL_CLIP_PLANE0 + i));
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
for (int i = 0; i < lightDataStates.length; i++) {
|
||||
lightDataStates[i] = new LightStateStack(GL11.GL_LIGHT0 + i);
|
||||
lightDataStates[i] = member(new LightStateStack(GL11.GL_LIGHT0 + i, StackIdAllocator.nextId()));
|
||||
}
|
||||
allStacksBuilder.add(ffp.normalStack);
|
||||
allStacksBuilder.add(ffp.texCoordStack);
|
||||
allStacks = allStacksBuilder.toArray(new IStateStack<?>[0]);
|
||||
|
||||
final int capacity = StackIdAllocator.allocated();
|
||||
stackById = new CowStateStack<?>[capacity];
|
||||
depthsById = new CowDepths[capacity];
|
||||
restoreBitById = new int[capacity];
|
||||
restoreUnitById = new int[capacity];
|
||||
kindById = new byte[capacity];
|
||||
for (final IStateStack<?> stack : allStacks) {
|
||||
final CowStateStack<?> s = (CowStateStack<?>) stack;
|
||||
final int id = s.stackId();
|
||||
stackById[id] = s;
|
||||
depthsById[id] = s.cowDepths();
|
||||
depthsById[id].owner = this;
|
||||
restoreBitById[id] = s.restoreBit();
|
||||
restoreUnitById[id] = s.restoreUnit();
|
||||
kindById[id] = CowDispatch.kindOf(s);
|
||||
}
|
||||
|
||||
modifiedIds = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH][allStacks.length];
|
||||
modifiedCount = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import org.lwjgl.opengl.GL43;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class GLDebug {
|
||||
|
||||
@@ -564,7 +565,7 @@ public final class GLDebug {
|
||||
/**
|
||||
* Overload that lets you gate the String concat behind the debugState
|
||||
*/
|
||||
public static void pushGroup(String prefix, int value) {
|
||||
public static void pushGroup(String prefix, long value) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.pushGroup(prefix + value);
|
||||
}
|
||||
@@ -586,6 +587,13 @@ public final class GLDebug {
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetGroupStack() {
|
||||
if (debugState instanceof KHRDebugState khr) {
|
||||
khr.depth = 0;
|
||||
Arrays.fill(khr.groupStack, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debugMessage(String message) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.debugMessage(message);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.OpenGLException;
|
||||
|
||||
final class GLErrorReporter {
|
||||
|
||||
private GLErrorReporter() {}
|
||||
|
||||
static void reportError(int error) {
|
||||
throw new OpenGLException(error);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,19 +1,25 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.antlr.v4.runtime.BailErrorStrategy;
|
||||
import org.antlr.v4.runtime.BufferedTokenStream;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonToken;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.DefaultErrorStrategy;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.WritableToken;
|
||||
import org.antlr.v4.runtime.atn.PredictionMode;
|
||||
import org.antlr.v4.runtime.misc.ParseCancellationException;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLPreParser;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -47,11 +53,11 @@ public class GlslTransformUtils {
|
||||
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
|
||||
// Some reserved words to always rename
|
||||
0, List.of(
|
||||
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"),
|
||||
new ReservedWordRename(Pattern.compile("\\bnew\\b"), RENAMED_PREFIX + "new")
|
||||
new ReservedWordRename(Pattern.compile("sample(?<=\\bsample)\\b"), RENAMED_PREFIX + "sample"),
|
||||
new ReservedWordRename(Pattern.compile("new(?<=\\bnew)\\b"), RENAMED_PREFIX + "new")
|
||||
),
|
||||
400, List.of(
|
||||
new ReservedWordRename(Pattern.compile("\\bsampler\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
|
||||
new ReservedWordRename(Pattern.compile("sampler(?<=\\bsampler)\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
|
||||
)
|
||||
);
|
||||
|
||||
@@ -70,6 +76,10 @@ public class GlslTransformUtils {
|
||||
}
|
||||
|
||||
public static String renameReservedWords(String source, int targetVersion) {
|
||||
if (!containsWholeWord(source, "sample") && !containsWholeWord(source, "new")
|
||||
&& (targetVersion < 400 || !containsWholeWord(source, "sampler"))) {
|
||||
return source;
|
||||
}
|
||||
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
|
||||
// Rename identifiers that become reserved words at or above this GLSL version
|
||||
if (targetVersion >= entry.getKey()) {
|
||||
@@ -81,6 +91,23 @@ public class GlslTransformUtils {
|
||||
return source;
|
||||
}
|
||||
|
||||
static boolean containsWholeWord(String source, String word) {
|
||||
int from = 0;
|
||||
while (true) {
|
||||
final int match = source.indexOf(word, from);
|
||||
if (match < 0) return false;
|
||||
final int end = match + word.length();
|
||||
if ((match == 0 || !isAsciiWordChar(source.charAt(match - 1))) && (end == source.length() || !isAsciiWordChar(source.charAt(end)))) {
|
||||
return true;
|
||||
}
|
||||
from = match + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAsciiWordChar(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
|
||||
}
|
||||
|
||||
public static String restoreReservedWords(String source) {
|
||||
source = source.replace(RENAMED_PREFIX + "texture", "texture");
|
||||
source = source.replace(RENAMED_PREFIX + "sampler", "sampler");
|
||||
@@ -88,95 +115,86 @@ public class GlslTransformUtils {
|
||||
return source;
|
||||
}
|
||||
|
||||
public static void restoreReservedWordsInTree(ParseTree tree) {
|
||||
if (tree instanceof TerminalNode terminal) {
|
||||
final Token token = terminal.getSymbol();
|
||||
final String text = token.getText();
|
||||
if (text != null && text.startsWith(RENAMED_PREFIX) && token instanceof WritableToken writable) {
|
||||
final String rest = text.substring(RENAMED_PREFIX.length());
|
||||
if (rest.startsWith("texture") || rest.startsWith("sample")) {
|
||||
writable.setText(rest);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < tree.getChildCount(); i++) {
|
||||
restoreReservedWordsInTree(tree.getChild(i));
|
||||
}
|
||||
}
|
||||
|
||||
public record QuietParse(GLSLParser.Translation_unitContext full, GLSLPreParser.Translation_unitContext pre) {}
|
||||
|
||||
public static GLSLParser.Translation_unitContext parseFullQuiet(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
|
||||
parser.removeErrorListeners();
|
||||
parser.setBuildParseTree(true);
|
||||
return parser.translation_unit();
|
||||
}
|
||||
|
||||
public static GLSLPreParser.Translation_unitContext parsePreQuiet(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
final GLSLPreParser preParser = new GLSLPreParser(new CommonTokenStream(lexer, GLSLLexer.DIRECTIVES));
|
||||
preParser.removeErrorListeners();
|
||||
preParser.setBuildParseTree(true);
|
||||
return preParser.translation_unit();
|
||||
return parseQuiet(new GLSLParser(new CommonTokenStream(quietLexer(source))), GLSLParser::translation_unit);
|
||||
}
|
||||
|
||||
public static QuietParse parseBothQuiet(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
final GLSLPreParser preParser = new GLSLPreParser(new BufferedTokenStream(lexer));
|
||||
preParser.removeErrorListeners();
|
||||
preParser.setBuildParseTree(true);
|
||||
final GLSLPreParser.Translation_unitContext pre = preParser.translation_unit();
|
||||
final GLSLLexer lexer = quietLexer(source);
|
||||
final GLSLPreParser.Translation_unitContext pre = parseQuiet(new GLSLPreParser(new BufferedTokenStream(lexer)), GLSLPreParser::translation_unit);
|
||||
lexer.reset();
|
||||
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
|
||||
parser.removeErrorListeners();
|
||||
parser.setBuildParseTree(true);
|
||||
return new QuietParse(parser.translation_unit(), pre);
|
||||
return new QuietParse(parseQuiet(new GLSLParser(new CommonTokenStream(lexer)), GLSLParser::translation_unit), pre);
|
||||
}
|
||||
|
||||
public static Set<String> identifiersInDirectiveText(String source) {
|
||||
private static <P extends Parser, T> T parseQuiet(P parser, Function<P, T> rule) {
|
||||
parser.removeErrorListeners();
|
||||
parser.setErrorHandler(new BailErrorStrategy());
|
||||
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
|
||||
try {
|
||||
return rule.apply(parser);
|
||||
} catch (ParseCancellationException e) {
|
||||
// Rewinds the token stream, which keeps every token SLL already pulled from the lexer.
|
||||
parser.reset();
|
||||
parser.setErrorHandler(new DefaultErrorStrategy());
|
||||
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
|
||||
return rule.apply(parser);
|
||||
}
|
||||
}
|
||||
|
||||
public static GLSLLexer quietLexer(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
final BufferedTokenStream tokens = new BufferedTokenStream(lexer);
|
||||
tokens.fill();
|
||||
|
||||
final Set<String> names = new HashSet<>();
|
||||
for (Token token : tokens.getTokens()) {
|
||||
switch (token.getType()) {
|
||||
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private static void collectIdentifiers(String text, Set<String> out) {
|
||||
if (text == null) return;
|
||||
final int len = text.length();
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
final char c = text.charAt(i);
|
||||
if (c != '_' && !Character.isLetter(c)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
int end = i + 1;
|
||||
while (end < len) {
|
||||
final char n = text.charAt(end);
|
||||
if (n != '_' && !Character.isLetterOrDigit(n)) break;
|
||||
end++;
|
||||
}
|
||||
out.add(text.substring(i, end));
|
||||
i = end;
|
||||
}
|
||||
return lexer;
|
||||
}
|
||||
|
||||
public static String getFormattedShader(ParseTree tree, String header) {
|
||||
final StringBuilder sb = new StringBuilder(header + "\n");
|
||||
final String[] tabHolder = {""};
|
||||
getFormattedShader(tree, sb, tabHolder);
|
||||
getFormattedShader(tree, sb, tabHolder, false);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
|
||||
if (tree instanceof TerminalNode) {
|
||||
public static String getFormattedShaderRebased(ParseTree tree, String header) {
|
||||
final StringBuilder sb = new StringBuilder(header + "\n");
|
||||
final String[] tabHolder = {""};
|
||||
getFormattedShader(tree, sb, tabHolder, true);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder, boolean rebaseOffsets) {
|
||||
if (tree instanceof TerminalNode terminal) {
|
||||
final String text = tree.getText();
|
||||
if (text.equals("<EOF>")) {
|
||||
return;
|
||||
}
|
||||
if (text.equals("#")) {
|
||||
stringBuilder.append("\n#");
|
||||
rebaseAt(terminal, stringBuilder.length() - 1, text, rebaseOffsets);
|
||||
return;
|
||||
}
|
||||
stringBuilder.append(text);
|
||||
if (!text.equals("}")) {
|
||||
rebaseAt(terminal, stringBuilder.length() - text.length(), text, rebaseOffsets);
|
||||
}
|
||||
if (text.equals("{")) {
|
||||
stringBuilder.append(" \n\t");
|
||||
tabHolder[0] = "\t";
|
||||
@@ -186,6 +204,7 @@ public class GlslTransformUtils {
|
||||
if (stringBuilder.length() >= 2) {
|
||||
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
|
||||
}
|
||||
rebaseAt(terminal, stringBuilder.length() - 1, text, rebaseOffsets);
|
||||
tabHolder[0] = "";
|
||||
stringBuilder.append(" \n");
|
||||
} else {
|
||||
@@ -193,8 +212,17 @@ public class GlslTransformUtils {
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < tree.getChildCount(); ++i) {
|
||||
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
|
||||
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder, rebaseOffsets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void rebaseAt(TerminalNode terminal, int startIndex, String text, boolean rebaseOffsets) {
|
||||
if (!rebaseOffsets) return;
|
||||
if (terminal.getSymbol() instanceof CommonToken common) {
|
||||
common.setText(text);
|
||||
common.setStartIndex(startIndex);
|
||||
common.setStopIndex(startIndex + text.length() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
public final class LTWWorkaround {
|
||||
|
||||
private LTWWorkaround() {}
|
||||
|
||||
private static ByteBuffer oneTexel;
|
||||
|
||||
public static boolean isLtwVersionString(String glVersion) {
|
||||
return glVersion != null && glVersion.contains("LTW");
|
||||
}
|
||||
|
||||
public static void onTexImage2D(int target, int level, int format, boolean hasPixels) {
|
||||
if (target != GL11.GL_TEXTURE_2D || level != 0 || format != GL12.GL_BGRA || hasPixels) return;
|
||||
if (oneTexel == null) {
|
||||
oneTexel = BufferUtils.createByteBuffer(4);
|
||||
}
|
||||
oneTexel.clear();
|
||||
GLStateManager.forcePixelUnpackState(PixelStoreState.DEFAULT);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texSubImage2D(target, level, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oneTexel);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
GLStateManager.restorePixelUnpackState(PixelStoreState.DEFAULT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
final class LegacyQueries {
|
||||
|
||||
static final int BOOL = 1;
|
||||
static final int INT = 2;
|
||||
static final int FLOAT = 3;
|
||||
static final int FLOATN = 4;
|
||||
|
||||
private static final int COUNT_SHIFT = 3;
|
||||
|
||||
private LegacyQueries() {}
|
||||
|
||||
static int desc(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_ALPHA_TEST, GL11.GL_AUTO_NORMAL, GL11.GL_COLOR_MATERIAL, GL14.GL_COLOR_SUM, GL11.GL_FOG,
|
||||
GL11.GL_INDEX_LOGIC_OP, GL11.GL_LIGHTING, GL11.GL_LIGHT0, GL11.GL_LIGHT1, GL11.GL_LIGHT2, GL11.GL_LIGHT3,
|
||||
GL11.GL_LIGHT4, GL11.GL_LIGHT5, GL11.GL_LIGHT6, GL11.GL_LIGHT7, GL11.GL_LINE_STIPPLE, GL11.GL_MAP1_COLOR_4,
|
||||
GL11.GL_MAP1_INDEX, GL11.GL_MAP1_NORMAL, GL11.GL_MAP1_TEXTURE_COORD_1, GL11.GL_MAP1_TEXTURE_COORD_2,
|
||||
GL11.GL_MAP1_TEXTURE_COORD_3, GL11.GL_MAP1_TEXTURE_COORD_4, GL11.GL_MAP1_VERTEX_3, GL11.GL_MAP1_VERTEX_4,
|
||||
GL11.GL_MAP2_COLOR_4, GL11.GL_MAP2_INDEX, GL11.GL_MAP2_NORMAL, GL11.GL_MAP2_TEXTURE_COORD_1,
|
||||
GL11.GL_MAP2_TEXTURE_COORD_2, GL11.GL_MAP2_TEXTURE_COORD_3, GL11.GL_MAP2_TEXTURE_COORD_4,
|
||||
GL11.GL_MAP2_VERTEX_3, GL11.GL_MAP2_VERTEX_4, GL11.GL_NORMALIZE, GL11.GL_POINT_SMOOTH,
|
||||
GL11.GL_POLYGON_STIPPLE, GL12.GL_RESCALE_NORMAL, GL11.GL_TEXTURE_1D, GL12.GL_TEXTURE_3D,
|
||||
GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_GEN_S, GL11.GL_TEXTURE_GEN_T, GL11.GL_TEXTURE_GEN_R,
|
||||
GL11.GL_TEXTURE_GEN_Q, GL11.GL_LIGHT_MODEL_LOCAL_VIEWER, GL11.GL_LIGHT_MODEL_TWO_SIDE,
|
||||
GL11.GL_VERTEX_ARRAY, GL11.GL_NORMAL_ARRAY, GL11.GL_COLOR_ARRAY, GL11.GL_TEXTURE_COORD_ARRAY,
|
||||
GL11.GL_INDEX_ARRAY, GL11.GL_EDGE_FLAG_ARRAY, GL14.GL_SECONDARY_COLOR_ARRAY, GL14.GL_FOG_COORDINATE_ARRAY,
|
||||
GL11.GL_EDGE_FLAG, GL20.GL_POINT_SPRITE, GL20.GL_VERTEX_PROGRAM_TWO_SIDE -> BOOL;
|
||||
case GL11.GL_ALPHA_TEST_FUNC, GL11.GL_LIST_BASE, GL11.GL_MATRIX_MODE, GL11.GL_SHADE_MODEL,
|
||||
GL11.GL_COLOR_MATERIAL_FACE, GL11.GL_COLOR_MATERIAL_PARAMETER, GL12.GL_LIGHT_MODEL_COLOR_CONTROL,
|
||||
GL11.GL_FOG_MODE, GL11.GL_LINE_STIPPLE_PATTERN, GL11.GL_LINE_STIPPLE_REPEAT, GL13.GL_CLIENT_ACTIVE_TEXTURE,
|
||||
GL20.GL_MAX_TEXTURE_COORDS, GL11.GL_RENDER_MODE, GL11.GL_FOG_HINT, GL11.GL_PERSPECTIVE_CORRECTION_HINT,
|
||||
GL11.GL_POINT_SMOOTH_HINT, GL14.GL_GENERATE_MIPMAP_HINT, GL11.GL_LINE_SMOOTH_HINT, GL11.GL_POLYGON_SMOOTH_HINT,
|
||||
GL13.GL_TEXTURE_COMPRESSION_HINT, GL20.GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL20.GL_POINT_SPRITE_COORD_ORIGIN,
|
||||
GL11.GL_VERTEX_ARRAY_SIZE, GL11.GL_VERTEX_ARRAY_TYPE, GL11.GL_VERTEX_ARRAY_STRIDE, GL15.GL_VERTEX_ARRAY_BUFFER_BINDING,
|
||||
GL11.GL_NORMAL_ARRAY_TYPE, GL11.GL_NORMAL_ARRAY_STRIDE, GL15.GL_NORMAL_ARRAY_BUFFER_BINDING,
|
||||
GL11.GL_COLOR_ARRAY_SIZE, GL11.GL_COLOR_ARRAY_TYPE, GL11.GL_COLOR_ARRAY_STRIDE, GL15.GL_COLOR_ARRAY_BUFFER_BINDING,
|
||||
GL11.GL_INDEX_ARRAY_TYPE, GL11.GL_INDEX_ARRAY_STRIDE, GL15.GL_INDEX_ARRAY_BUFFER_BINDING,
|
||||
GL11.GL_TEXTURE_COORD_ARRAY_SIZE, GL11.GL_TEXTURE_COORD_ARRAY_TYPE, GL11.GL_TEXTURE_COORD_ARRAY_STRIDE,
|
||||
GL15.GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, GL11.GL_EDGE_FLAG_ARRAY_STRIDE, GL15.GL_EDGE_FLAG_ARRAY_BUFFER_BINDING,
|
||||
GL14.GL_SECONDARY_COLOR_ARRAY_SIZE, GL14.GL_SECONDARY_COLOR_ARRAY_TYPE, GL14.GL_SECONDARY_COLOR_ARRAY_STRIDE,
|
||||
GL15.GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING, GL14.GL_FOG_COORDINATE_ARRAY_TYPE, GL14.GL_FOG_COORDINATE_ARRAY_STRIDE,
|
||||
GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING -> INT;
|
||||
case GL11.GL_FOG_DENSITY, GL11.GL_FOG_START, GL11.GL_FOG_END, GL14.GL_POINT_SIZE_MIN, GL14.GL_POINT_SIZE_MAX,
|
||||
GL14.GL_POINT_FADE_THRESHOLD_SIZE -> FLOAT;
|
||||
case GL11.GL_ALPHA_TEST_REF -> FLOATN;
|
||||
case GL14.GL_POINT_DISTANCE_ATTENUATION -> FLOAT | 2 << COUNT_SHIFT;
|
||||
case GL11.GL_CURRENT_TEXTURE_COORDS -> FLOAT | 3 << COUNT_SHIFT;
|
||||
case GL11.GL_MODELVIEW_MATRIX, GL11.GL_PROJECTION_MATRIX, GL11.GL_TEXTURE_MATRIX -> FLOATN | 15 << COUNT_SHIFT;
|
||||
case GL11.GL_CURRENT_NORMAL -> FLOATN | 2 << COUNT_SHIFT;
|
||||
case GL11.GL_CURRENT_COLOR, GL14.GL_CURRENT_SECONDARY_COLOR, GL11.GL_FOG_COLOR, GL11.GL_LIGHT_MODEL_AMBIENT -> FLOATN | 3 << COUNT_SHIFT;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
static int type(int desc) {
|
||||
return desc & 7;
|
||||
}
|
||||
|
||||
static int count(int desc) {
|
||||
return (desc >> COUNT_SHIFT) + 1;
|
||||
}
|
||||
|
||||
static int floatToInt(float value) {
|
||||
return (int) (2147483647.0 * value);
|
||||
}
|
||||
|
||||
static int lround(float value) {
|
||||
return value < 0 ? -Math.round(-value) : Math.round(value);
|
||||
}
|
||||
|
||||
static int toInt(int type, float value) {
|
||||
return type == FLOATN ? floatToInt(value) : lround(value);
|
||||
}
|
||||
|
||||
private static FloatBuffer fillScratch(GLContextState glCtx, int pname) {
|
||||
final FloatBuffer scratch = glCtx.queryScratch;
|
||||
scratch.clear();
|
||||
GLStateManager.glGetFloat(pname, scratch);
|
||||
return scratch;
|
||||
}
|
||||
|
||||
private static IllegalStateException uncached(int pname) {
|
||||
return new IllegalStateException("Legacy pname 0x" + Integer.toHexString(pname) + " has no cached answer for its declared type");
|
||||
}
|
||||
|
||||
static int getInteger(GLContextState glCtx, int pname, int desc) {
|
||||
final int type = type(desc);
|
||||
if (count(desc) > 1) {
|
||||
return toInt(type, fillScratch(glCtx, pname).get(0));
|
||||
}
|
||||
return switch (type) {
|
||||
case BOOL -> GLStateManager.glGetBoolean(glCtx, pname) ? GL11.GL_TRUE : GL11.GL_FALSE;
|
||||
case FLOAT -> lround(GLStateManager.glGetFloat(glCtx, pname));
|
||||
case FLOATN -> floatToInt(GLStateManager.glGetFloat(glCtx, pname));
|
||||
default -> throw uncached(pname);
|
||||
};
|
||||
}
|
||||
|
||||
static float getFloat(GLContextState glCtx, int pname, int desc) {
|
||||
if (count(desc) > 1) {
|
||||
return fillScratch(glCtx, pname).get(0);
|
||||
}
|
||||
return switch (type(desc)) {
|
||||
case BOOL -> GLStateManager.glGetBoolean(glCtx, pname) ? 1.0f : 0.0f;
|
||||
case INT -> (float) GLStateManager.glGetInteger(glCtx, pname);
|
||||
default -> throw uncached(pname);
|
||||
};
|
||||
}
|
||||
|
||||
static boolean getBoolean(GLContextState glCtx, int pname, int desc) {
|
||||
if (count(desc) > 1) {
|
||||
return fillScratch(glCtx, pname).get(0) != 0.0f;
|
||||
}
|
||||
return switch (type(desc)) {
|
||||
case INT -> GLStateManager.glGetInteger(glCtx, pname) != 0;
|
||||
case FLOAT, FLOATN -> GLStateManager.glGetFloat(glCtx, pname) != 0.0f;
|
||||
default -> throw uncached(pname);
|
||||
};
|
||||
}
|
||||
|
||||
static boolean getIntegerv(GLContextState glCtx, int pname, IntBuffer params) {
|
||||
final int desc = desc(pname);
|
||||
if (desc == 0) return false;
|
||||
final int pos = params.position();
|
||||
final int type = type(desc);
|
||||
final FloatBuffer scratch = fillScratch(glCtx, pname);
|
||||
for (int i = 0, count = count(desc); i < count; i++) {
|
||||
params.put(pos + i, toInt(type, scratch.get(i)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean getIntegerv(GLContextState glCtx, int pname, int[] params) {
|
||||
final int desc = desc(pname);
|
||||
if (desc == 0) return false;
|
||||
final int type = type(desc);
|
||||
final FloatBuffer scratch = fillScratch(glCtx, pname);
|
||||
for (int i = 0, count = Math.min(count(desc), params.length); i < count; i++) {
|
||||
params[i] = toInt(type, scratch.get(i));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean getBooleanv(GLContextState glCtx, int pname, ByteBuffer params) {
|
||||
final int desc = desc(pname);
|
||||
if (desc == 0) return false;
|
||||
final int pos = params.position();
|
||||
final FloatBuffer scratch = fillScratch(glCtx, pname);
|
||||
for (int i = 0, count = count(desc); i < count; i++) {
|
||||
params.put(pos + i, (byte) (scratch.get(i) != 0.0f ? GL11.GL_TRUE : GL11.GL_FALSE));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class LightingSnapshot {
|
||||
final boolean[] lightEnabled = new boolean[VertexKey.FFP_LIGHT_COUNT];
|
||||
final LightState[] lights = new LightState[VertexKey.FFP_LIGHT_COUNT];
|
||||
final LightModelState lightModel = new LightModelState();
|
||||
final MaterialState frontMaterial = new MaterialState(GL11.GL_FRONT);
|
||||
final MaterialState backMaterial = new MaterialState(GL11.GL_BACK);
|
||||
boolean colorMaterial;
|
||||
int colorMaterialFace;
|
||||
int colorMaterialParameter;
|
||||
boolean normalize;
|
||||
boolean rescaleNormal;
|
||||
|
||||
public LightingSnapshot() {
|
||||
for (int i = 0; i < lights.length; i++) {
|
||||
lights[i] = new LightState(GL11.GL_LIGHT0 + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,17 +82,17 @@ public final class QuadConverter {
|
||||
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
|
||||
* @param vertexCount number of vertices (must be multiple of 4)
|
||||
*/
|
||||
public static void drawQuadsAsTriangles(int first, int vertexCount) {
|
||||
drawSharedQuadEbo(first, vertexCount, 1, false);
|
||||
public static void drawQuadsAsTriangles(GLContextState glCtx, int first, int vertexCount) {
|
||||
drawSharedQuadEbo(glCtx, first, vertexCount, 1, false);
|
||||
}
|
||||
|
||||
public static void drawQuadsAsTrianglesInstanced(int first, int vertexCount, int primcount) {
|
||||
drawSharedQuadEbo(first, vertexCount, primcount, true);
|
||||
public static void drawQuadsAsTrianglesInstanced(GLContextState glCtx, int first, int vertexCount, int primcount) {
|
||||
drawSharedQuadEbo(glCtx, first, vertexCount, primcount, true);
|
||||
}
|
||||
|
||||
private static int misalignedDraws;
|
||||
|
||||
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) {
|
||||
private static void drawSharedQuadEbo(GLContextState glCtx, int first, int vertexCount, int primcount, boolean instanced) {
|
||||
final int quadCount = vertexCount / 4;
|
||||
if ((first & 3) != 0) {
|
||||
misalignedDraws++;
|
||||
@@ -111,26 +111,21 @@ public final class QuadConverter {
|
||||
memPutInt(ptr + 20, base + 3);
|
||||
ptr += 24;
|
||||
}
|
||||
uploadAndDraw(dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
|
||||
uploadAndDraw(glCtx, dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
|
||||
return;
|
||||
}
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
GLStateManager.preDraw();
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
|
||||
final long indexOffset = (long) (first / 4) * 6 * 4;
|
||||
if (instanced) {
|
||||
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
|
||||
} else {
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
|
||||
}
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
|
||||
final int prevEbo = glCtx.vaos.boundEBO;
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
|
||||
final long indexOffset = (long) (first / 4) * 6 * 4;
|
||||
if (instanced) {
|
||||
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
|
||||
} else {
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
|
||||
}
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,42 +138,37 @@ public final class QuadConverter {
|
||||
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
|
||||
* @param bytesPerIndex bytes per index element
|
||||
*/
|
||||
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
|
||||
uploadAndDraw(dst, triIndexCount, indexType, bytesPerIndex, 1, false);
|
||||
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, indexType, bytesPerIndex, 1, false);
|
||||
}
|
||||
|
||||
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
GLStateManager.preDraw();
|
||||
final int needed = triIndexCount * bytesPerIndex;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
|
||||
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
|
||||
final int needed = triIndexCount * bytesPerIndex;
|
||||
final int prevEbo = glCtx.vaos.boundEBO;
|
||||
|
||||
if (scratchEboId == 0) {
|
||||
scratchEboId = RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
|
||||
|
||||
if (needed > scratchEboCapacity) {
|
||||
// Power-of-2 growth -- allocate full capacity, upload actual data
|
||||
int newCap = Math.max(4096, scratchEboCapacity);
|
||||
while (newCap < needed) newCap *= 2;
|
||||
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
|
||||
scratchEboCapacity = newCap;
|
||||
}
|
||||
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
|
||||
|
||||
if (instanced) {
|
||||
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
|
||||
} else {
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
if (scratchEboId == 0) {
|
||||
scratchEboId = RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
|
||||
|
||||
if (needed > scratchEboCapacity) {
|
||||
// Power-of-2 growth -- allocate full capacity, upload actual data
|
||||
int newCap = Math.max(4096, scratchEboCapacity);
|
||||
while (newCap < needed) newCap *= 2;
|
||||
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
|
||||
scratchEboCapacity = newCap;
|
||||
}
|
||||
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
|
||||
|
||||
if (instanced) {
|
||||
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
|
||||
} else {
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
memFree(dst);
|
||||
}
|
||||
|
||||
@@ -190,15 +180,15 @@ public final class QuadConverter {
|
||||
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
|
||||
* @param offset byte offset into the currently bound EBO
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
|
||||
drawQuadElementsFromEbo(indexCount, type, offset, 1, false);
|
||||
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int indexCount, int type, long offset) {
|
||||
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, 1, false);
|
||||
}
|
||||
|
||||
public static void drawQuadElementsAsTrianglesInstanced(int indexCount, int type, long offset, int primcount) {
|
||||
drawQuadElementsFromEbo(indexCount, type, offset, primcount, true);
|
||||
public static void drawQuadElementsAsTrianglesInstanced(GLContextState glCtx, int indexCount, int type, long offset, int primcount) {
|
||||
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, primcount, true);
|
||||
}
|
||||
|
||||
private static void drawQuadElementsFromEbo(int indexCount, int type, long offset, int primcount, boolean instanced) {
|
||||
private static void drawQuadElementsFromEbo(GLContextState glCtx, int indexCount, int type, long offset, int primcount, boolean instanced) {
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
|
||||
final int quadCount = indexCount / 4;
|
||||
@@ -213,13 +203,13 @@ public final class QuadConverter {
|
||||
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
memFree(src);
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side IntBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
|
||||
public static void drawQuadElementsAsTriangles(GLContextState glCtx, IntBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
@@ -230,13 +220,13 @@ public final class QuadConverter {
|
||||
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
|
||||
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ShortBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
|
||||
public static void drawQuadElementsAsTriangles(GLContextState glCtx, ShortBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
@@ -247,13 +237,13 @@ public final class QuadConverter {
|
||||
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
|
||||
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
|
||||
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int count, int type, ByteBuffer indices) {
|
||||
if (count == 0) return;
|
||||
assert count % 4 == 0;
|
||||
final int quadCount = count / 4;
|
||||
@@ -263,23 +253,7 @@ public final class QuadConverter {
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the shared EBO.
|
||||
*/
|
||||
public static void destroy() {
|
||||
if (eboId != 0) {
|
||||
GLStateManager.glDeleteBuffers(eboId);
|
||||
eboId = 0;
|
||||
maxQuads = 0;
|
||||
}
|
||||
if (scratchEboId != 0) {
|
||||
GLStateManager.glDeleteBuffers(scratchEboId);
|
||||
scratchEboId = 0;
|
||||
scratchEboCapacity = 0;
|
||||
}
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
|
||||
@@ -8,6 +9,7 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.taumc.celeritas.lwjgl.LWJGLServiceProvider;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -46,7 +48,9 @@ public class RenderSystem {
|
||||
private static boolean supportsClearTexture;
|
||||
private static boolean supportsTesselation;
|
||||
private static boolean supportsSamplerObjects;
|
||||
private static boolean supportsProgramBinary;
|
||||
private static int maxImageUnits;
|
||||
@Getter private static int maxCombinedTextureImageUnits;
|
||||
private static int maxSSBOBindings;
|
||||
private static int maxGlslVersion;
|
||||
private static boolean supportsGpuShader4;
|
||||
@@ -59,8 +63,8 @@ public class RenderSystem {
|
||||
private static volatile boolean glesDetected;
|
||||
private static volatile boolean hasClipCullDistance;
|
||||
|
||||
// Sampler object state tracking (null if unsupported)
|
||||
private static int[] samplers;
|
||||
private static volatile boolean isLTW;
|
||||
private static volatile boolean ltwDetected;
|
||||
|
||||
private static final Pattern SEMVER_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)\\.*(?<bugfix>\\d*)(.*)");
|
||||
|
||||
@@ -107,6 +111,13 @@ public class RenderSystem {
|
||||
|
||||
BackendManager.init();
|
||||
|
||||
try {
|
||||
// Force init on client thread
|
||||
GLStateManager.LOGGER.info("Celeritas LWJGL service: {}", LWJGLServiceProvider.LWJGL.getClass().getName());
|
||||
} catch (Throwable t) {
|
||||
GLStateManager.LOGGER.debug("Could not resolve the celeritas LWJGL service: {}", t.toString());
|
||||
}
|
||||
|
||||
supportsCompute = supportsCompute();
|
||||
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
|
||||
|
||||
@@ -146,9 +157,12 @@ public class RenderSystem {
|
||||
// Check for sampler objects support (GL 3.3+ or ARB extension)
|
||||
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
|
||||
if (supportsSamplerObjects) {
|
||||
samplers = new int[RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
|
||||
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
|
||||
}
|
||||
|
||||
supportsProgramBinary = RENDER_BACKEND.supportsProgramBinary();
|
||||
GLStateManager.LOGGER.info("Program Binary: {}", supportsProgramBinary);
|
||||
|
||||
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
|
||||
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
|
||||
|
||||
@@ -159,13 +173,13 @@ public class RenderSystem {
|
||||
|
||||
if (isGLES) {
|
||||
GLStateManager.LOGGER.info("GL ES context detected, enabling shader transformer.");
|
||||
ShaderManager.getInstance().enable();
|
||||
ShaderManager.enable();
|
||||
} else if (GLStateManager.capabilities.OpenGL32) {
|
||||
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
|
||||
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
|
||||
coreProfile = true;
|
||||
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
|
||||
ShaderManager.getInstance().enable();
|
||||
ShaderManager.enable();
|
||||
} else {
|
||||
throw new IllegalStateException("Non-core GL context (profile mask 0x" + Integer.toHexString(profileMask)
|
||||
+ "); FFP emulation requires a core profile and nothing would render. Context creation should have rejected this.");
|
||||
@@ -403,6 +417,26 @@ public class RenderSystem {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLTW() {
|
||||
if (!ltwDetected) detectLTW();
|
||||
return isLTW;
|
||||
}
|
||||
|
||||
private static synchronized void detectLTW() {
|
||||
if (ltwDetected) return;
|
||||
if (SystemProperties.DISABLE_LTW_WORKAROUND) {
|
||||
ltwDetected = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final String v = RENDER_BACKEND.getString(GL11.GL_VERSION);
|
||||
if (v == null) return;
|
||||
isLTW = LTWWorkaround.isLtwVersionString(v);
|
||||
ltwDetected = true;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean supportsSSBO() {
|
||||
return supportsSSBO;
|
||||
}
|
||||
@@ -454,34 +488,33 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void disableBufferBlend(int buffer) {
|
||||
RENDER_BACKEND.disablei(GL11.GL_BLEND, buffer);
|
||||
GLStateManager.getBlendMode().setUnknownState();
|
||||
GLStateManager.glDisablei(GL11.GL_BLEND, buffer);
|
||||
}
|
||||
|
||||
public static void enableBufferBlend(int buffer) {
|
||||
RENDER_BACKEND.enablei(GL11.GL_BLEND, buffer);
|
||||
GLStateManager.getBlendMode().setUnknownState();
|
||||
GLStateManager.glEnablei(GL11.GL_BLEND, buffer);
|
||||
}
|
||||
|
||||
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
RENDER_BACKEND.blendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
GLStateManager.glBlendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int unit, int texture) {
|
||||
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
dsaState.bindTextureToUnit(unit, texture);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int target, int unit, int texture) {
|
||||
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
dsaState.bindTextureToUnit(target, unit, texture);
|
||||
}
|
||||
|
||||
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
public static void setupProjectionMatrix(Matrix4f matrix) {
|
||||
final FloatBuffer projectionMatrixBuffer = GLStateManager.ctx().projectionMatrixBuffer;
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPushMatrix();
|
||||
matrix.get(0, PROJECTION_MATRIX_BUFFER);
|
||||
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
|
||||
matrix.get(0, projectionMatrixBuffer);
|
||||
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
@@ -523,11 +556,11 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int getIndexedBufferBinding(int target, int index) {
|
||||
return RENDER_BACKEND.getIndexedBufferBinding(target, index);
|
||||
return GLStateManager.getIndexedBufferBinding(target, index);
|
||||
}
|
||||
|
||||
public static void bindBufferBase(int target, int index, int buffer) {
|
||||
RENDER_BACKEND.bindBufferBase(target, index, buffer);
|
||||
GLStateManager.glBindBufferBase(target, index, buffer);
|
||||
}
|
||||
|
||||
public static void bufferStorage(int target, long size, int flags) {
|
||||
@@ -557,18 +590,6 @@ public class RenderSystem {
|
||||
RENDER_BACKEND.clearTexImage(texture, level, format, type);
|
||||
}
|
||||
|
||||
public static void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
dsaState.textureStorage1D(texture, target, levels, internalFormat, width);
|
||||
}
|
||||
|
||||
public static void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
dsaState.textureStorage2D(texture, target, levels, internalFormat, width, height);
|
||||
}
|
||||
|
||||
public static void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
dsaState.textureStorage3D(texture, target, levels, internalFormat, width, height, depth);
|
||||
}
|
||||
|
||||
public static int getMaxGlslVersion() {
|
||||
return maxGlslVersion;
|
||||
}
|
||||
@@ -594,9 +615,13 @@ public class RenderSystem {
|
||||
return supportsSamplerObjects;
|
||||
}
|
||||
|
||||
public static boolean supportsProgramBinary() {
|
||||
return supportsProgramBinary;
|
||||
}
|
||||
|
||||
public static int genSampler() {
|
||||
if (!supportsSamplerObjects) return 0;
|
||||
return RENDER_BACKEND.genSamplers();
|
||||
return GLStateManager.glGenSamplers();
|
||||
}
|
||||
|
||||
public static void destroySampler(int sampler) {
|
||||
@@ -606,22 +631,25 @@ public class RenderSystem {
|
||||
|
||||
public static void samplerParameteri(int sampler, int pname, int param) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
RENDER_BACKEND.samplerParameteri(sampler, pname, param);
|
||||
GLStateManager.glSamplerParameteri(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void samplerParameterf(int sampler, int pname, float param) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
GLStateManager.glSamplerParameterf(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void bindSamplerToUnit(int unit, int sampler) {
|
||||
if (!supportsSamplerObjects) return;
|
||||
if (samplers[unit] == sampler) return;
|
||||
RENDER_BACKEND.bindSampler(unit, sampler);
|
||||
samplers[unit] = sampler;
|
||||
GLStateManager.glBindSampler(unit, sampler);
|
||||
}
|
||||
|
||||
public static void unbindAllSamplers() {
|
||||
if (!supportsSamplerObjects) return;
|
||||
for (int i = 0; i < samplers.length; i++) {
|
||||
if (samplers[i] != 0) {
|
||||
RENDER_BACKEND.bindSampler(i, 0);
|
||||
samplers[i] = 0;
|
||||
final int count = GLStateManager.getSamplerUnitCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (GLStateManager.getSamplerBinding(i) != 0) {
|
||||
GLStateManager.glBindSampler(i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -660,4 +688,12 @@ public class RenderSystem {
|
||||
if (actualVersion < 0) return true;
|
||||
return actualVersion >= requestedMajor * 10 + requestedMinor;
|
||||
}
|
||||
|
||||
public static final int MIN_CORE_VERSION = 33;
|
||||
|
||||
public static int probeCapAfterRejection(int requested, int actual) {
|
||||
if (actual >= MIN_CORE_VERSION && actual < requested) return actual;
|
||||
if (actual >= 0 && actual < MIN_CORE_VERSION && requested > MIN_CORE_VERSION) return MIN_CORE_VERSION;
|
||||
return requested - 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class StateSet {
|
||||
|
||||
public static final int R_DEPTH = 1;
|
||||
public static final int R_BLEND = 1 << 1;
|
||||
public static final int R_COLOR_MASK = 1 << 2;
|
||||
public static final int R_CLEAR_COLOR = 1 << 3;
|
||||
public static final int R_DRAW_BUFFER = 1 << 4;
|
||||
public static final int R_LOGIC_OP = 1 << 5;
|
||||
public static final int R_SECONDARY_COLOR = 1 << 6;
|
||||
public static final int R_STENCIL = 1 << 7;
|
||||
public static final int R_VIEWPORT = 1 << 8;
|
||||
public static final int R_LINE = 1 << 9;
|
||||
public static final int R_POINT = 1 << 10;
|
||||
public static final int R_POLYGON = 1 << 11;
|
||||
public static final int R_TEXTURE = 1 << 12;
|
||||
public static final int R_ACTIVE_UNIT = 1 << 13;
|
||||
public static final int R_PROGRAM = 1 << 14;
|
||||
public static final int R_SCISSOR = 1 << 15;
|
||||
public static final int R_HINT = 1 << 16;
|
||||
|
||||
public static final int B_FRAG_COLORBUF = 1;
|
||||
public static final int B_FRAG_TEXTURE = 1 << 1;
|
||||
public static final int B_FRAG_FOG = 1 << 2;
|
||||
public static final int B_CURRENT = 1 << 3;
|
||||
public static final int B_LIGHTING = 1 << 4;
|
||||
public static final int B_TRANSFORM = 1 << 5;
|
||||
|
||||
private static final long[] NO_MEMBERS = new long[0];
|
||||
private static final Int2ObjectMap<StateSet> maskToStateSetMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public final int glMask;
|
||||
public final long[] members;
|
||||
public final int restore;
|
||||
public final int bump;
|
||||
public final boolean blendSnapshot;
|
||||
public final DisplayListCommand pushCommand;
|
||||
|
||||
StateSet(int glMask, long[] members, int restore, int bump, boolean blendSnapshot) {
|
||||
this.glMask = glMask;
|
||||
this.members = members;
|
||||
this.restore = restore;
|
||||
this.bump = bump;
|
||||
this.blendSnapshot = blendSnapshot;
|
||||
this.pushCommand = () -> GLStateManager.pushStateLive(this);
|
||||
}
|
||||
|
||||
public boolean contains(int id) {
|
||||
if (id < 0) return false;
|
||||
final int word = id >> 6;
|
||||
return word < members.length && (members[word] & (1L << id)) != 0;
|
||||
}
|
||||
|
||||
private static long[] ids(CowStateStack<?>... stacks) {
|
||||
if (stacks.length == 0) return NO_MEMBERS;
|
||||
int max = 0;
|
||||
for (CowStateStack<?> s : stacks) max = Math.max(max, s.stackId());
|
||||
final long[] bits = new long[(max >> 6) + 1];
|
||||
for (CowStateStack<?> s : stacks) {
|
||||
final int id = s.stackId();
|
||||
bits[id >> 6] |= 1L << id;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static final StateSet BATCH = buildBatch();
|
||||
public static final StateSet FONT = buildFont();
|
||||
public static final StateSet FONT_PIPELINE = buildFontPipeline();
|
||||
public static final StateSet HAND = buildHand();
|
||||
public static final StateSet BLEND = buildBlend();
|
||||
public static final StateSet ALPHA = buildAlpha();
|
||||
public static final StateSet CUTOUT = buildCutout();
|
||||
public static final StateSet MASKS = buildMasks();
|
||||
public static final StateSet CULL = buildCull();
|
||||
|
||||
private static StateSet buildBatch() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getAlphaState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getColorMask(), GLStateManager.getPolygonState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getLightingState(), GLStateManager.getCullState(), GLStateManager.getPolygonOffsetFillState(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0), GLStateManager.getTextures().getTextureUnitStates(1),
|
||||
GLStateManager.getLightStates()[0], GLStateManager.getLightStates()[1],
|
||||
GLStateManager.getLightDataStates()[0], GLStateManager.getLightDataStates()[1],
|
||||
GLStateManager.getColorMaterial(), GLStateManager.getColorMaterialFace(), GLStateManager.getColorMaterialParameter(),
|
||||
GLStateManager.getLightModel(), GLStateManager.getFrontMaterial(), GLStateManager.getBackMaterial(),
|
||||
GLStateManager.getNormalizeState(), GLStateManager.getRescaleNormalState()
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_POLYGON | R_TEXTURE | R_ACTIVE_UNIT,
|
||||
B_FRAG_COLORBUF | B_LIGHTING,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildFont() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getDepthState(), GLStateManager.getShadeModelState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(), GLStateManager.getProgramStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0),
|
||||
GLStateManager.getColorMask(), GLStateManager.getCullState(),
|
||||
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM | R_POLYGON,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildFontPipeline() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0),
|
||||
GLStateManager.getColorMask(), GLStateManager.getCullState(),
|
||||
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_POLYGON,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildHand() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getAlphaState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getTextures().getTextureUnitStates(1)
|
||||
),
|
||||
R_BLEND | R_DEPTH,
|
||||
B_FRAG_COLORBUF,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildBlend() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getBlendState(), GLStateManager.getBlendMode()),
|
||||
R_BLEND,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildAlpha() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getAlphaState(), GLStateManager.getAlphaTest()),
|
||||
0,
|
||||
B_FRAG_COLORBUF,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildCutout() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getAlphaState(), GLStateManager.getAlphaTest(), GLStateManager.getTextures().getTextureUnitStates(1)),
|
||||
0,
|
||||
B_FRAG_COLORBUF,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildMasks() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getDepthState(), GLStateManager.getColorMask()),
|
||||
R_DEPTH | R_COLOR_MASK,
|
||||
0,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildCull() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getPolygonState(), GLStateManager.getCullState()),
|
||||
R_POLYGON,
|
||||
0,
|
||||
false);
|
||||
}
|
||||
|
||||
public static StateSet forMask(int mask) {
|
||||
StateSet cached = maskToStateSetMap.get(mask);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
cached = buildForMask(mask);
|
||||
maskToStateSetMap.put(mask, cached);
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static StateSet buildForMask(int mask) {
|
||||
int restore = 0;
|
||||
int bump = 0;
|
||||
final boolean blendSnapshot = (mask & (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT)) != 0;
|
||||
|
||||
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
restore |= R_DEPTH;
|
||||
}
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
restore |= R_BLEND | R_COLOR_MASK | R_CLEAR_COLOR | R_DRAW_BUFFER | R_LOGIC_OP;
|
||||
bump |= B_FRAG_COLORBUF;
|
||||
}
|
||||
if ((mask & GL11.GL_CURRENT_BIT) != 0) {
|
||||
restore |= R_SECONDARY_COLOR;
|
||||
bump |= B_CURRENT;
|
||||
}
|
||||
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
restore |= R_STENCIL;
|
||||
}
|
||||
if ((mask & GL11.GL_VIEWPORT_BIT) != 0) {
|
||||
restore |= R_VIEWPORT;
|
||||
}
|
||||
if ((mask & GL11.GL_SCISSOR_BIT) != 0) {
|
||||
restore |= R_SCISSOR;
|
||||
}
|
||||
if ((mask & GL11.GL_LINE_BIT) != 0) {
|
||||
restore |= R_LINE;
|
||||
}
|
||||
if ((mask & GL11.GL_POINT_BIT) != 0) {
|
||||
restore |= R_POINT;
|
||||
}
|
||||
if ((mask & GL11.GL_HINT_BIT) != 0) {
|
||||
restore |= R_HINT;
|
||||
}
|
||||
if ((mask & GL11.GL_POLYGON_BIT) != 0) {
|
||||
restore |= R_POLYGON;
|
||||
}
|
||||
if ((mask & GL11.GL_TEXTURE_BIT) != 0) {
|
||||
restore |= R_TEXTURE | R_ACTIVE_UNIT;
|
||||
bump |= B_FRAG_TEXTURE;
|
||||
}
|
||||
if ((mask & GL11.GL_LIGHTING_BIT) != 0) {
|
||||
bump |= B_LIGHTING;
|
||||
}
|
||||
if ((mask & GL11.GL_FOG_BIT) != 0) {
|
||||
bump |= B_FRAG_FOG;
|
||||
}
|
||||
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
|
||||
bump |= B_TRANSFORM;
|
||||
}
|
||||
|
||||
final long[] members = Feature.maskToMembers(mask);
|
||||
|
||||
return new StateSet(mask, members, restore, bump, blendSnapshot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
|
||||
public final class BackendOptions {
|
||||
|
||||
private static boolean configSdlGpu;
|
||||
private static String configDriver = "";
|
||||
private static int configGlVersion;
|
||||
|
||||
private BackendOptions() {}
|
||||
|
||||
public static void latch(boolean sdlGpu, String driver, int glVersion) {
|
||||
configSdlGpu = sdlGpu;
|
||||
configDriver = driver;
|
||||
configGlVersion = glVersion;
|
||||
}
|
||||
|
||||
public static boolean sdlGpuRequested() {
|
||||
return SystemProperties.SDL_GPU_OVERRIDE != null ? SystemProperties.SDL_GPU_OVERRIDE : configSdlGpu;
|
||||
}
|
||||
|
||||
public static String sdlGpuDriver() {
|
||||
return sdlGpuDriver(configDriver);
|
||||
}
|
||||
|
||||
public static String sdlGpuDriver(String driver) {
|
||||
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : driver;
|
||||
}
|
||||
|
||||
public static boolean configSdlGpu() {
|
||||
return configSdlGpu;
|
||||
}
|
||||
|
||||
public static String configDriver() {
|
||||
return configDriver;
|
||||
}
|
||||
|
||||
public static int configGlVersion() {
|
||||
return configGlVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public final class BackendStartGuard {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/BackendStartGuard");
|
||||
|
||||
static final int STRIKE_LIMIT = 3;
|
||||
|
||||
private static File marker;
|
||||
private static File verified;
|
||||
private static String key;
|
||||
private static int strikes;
|
||||
private static boolean pending;
|
||||
private static boolean tripped;
|
||||
|
||||
private BackendStartGuard() {}
|
||||
|
||||
public static boolean begin(File dir, boolean sdlGpuConfigured, boolean flagForced, int requirementsRevision, String requestedDriver) {
|
||||
marker = new File(dir, "sdlgpu-starting");
|
||||
verified = new File(dir, "sdlgpu-verified");
|
||||
key = requirementsRevision + "/" + requestedDriver;
|
||||
pending = tripped = false;
|
||||
if (flagForced) return false;
|
||||
if (!sdlGpuConfigured) {
|
||||
delete(marker);
|
||||
return false;
|
||||
}
|
||||
if (key.equals(read(verified))) return false;
|
||||
final String count = read(marker);
|
||||
final Integer parsed = count == null ? null : Ints.tryParse(count.trim());
|
||||
strikes = parsed == null ? 0 : parsed;
|
||||
if (strikes >= STRIKE_LIMIT) {
|
||||
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; resetting the render backend to OpenGL", strikes);
|
||||
delete(marker);
|
||||
tripped = true;
|
||||
return true;
|
||||
}
|
||||
if (strikes == STRIKE_LIMIT - 1) {
|
||||
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; one more and the render backend switches to OpenGL", strikes);
|
||||
} else if (strikes > 0) {
|
||||
LOGGER.info("The last start ended while bringing up the SDL GPU device");
|
||||
}
|
||||
pending = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void arm() {
|
||||
if (pending) write(marker, Integer.toString(strikes + 1));
|
||||
}
|
||||
|
||||
public static void disarm() {
|
||||
if (pending) delete(marker);
|
||||
}
|
||||
|
||||
public static void latch() {
|
||||
if (!pending) return;
|
||||
pending = false;
|
||||
write(verified, key);
|
||||
LOGGER.info("SDL GPU booted; the startup guard stays off until the requirements revision or driver changes");
|
||||
}
|
||||
|
||||
public static boolean tripped() {
|
||||
return tripped;
|
||||
}
|
||||
|
||||
private static String read(File file) {
|
||||
try {
|
||||
return file.isFile() ? new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8) : null;
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not read {}: {}", file, e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void write(File file, String content) {
|
||||
try {
|
||||
Files.createDirectories(file.toPath().getParent());
|
||||
Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not write {}: {}", file, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void delete(File file) {
|
||||
if (file.isFile() && !file.delete()) LOGGER.warn("Could not delete {}", file);
|
||||
}
|
||||
}
|
||||
+50
-27
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.AMDDebugOutput;
|
||||
import org.lwjgl.opengl.AMDDebugOutputCallback;
|
||||
@@ -10,6 +11,7 @@ import org.lwjgl.opengl.ARBDebugOutputCallback;
|
||||
import org.lwjgl.opengl.ARBDirectStateAccess;
|
||||
import org.lwjgl.opengl.ARBTimerQuery;
|
||||
import org.lwjgl.opengl.ContextCapabilities;
|
||||
import org.lwjgl.opengl.DisplayMode;
|
||||
import org.lwjgl.opengl.EXTDirectStateAccess;
|
||||
import org.lwjgl.opengl.EXTShaderImageLoadStore;
|
||||
import org.lwjgl.opengl.GLContext;
|
||||
@@ -22,6 +24,7 @@ import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL41;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
@@ -39,7 +42,7 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
|
||||
/**
|
||||
* LWJGL2 GL implementation of {@link RenderBackend}.
|
||||
@@ -114,13 +117,31 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
public int getMinGLSLVersion() {return 330;}
|
||||
|
||||
@Override
|
||||
public void setVSyncEnabled(boolean enabled) {Display.setVSyncEnabled(enabled);}
|
||||
protected VSyncMode applyVSyncMode(VSyncMode preferred) {
|
||||
final boolean enabled = preferred.tearFree();
|
||||
Display.setVSyncEnabled(enabled);
|
||||
return enabled ? VSyncMode.ON : VSyncMode.OFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long queryRefreshPeriodNanos() {
|
||||
final DisplayMode mode = Display.getDisplayMode();
|
||||
if (mode != null && mode.getFrequency() > 0) return periodFromIntegerHz(mode.getFrequency());
|
||||
final DisplayMode desktop = Display.getDesktopDisplayMode();
|
||||
return desktop == null ? 0L : periodFromIntegerHz(desktop.getFrequency());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnisotropicSupported() {
|
||||
return caps != null && caps.GL_EXT_texture_filter_anisotropic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayFromWorkerThread(boolean processMessages) {
|
||||
if (processMessages) pumpDisplayMessages();
|
||||
Display.update(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {GL11.glFlush();}
|
||||
|
||||
@@ -633,6 +654,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
GL11.glReadPixels(x, y, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset) {
|
||||
GL11.glReadPixels(x, y, width, height, format, type, pixelBufferOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
|
||||
GL11.glGetTexImage(target, level, format, type, pixels);
|
||||
@@ -783,6 +809,28 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
return GL20.glIsProgram(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsProgramBinary() {
|
||||
final ContextCapabilities caps = GLStateManager.capabilities;
|
||||
return !RenderSystem.isGLES() && (caps.OpenGL41 || caps.GL_ARB_get_program_binary)
|
||||
&& GL11.glGetInteger(GL41.GL_NUM_PROGRAM_BINARY_FORMATS) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void programParameteri(int program, int pname, int value) {
|
||||
GL41.glProgramParameteri(program, pname, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {
|
||||
GL41.glGetProgramBinary(program, length, binaryFormat, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {
|
||||
GL41.glProgramBinary(program, binaryFormat, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateProgram(int program) {
|
||||
GL20.glValidateProgram(program);
|
||||
@@ -1230,31 +1278,6 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
GL42.glTexStorage2D(target, levels, internalFormat, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage2D(int texture, int levels, int internalFormat, int width, int height) {
|
||||
ARBDirectStateAccess.glTextureStorage2D(texture, levels, internalFormat, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texStorage1D(int target, int levels, int internalFormat, int width) {
|
||||
GL42.glTexStorage1D(target, levels, internalFormat, width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texStorage3D(int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
GL42.glTexStorage3D(target, levels, internalFormat, width, height, depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage1D(int texture, int levels, int internalFormat, int width) {
|
||||
ARBDirectStateAccess.glTextureStorage1D(texture, levels, internalFormat, width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage3D(int texture, int levels, int internalFormat, int width, int height, int depth) {
|
||||
ARBDirectStateAccess.glTextureStorage3D(texture, levels, internalFormat, width, height, depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateTextureMipmap(int texture) {
|
||||
ARBDirectStateAccess.glGenerateTextureMipmap(texture);
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.input.Mouse;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public final class MainThreadPump {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("MainThreadPump");
|
||||
|
||||
private static final Tracy.ZoneId Z_DISPLAY_MESSAGES = Tracy.zoneId("displayMessages", Tracy.COLOR_CLIENT);
|
||||
|
||||
private static final long PARK_MILLIS = 20L;
|
||||
private static final long STALL_WARN_NANOS = 5_000_000_000L;
|
||||
|
||||
public static void pollInput() {
|
||||
Keyboard.poll();
|
||||
Mouse.poll();
|
||||
}
|
||||
|
||||
private final Executor mainExecutor;
|
||||
private final BooleanSupplier isMainThread;
|
||||
private final Runnable pumpBody;
|
||||
private final Runnable pollBody;
|
||||
private final Runnable task = this::runPump;
|
||||
private final Object lock = new Object();
|
||||
|
||||
private boolean done;
|
||||
private Throwable failure;
|
||||
private ClassLoader callerLoader;
|
||||
|
||||
private boolean presentPumpPending;
|
||||
private boolean presentInFlight;
|
||||
private Throwable presentFailure;
|
||||
private ClassLoader presentLoader;
|
||||
|
||||
public MainThreadPump(Executor mainExecutor, BooleanSupplier isMainThread, Runnable pumpBody, Runnable pollBody) {
|
||||
this.mainExecutor = mainExecutor;
|
||||
this.isMainThread = isMainThread;
|
||||
this.pumpBody = pumpBody;
|
||||
this.pollBody = pollBody;
|
||||
}
|
||||
|
||||
public void pumpMessages() {
|
||||
Tracy.beginZone(Z_DISPLAY_MESSAGES);
|
||||
try {
|
||||
if (isMainThread.getAsBoolean()) {
|
||||
pumpBody.run();
|
||||
} else {
|
||||
synchronized (lock) {
|
||||
await(true);
|
||||
final Throwable t = presentFailure;
|
||||
presentFailure = null;
|
||||
if (t != null) throw asUnchecked(t);
|
||||
if (!presentInFlight) runPumpOnMainThread();
|
||||
}
|
||||
}
|
||||
pollBody.run();
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private void runPumpOnMainThread() {
|
||||
final Thread self = Thread.currentThread();
|
||||
callerLoader = self.getContextClassLoader();
|
||||
done = false;
|
||||
mainExecutor.execute(task);
|
||||
await(false);
|
||||
final Throwable t = failure;
|
||||
if (t != null) {
|
||||
throw asUnchecked(t);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean waiting(boolean presentPump) {
|
||||
return presentPump ? presentPumpPending : !done;
|
||||
}
|
||||
|
||||
private void await(boolean presentPump) {
|
||||
boolean interrupted = false;
|
||||
boolean stallReported = false;
|
||||
final long stallAt = System.nanoTime() + STALL_WARN_NANOS;
|
||||
while (waiting(presentPump)) {
|
||||
try {
|
||||
lock.wait(PARK_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
interrupted = true;
|
||||
}
|
||||
if (waiting(presentPump) && !stallReported && System.nanoTime() - stallAt >= 0L) {
|
||||
stallReported = true;
|
||||
LOGGER.warn("Display message pump has been waiting on the RFB main thread for over {} ms", STALL_WARN_NANOS / 1_000_000L);
|
||||
}
|
||||
}
|
||||
if (interrupted) Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
public void beginPresent() {
|
||||
synchronized (lock) {
|
||||
presentInFlight = true;
|
||||
presentPumpPending = true;
|
||||
presentLoader = Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
}
|
||||
|
||||
public void runPresentPump() {
|
||||
final ClassLoader loader;
|
||||
synchronized (lock) {
|
||||
loader = presentLoader;
|
||||
}
|
||||
final Throwable t = pumpWith(loader);
|
||||
synchronized (lock) {
|
||||
if (t != null) presentFailure = t;
|
||||
presentPumpPending = false;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void endPresent() {
|
||||
synchronized (lock) {
|
||||
presentInFlight = false;
|
||||
presentPumpPending = false;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void runPump() {
|
||||
final Throwable t = pumpWith(callerLoader);
|
||||
synchronized (lock) {
|
||||
failure = t;
|
||||
done = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private Throwable pumpWith(ClassLoader loader) {
|
||||
final Thread self = Thread.currentThread();
|
||||
final ClassLoader saved = self.getContextClassLoader();
|
||||
try {
|
||||
if (loader != null) self.setContextClassLoader(loader);
|
||||
pumpBody.run();
|
||||
return null;
|
||||
} catch (Throwable t) {
|
||||
return t;
|
||||
} finally {
|
||||
self.setContextClassLoader(saved);
|
||||
}
|
||||
}
|
||||
|
||||
private static RuntimeException asUnchecked(Throwable t) {
|
||||
if (t instanceof RuntimeException runtime) return runtime;
|
||||
if (t instanceof Error error) throw error;
|
||||
return new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class MoltenVK {
|
||||
|
||||
public enum Source {
|
||||
ENV,
|
||||
JAR,
|
||||
SYSTEM,
|
||||
NONE
|
||||
}
|
||||
|
||||
public static final String ENV_VULKAN_LIBRARY = "SDL_VULKAN_LIBRARY";
|
||||
public static final String JAR_NAME = "angelica-moltenvk";
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/MoltenVK");
|
||||
private static final String LIB_NAME = "libMoltenVK.dylib";
|
||||
private static final String[] SYSTEM_PATHS = { "/opt/homebrew/lib/" + LIB_NAME, "/usr/local/lib/" + LIB_NAME };
|
||||
private static final String ARCH = archName(System.getProperty("os.arch", ""));
|
||||
|
||||
private static Source source;
|
||||
private static String path;
|
||||
private static boolean located;
|
||||
|
||||
private MoltenVK() {}
|
||||
|
||||
public static synchronized Source source() {
|
||||
if (source == null) {
|
||||
final boolean macos = System.getProperty("os.name", "").toLowerCase().contains("mac");
|
||||
source = detect(macos, System.getenv(ENV_VULKAN_LIBRARY), MoltenVK.class.getResource(bundledResource(ARCH)) != null, SYSTEM_PATHS);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
public static synchronized String locate() {
|
||||
if (!located) {
|
||||
path = resolve(source(), System.getenv(ENV_VULKAN_LIBRARY), bundledResource(ARCH), new File(SystemProperties.MOLTENVK_DIR), ARCH, SYSTEM_PATHS);
|
||||
located = true;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
static String archName(String osArch) {
|
||||
final String arch = osArch.toLowerCase();
|
||||
return arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64";
|
||||
}
|
||||
|
||||
static String bundledResource(String arch) {
|
||||
return "/macos/" + arch + "/org/lwjgl/vulkan/" + LIB_NAME;
|
||||
}
|
||||
|
||||
static Source detect(boolean macos, String env, boolean bundled, String[] systemPaths) {
|
||||
if (!macos) return Source.NONE;
|
||||
if (env != null && !env.isEmpty()) return Source.ENV;
|
||||
if (bundled) return Source.JAR;
|
||||
return systemPath(systemPaths) != null ? Source.SYSTEM : Source.NONE;
|
||||
}
|
||||
|
||||
static String resolve(Source source, String env, String resource, File dir, String arch, String[] systemPaths) {
|
||||
return switch (source) {
|
||||
case ENV -> env;
|
||||
case JAR -> {
|
||||
final String extracted = extract(resource, dir, arch);
|
||||
yield extracted != null ? extracted : systemPath(systemPaths);
|
||||
}
|
||||
case SYSTEM -> systemPath(systemPaths);
|
||||
case NONE -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private static String systemPath(String[] systemPaths) {
|
||||
for (final String candidate : systemPaths) {
|
||||
if (new File(candidate).isFile()) return candidate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String extract(String resource, File dir, String arch) {
|
||||
final byte[] lib;
|
||||
try (InputStream in = MoltenVK.class.getResourceAsStream(resource)) {
|
||||
if (in == null) return null;
|
||||
lib = readAll(in);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("MoltenVK: failed reading {}: {}", resource, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
final File target = new File(dir, "libMoltenVK-" + arch + ".dylib");
|
||||
File tmp = null;
|
||||
try {
|
||||
if (!target.isFile() || target.length() != lib.length || !Arrays.equals(Files.readAllBytes(target.toPath()), lib)) {
|
||||
if (!dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
|
||||
tmp = File.createTempFile("libMoltenVK", ".dylib", dir);
|
||||
Files.write(tmp.toPath(), lib);
|
||||
Files.move(tmp.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("MoltenVK: failed extracting to {}: {}", target, e.toString());
|
||||
if (tmp != null) tmp.delete();
|
||||
return null;
|
||||
}
|
||||
return target.getAbsolutePath();
|
||||
}
|
||||
|
||||
private static byte[] readAll(InputStream in) throws IOException {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream(1 << 20);
|
||||
final byte[] buf = new byte[16384];
|
||||
int n;
|
||||
while ((n = in.read(buf)) != -1) {
|
||||
out.write(buf, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLESFormatRemap;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
|
||||
import org.lwjgl.LWJGLException;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -12,6 +18,8 @@ import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Abstract rendering backend.
|
||||
@@ -28,6 +36,9 @@ public abstract class RenderBackend {
|
||||
/** Returns true if the current thread has a valid render context. */
|
||||
public abstract boolean hasContext();
|
||||
|
||||
/** Returns true if the calling thread may issue render commands. */
|
||||
public boolean hasContextOnThread() { return hasContext(); }
|
||||
|
||||
/** Returns true if the calling thread owns the "GL Context" */
|
||||
public abstract boolean isCurrent();
|
||||
|
||||
@@ -48,6 +59,7 @@ public abstract class RenderBackend {
|
||||
|
||||
public boolean framebufferCompletenessIsMeaningful() { return true; }
|
||||
public void onPersistentBufferWrite(int glId, long offset, long size) {}
|
||||
public boolean isBufferImmutable(int glId) { return false; }
|
||||
|
||||
public boolean isAnisotropicSupported() { return true; }
|
||||
|
||||
@@ -62,7 +74,176 @@ public abstract class RenderBackend {
|
||||
public abstract void flush();
|
||||
public abstract void finish();
|
||||
|
||||
public abstract void setVSyncEnabled(boolean enabled);
|
||||
private final ByteBuffer[] clientStagingCache = new ByteBuffer[16];
|
||||
private VSyncMode effectiveVSyncMode = VSyncMode.ON;
|
||||
private VSyncMode preferredTearFreeMode;
|
||||
|
||||
public final void setVSyncEnabled(boolean enabled) {
|
||||
setVSyncMode(enabled ? preferredTearFreeMode() : VSyncMode.OFF);
|
||||
}
|
||||
|
||||
public final void setVSyncMode(VSyncMode preferred) {
|
||||
setVSyncPreference(preferred);
|
||||
final VSyncMode mode = applyVSyncMode(preferred);
|
||||
vsyncHonored = applyHonored();
|
||||
publishVSyncMode(mode);
|
||||
}
|
||||
|
||||
public final void setVSyncPreference(VSyncMode preferred) {
|
||||
if (preferred.tearFree()) preferredTearFreeMode = preferred;
|
||||
}
|
||||
|
||||
public final VSyncMode preferredTearFreeMode() {
|
||||
return resolveTearFreeMode(preferredTearFreeMode, this::supportsVSyncMode);
|
||||
}
|
||||
|
||||
static VSyncMode resolveTearFreeMode(VSyncMode preferred, Predicate<VSyncMode> supported) {
|
||||
if (preferred != null && preferred.tearFree() && supported.test(preferred)) return preferred;
|
||||
return supported.test(VSyncMode.MAILBOX) ? VSyncMode.MAILBOX : VSyncMode.ON;
|
||||
}
|
||||
|
||||
protected abstract VSyncMode applyVSyncMode(VSyncMode preferred);
|
||||
|
||||
protected boolean applyHonored() { return true; }
|
||||
|
||||
private boolean vsyncHonored = true;
|
||||
|
||||
public final boolean vsyncHonored() { return vsyncHonored; }
|
||||
|
||||
public boolean supportsVSyncMode(VSyncMode mode) { return mode != VSyncMode.MAILBOX; }
|
||||
|
||||
protected final void publishVSyncMode(VSyncMode mode) {
|
||||
effectiveVSyncMode = mode;
|
||||
bumpDisplayGeneration();
|
||||
}
|
||||
|
||||
public final VSyncMode getEffectiveVSyncMode() { return effectiveVSyncMode; }
|
||||
|
||||
private int displayGeneration;
|
||||
|
||||
public final int displayGeneration() { return displayGeneration; }
|
||||
|
||||
protected final void bumpDisplayGeneration() { displayGeneration++; }
|
||||
|
||||
private static final long REFRESH_POLL_NANOS = 1_000_000_000L;
|
||||
private static final long MIN_PLAUSIBLE_PERIOD_NANOS = 1_000_000L;
|
||||
private static final long MAX_PLAUSIBLE_PERIOD_NANOS = 50_000_000L;
|
||||
|
||||
private long refreshPollNanos;
|
||||
private long cachedRefreshPeriodNanos;
|
||||
|
||||
public final long refreshPeriodNanos() {
|
||||
final long now = System.nanoTime();
|
||||
if (refreshPollNanos == 0L || now - refreshPollNanos >= REFRESH_POLL_NANOS) {
|
||||
refreshPollNanos = now;
|
||||
final long fresh = plausiblePeriodNanos(queryRefreshPeriodNanos());
|
||||
if (fresh != cachedRefreshPeriodNanos) bumpDisplayGeneration();
|
||||
cachedRefreshPeriodNanos = fresh;
|
||||
}
|
||||
return cachedRefreshPeriodNanos;
|
||||
}
|
||||
|
||||
protected long queryRefreshPeriodNanos() { return 0L; }
|
||||
|
||||
static long plausiblePeriodNanos(long periodNanos) {
|
||||
return (periodNanos >= MIN_PLAUSIBLE_PERIOD_NANOS && periodNanos <= MAX_PLAUSIBLE_PERIOD_NANOS) ? periodNanos : 0L;
|
||||
}
|
||||
|
||||
public static long periodFromRational(int numerator, int denominator, float fallbackHz) {
|
||||
if (numerator > 0 && denominator > 0) {
|
||||
final long scaledDenominator = 1_000_000_000L * denominator;
|
||||
return (scaledDenominator + numerator / 2) / numerator;
|
||||
}
|
||||
return fallbackHz > 0.0f ? Math.round(1_000_000_000.0 / fallbackHz) : 0L;
|
||||
}
|
||||
|
||||
private static final int[] TRUNCATED_NTSC_HZ = {23, 29, 59, 71, 119, 143, 239};
|
||||
|
||||
private static boolean isTruncatedNtscRate(int hz) {
|
||||
for (int candidate : TRUNCATED_NTSC_HZ) {
|
||||
if (candidate == hz) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static long periodFromIntegerHz(int hz) {
|
||||
if (hz <= 0) return 0L;
|
||||
if (isTruncatedNtscRate(hz)) {
|
||||
final long denominator = (hz + 1) * 1000L;
|
||||
return Math.round(1_000_000_000.0 * 1001 / denominator);
|
||||
}
|
||||
return Math.round(1_000_000_000.0 / hz);
|
||||
}
|
||||
|
||||
public static int hzFromPeriod(long periodNanos) {
|
||||
return periodNanos <= 0L ? 0 : (int) Math.round(1_000_000_000.0 / periodNanos);
|
||||
}
|
||||
|
||||
public boolean hasSwapchainBackpressure() { return false; }
|
||||
|
||||
public static final class GateSample {
|
||||
public long durationNanos;
|
||||
public long endNanos;
|
||||
public long gpuWaitNanos;
|
||||
}
|
||||
|
||||
static final class GateRecorder {
|
||||
|
||||
private long gateDurationNanos;
|
||||
private long gateEndNanos;
|
||||
private long gpuWaitNanos;
|
||||
|
||||
synchronized void recordGate(long durationNanos, long endNanos) {
|
||||
if (gateEndNanos != 0L && gateDurationNanos >= durationNanos) return;
|
||||
gateDurationNanos = durationNanos;
|
||||
gateEndNanos = endNanos;
|
||||
}
|
||||
|
||||
synchronized void recordGpuWait(long nanos) {
|
||||
gpuWaitNanos += nanos;
|
||||
}
|
||||
|
||||
synchronized void readGateSample(GateSample out) {
|
||||
final long end = gateEndNanos;
|
||||
out.durationNanos = end == 0L ? 0L : gateDurationNanos;
|
||||
out.endNanos = end;
|
||||
out.gpuWaitNanos = gpuWaitNanos;
|
||||
gpuWaitNanos = 0L;
|
||||
gateEndNanos = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private final GateRecorder gateRecorder = new GateRecorder();
|
||||
|
||||
public boolean wantsDisplayUpdateGateTiming() { return true; }
|
||||
|
||||
public final void recordGate(long durationNanos, long endNanos) {
|
||||
gateRecorder.recordGate(durationNanos, endNanos);
|
||||
}
|
||||
|
||||
public final void recordGpuWait(long nanos) {
|
||||
gateRecorder.recordGpuWait(nanos);
|
||||
}
|
||||
|
||||
public final void readGateSample(GateSample out) {
|
||||
gateRecorder.readGateSample(out);
|
||||
}
|
||||
|
||||
public void awaitPresent() {}
|
||||
|
||||
public void parkNanos(long nanos) {
|
||||
LockSupport.parkNanos(nanos);
|
||||
}
|
||||
|
||||
public void pumpDisplayMessages() {
|
||||
Display.processMessages();
|
||||
}
|
||||
|
||||
private boolean presentSuppressed;
|
||||
|
||||
public final void setPresentSuppressed(boolean suppressed) { presentSuppressed = suppressed; }
|
||||
|
||||
public final boolean isPresentSuppressed() { return presentSuppressed; }
|
||||
|
||||
public void onFrameBegin() {
|
||||
if (GLStateManager.takeStateSeedPending()) {
|
||||
@@ -70,23 +251,37 @@ public abstract class RenderBackend {
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateStateMirror() {}
|
||||
|
||||
public void onFrameEnd() {}
|
||||
|
||||
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
|
||||
public void onPreSwapchainInvalidatingChange(Object change) {}
|
||||
public void onPreSwapchainInvalidatingChange(Object change) { bumpDisplayGeneration(); }
|
||||
|
||||
public boolean handleMakeCurrent(Object drawable) { return false; }
|
||||
public boolean handleReleaseContext(Object drawable) { return false; }
|
||||
public boolean handleSwapBuffers() { return false; }
|
||||
public void onRenderThreadReleased(Thread thread) {}
|
||||
|
||||
public void updateDisplayFromWorkerThread(boolean processMessages) throws LWJGLException {
|
||||
GLStateManager.swapBuffers();
|
||||
if (processMessages) {
|
||||
pumpDisplayMessages();
|
||||
}
|
||||
}
|
||||
|
||||
public String getTransferDebugInfo() { return null; }
|
||||
|
||||
public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) { return false; }
|
||||
public long beginVoxelizationBatch(int ssboBinding) { return 0L; }
|
||||
public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {}
|
||||
public void voxelizeRegion(long pass, boolean rebindVertexBuffer, float x, float y, float z, int rangeBase, int rangeCount, int vertexTotal) {}
|
||||
public void endVoxelizationBatch(long pass) {}
|
||||
|
||||
public void setVertexWriteReplay(int graphicsProgram, VertexWriteReplaySetup setup) {}
|
||||
|
||||
public boolean supportsComputeImageAtomics() { return true; }
|
||||
|
||||
public boolean supportsComputeImageAtomics(int width, int height, int depth) { return supportsComputeImageAtomics(); }
|
||||
|
||||
public abstract void enable(int cap);
|
||||
public abstract void enablei(int cap, int index);
|
||||
public abstract void disable(int cap);
|
||||
@@ -170,6 +365,71 @@ public abstract class RenderBackend {
|
||||
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {}
|
||||
public void texSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {}
|
||||
public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {}
|
||||
|
||||
public TextureStaging beginTextureStaging(int level, int x, int y, int width, int height) {
|
||||
final int size = Math.multiplyExact(Math.multiplyExact(width, height), 4);
|
||||
ByteBuffer buffer = null;
|
||||
for (int i = 0; i < clientStagingCache.length; i++) {
|
||||
final ByteBuffer cached = clientStagingCache[i];
|
||||
if (cached != null && cached.capacity() == size) {
|
||||
clientStagingCache[i] = null;
|
||||
buffer = cached;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (buffer == null) buffer = MemoryUtilities.memAlloc(size);
|
||||
buffer.clear();
|
||||
return new ClientTextureStaging(buffer, level, x, y, width, height);
|
||||
}
|
||||
|
||||
public boolean commitTextureStaging(TextureStaging staging) {
|
||||
final int type = RenderSystem.isGLES() ? GLESFormatRemap.remapPixelType(GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV) : GL12.GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
try {
|
||||
final ClientTextureStaging s = (ClientTextureStaging) staging;
|
||||
texSubImage2D(GL11.GL_TEXTURE_2D, s.level(), s.x(), s.y(), s.width(), s.height(), GL12.GL_BGRA, type, s.buffer());
|
||||
} finally {
|
||||
recycleClientStaging(staging.buffer());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void abandonTextureStaging(TextureStaging staging) {
|
||||
recycleClientStaging(staging.buffer());
|
||||
}
|
||||
|
||||
public void trimTextureStaging() {
|
||||
for (int i = 0; i < clientStagingCache.length; i++) {
|
||||
MemoryUtilities.memFree(clientStagingCache[i]);
|
||||
clientStagingCache[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private record ClientTextureStaging(ByteBuffer buffer, int level, int x, int y, int width, int height) implements TextureStaging {
|
||||
@Override
|
||||
public boolean bgra() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void recycleClientStaging(ByteBuffer buffer) {
|
||||
int victim = -1;
|
||||
for (int i = 0; i < clientStagingCache.length; i++) {
|
||||
final ByteBuffer cached = clientStagingCache[i];
|
||||
if (cached == null) {
|
||||
clientStagingCache[i] = buffer;
|
||||
return;
|
||||
}
|
||||
if (cached.capacity() < buffer.capacity() && (victim < 0 || cached.capacity() < clientStagingCache[victim].capacity())) {
|
||||
victim = i;
|
||||
}
|
||||
}
|
||||
if (victim < 0) {
|
||||
MemoryUtilities.memFree(buffer);
|
||||
return;
|
||||
}
|
||||
MemoryUtilities.memFree(clientStagingCache[victim]);
|
||||
clientStagingCache[victim] = buffer;
|
||||
}
|
||||
public void texSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {}
|
||||
public void copyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {}
|
||||
public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {}
|
||||
@@ -212,6 +472,7 @@ public abstract class RenderBackend {
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
|
||||
@@ -253,6 +514,10 @@ public abstract class RenderBackend {
|
||||
public abstract int getUniformLocation(int program, ByteBuffer name);
|
||||
public boolean isShader(int obj) { return false; }
|
||||
public boolean isProgram(int obj) { return false; }
|
||||
public boolean supportsProgramBinary() { return false; }
|
||||
public void programParameteri(int program, int pname, int value) {}
|
||||
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {}
|
||||
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {}
|
||||
public void validateProgram(int program) {}
|
||||
public void getAttachedShaders(int program, IntBuffer count, IntBuffer shaders) {
|
||||
if (count != null) count.put(0, 0);
|
||||
@@ -361,12 +626,7 @@ public abstract class RenderBackend {
|
||||
public abstract void textureParameteri(int texture, int target, int pname, int param);
|
||||
public abstract void textureParameterf(int texture, int target, int pname, float param);
|
||||
public abstract void textureParameteriv(int texture, int target, int pname, IntBuffer params);
|
||||
public abstract void texStorage1D(int target, int levels, int internalFormat, int width);
|
||||
public abstract void texStorage2D(int target, int levels, int internalFormat, int width, int height);
|
||||
public abstract void texStorage3D(int target, int levels, int internalFormat, int width, int height, int depth);
|
||||
public abstract void textureStorage1D(int texture, int levels, int internalFormat, int width);
|
||||
public abstract void textureStorage2D(int texture, int levels, int internalFormat, int width, int height);
|
||||
public abstract void textureStorage3D(int texture, int levels, int internalFormat, int width, int height, int depth);
|
||||
public abstract void generateTextureMipmap(int texture);
|
||||
public abstract void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
|
||||
public abstract void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
public enum VSyncMode {
|
||||
OFF,
|
||||
ON,
|
||||
MAILBOX;
|
||||
|
||||
public boolean tearFree() {
|
||||
return this != OFF;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
/**
|
||||
* How to replay a graphics program's vertex-stage image writes.
|
||||
*
|
||||
* @param inputLocations per input, the declared location or -1 for whatever the graphics program linked
|
||||
* @param images per image the replay uses, in binding order: the written ones, then the read-only ones
|
||||
* @param writtenImageCount how many of {@code images} the replay writes
|
||||
* @param samplerNames samplers the graphics program may not have, because only the stripped writes read them
|
||||
* @param samplerUnits per sampler name, the texture unit it reads at each draw, or -1 to use samplerTextures
|
||||
* @param samplerTextures per sampler name, its texture when samplerUnits is -1
|
||||
* @param samplerObjects per sampler name, the GL sampler object to sample it with when samplerUnits is -1, or 0
|
||||
* @param vertexBufferBinding storage buffer binding of the first vertex buffer the replay program declares
|
||||
* @param vertexBufferCount how many vertex buffers it declares, at consecutive bindings
|
||||
* @param indexBufferBinding storage buffer binding of its index buffer
|
||||
*/
|
||||
public record VertexWriteReplaySetup(int replayProgram, String[] inputNames, int[] inputLocations,
|
||||
IntSupplier[] images, int writtenImageCount,
|
||||
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, int[] samplerObjects,
|
||||
int vertexBufferBinding, int vertexBufferCount, int indexBufferBinding) {
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -73,7 +74,8 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public int getTexParameteri(int texture, int target, int pname) {
|
||||
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname));
|
||||
final long v = GLStateManager.lookupTexParameteri(TextureInfoCache.INSTANCE.getInfo(texture), pname);
|
||||
return v != GLStateManager.TEX_PARAM_MISS ? (int) v : RENDER_BACKEND.getTextureParameteri(texture, target, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,7 +106,9 @@ public class DSAARB extends DSAUnsupported {
|
||||
super.bindTextureToUnit(target != 0 ? target : GL11.GL_TEXTURE_2D, unit, texture);
|
||||
} else {
|
||||
RENDER_BACKEND.bindTextureUnit(unit, texture);
|
||||
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture, target);
|
||||
final var binding = GLStateManager.getTextures().getTextureUnitBindings(unit);
|
||||
binding.beforeModify();
|
||||
binding.setBinding(texture, target);
|
||||
GLStateManager.trackMaxBoundTextureUnit(unit);
|
||||
}
|
||||
}
|
||||
@@ -129,26 +133,13 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public int createFramebuffer() {
|
||||
GLStateManager.requireContext("glCreateFramebuffers");
|
||||
return RENDER_BACKEND.createFramebuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createTexture(int target) {
|
||||
GLStateManager.requireContext("glCreateTextures");
|
||||
return RENDER_BACKEND.createTextures(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
RENDER_BACKEND.textureStorage1D(texture, levels, internalFormat, width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
RENDER_BACKEND.textureStorage2D(texture, levels, internalFormat, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
RENDER_BACKEND.textureStorage3D(texture, levels, internalFormat, width, height, depth);
|
||||
}
|
||||
}
|
||||
@@ -40,9 +40,4 @@ public interface DSAAccess {
|
||||
|
||||
int createFramebuffer();
|
||||
int createTexture(int target);
|
||||
|
||||
// Texture storage methods for custom images
|
||||
void textureStorage1D(int texture, int target, int levels, int internalFormat, int width);
|
||||
void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height);
|
||||
void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.TextureBindingStack;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
@@ -23,16 +24,15 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
final int cachedBinding = previousBinding(target);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding); // Restore to cache value
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
final int cachedBinding = previousBinding(target);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding);
|
||||
@@ -40,7 +40,7 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
final int cachedBinding = previousBinding(target);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding);
|
||||
@@ -48,7 +48,7 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
final int cachedBinding = previousBinding(target);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding);
|
||||
@@ -56,20 +56,23 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public void texParameteri(int texture, int target, int pname, int param) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
final int previous = bindForEdit(target, texture);
|
||||
GLStateManager.glTexParameteri(target, pname, param);
|
||||
restoreBinding(target, previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameterf(int texture, int target, int pname, float param) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
final int previous = bindForEdit(target, texture);
|
||||
GLStateManager.glTexParameterf(target, pname, param);
|
||||
restoreBinding(target, previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
final int previous = bindForEdit(target, texture);
|
||||
GLStateManager.glTexParameter(target, pname, params);
|
||||
restoreBinding(target, previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,19 +89,23 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public int getTexParameteri(int texture, int target, int pname) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
return GLStateManager.glGetTexParameteri(target, pname);
|
||||
final int previous = bindForEdit(target, texture);
|
||||
final int result = GLStateManager.glGetTexParameteri(target, pname);
|
||||
restoreBinding(target, previous);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTexParameterf(int texture, int target, int pname) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
return GLStateManager.glGetTexParameterf(target, pname);
|
||||
final int previous = bindForEdit(target, texture);
|
||||
final float result = GLStateManager.glGetTexParameterf(target, pname);
|
||||
restoreBinding(target, previous);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
final int previous = GLStateManager.getBoundTextureForServerState();
|
||||
final int previous = previous2D();
|
||||
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
final int result = RENDER_BACKEND.getTexLevelParameteri(GL11.GL_TEXTURE_2D, level, pname);
|
||||
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, previous);
|
||||
@@ -107,7 +114,7 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
final int previous = GLStateManager.getBoundTextureForServerState();
|
||||
final int previous = previous2D();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, destTexture);
|
||||
RENDER_BACKEND.copyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
|
||||
@@ -158,32 +165,44 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public int createTexture(int target) {
|
||||
final int texture = RENDER_BACKEND.genTextures();
|
||||
final int texture = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
final int previous = RENDER_BACKEND.getInteger(GL11.GL_TEXTURE_BINDING_1D);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texStorage1D(target, levels, internalFormat, width);
|
||||
RENDER_BACKEND.bindTexture(target, previous);
|
||||
private static int bindingQuery(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_TEXTURE_1D -> GL11.GL_TEXTURE_BINDING_1D;
|
||||
case GL12.GL_TEXTURE_3D -> GL12.GL_TEXTURE_BINDING_3D;
|
||||
case GL13.GL_TEXTURE_CUBE_MAP -> GL13.GL_TEXTURE_BINDING_CUBE_MAP;
|
||||
case GL30.GL_TEXTURE_2D_ARRAY -> GL30.GL_TEXTURE_BINDING_2D_ARRAY;
|
||||
case GL31.GL_TEXTURE_RECTANGLE -> GL31.GL_TEXTURE_BINDING_RECTANGLE;
|
||||
default -> GL11.GL_TEXTURE_BINDING_2D;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
final int previous = (target == GL11.GL_TEXTURE_2D) ? GLStateManager.getBoundTextureForServerState() : RENDER_BACKEND.getInteger(GL31.GL_TEXTURE_BINDING_RECTANGLE);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texStorage2D(target, levels, internalFormat, width, height);
|
||||
RENDER_BACKEND.bindTexture(target, previous);
|
||||
private static int previousBinding(int target) {
|
||||
return target == GL11.GL_TEXTURE_2D ? previous2D() : RENDER_BACKEND.getInteger(bindingQuery(target));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
final int previous = RENDER_BACKEND.getInteger(GL12.GL_TEXTURE_BINDING_3D);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texStorage3D(target, levels, internalFormat, width, height, depth);
|
||||
RENDER_BACKEND.bindTexture(target, previous);
|
||||
private static int previous2D() {
|
||||
if (GLStateManager.isCachingEnabled()) {
|
||||
final TextureBindingStack slot = GLStateManager.getTextures().getTextureUnitBindings(GLStateManager.getActiveTextureUnit());
|
||||
final int t = slot.getTarget();
|
||||
if (t == 0 || t == GL11.GL_TEXTURE_2D) return slot.getBinding();
|
||||
}
|
||||
return RENDER_BACKEND.getInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
}
|
||||
|
||||
private static int bindForEdit(int target, int texture) {
|
||||
final int previous = previousBinding(target);
|
||||
if (target == GL11.GL_TEXTURE_2D) GLStateManager.glBindTexture(target, texture);
|
||||
else RENDER_BACKEND.bindTexture(target, texture);
|
||||
return previous;
|
||||
}
|
||||
|
||||
private static void restoreBinding(int target, int previous) {
|
||||
if (target == GL11.GL_TEXTURE_2D) GLStateManager.glBindTexture(target, previous);
|
||||
else RENDER_BACKEND.bindTexture(target, previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
public final class CombinedGlint {
|
||||
private static final Matrix4f savedMatrix = new Matrix4f();
|
||||
private static final Matrix4f inverse = new Matrix4f();
|
||||
private static final Matrix4f relative = new Matrix4f();
|
||||
private static boolean active;
|
||||
private static boolean replaceAlpha;
|
||||
|
||||
private CombinedGlint() {}
|
||||
|
||||
public static boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public static boolean replacesAlpha() {
|
||||
return active && replaceAlpha;
|
||||
}
|
||||
|
||||
public static boolean begin(Matrix4fc secondTextureMatrix) {
|
||||
if (active || GLStateManager.getActiveProgram() != 0 || GLStateManager.getLightingState().isEnabled() || GLStateManager.getColorSumState().isEnabled() || !GLStateManager.glIsEnabled(GL11.GL_BLEND)) return false;
|
||||
final var blend = GLStateManager.getBlendState();
|
||||
final boolean lastAlpha = blend.getSrcAlpha() == GL11.GL_ONE && blend.getDstAlpha() == GL11.GL_ZERO && blend.getEquationAlpha() == GL14.GL_FUNC_ADD;
|
||||
final boolean preserveAlpha = blend.getSrcAlpha() == GL11.GL_ZERO && blend.getDstAlpha() == GL11.GL_ONE && (blend.getEquationAlpha() == GL14.GL_FUNC_ADD || blend.getEquationAlpha() == GL14.GL_FUNC_REVERSE_SUBTRACT);
|
||||
if (blend.getSrcRgb() != GL11.GL_SRC_COLOR || blend.getDstRgb() != GL11.GL_ONE
|
||||
|| (!lastAlpha && !preserveAlpha && (blend.getSrcAlpha() != GL11.GL_SRC_COLOR || blend.getDstAlpha() != GL11.GL_ONE || blend.getEquationAlpha() != blend.getEquationRgb()))
|
||||
|| (blend.getEquationRgb() != GL14.GL_FUNC_ADD && blend.getEquationRgb() != GL14.GL_FUNC_REVERSE_SUBTRACT)) return false;
|
||||
final var textures = GLStateManager.getTextures();
|
||||
if (!textures.getTextureUnitStates(0).isEnabled() || textures.getTextureUnitBindings(0).getBinding() == 0 || textures.getTexEnvState(0).mode != GL11.GL_MODULATE) return false;
|
||||
if (textures.getTextureUnitStates(1).isEnabled() && textures.getTextureUnitBindings(1).getBinding() != 0 && textures.getTexEnvState(1).mode != GL11.GL_MODULATE) return false;
|
||||
for (int unit = 2; unit < 4; unit++) {
|
||||
if (textures.getTextureUnitStates(unit).isEnabled() && textures.getTextureUnitBindings(unit).getBinding() != 0) return false;
|
||||
}
|
||||
inverse.set(textures.getTextureUnitMatrix(0)).invert();
|
||||
relative.set(secondTextureMatrix).mul(inverse);
|
||||
if (!relative.isFinite()) return false;
|
||||
savedMatrix.set(textures.getTextureUnitMatrix(3));
|
||||
GLStateManager.setTextureMatrix(3, relative);
|
||||
replaceAlpha = lastAlpha;
|
||||
active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void end() {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
GLStateManager.setTextureMatrix(3, savedMatrix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class CubeInstancedAttribs {
|
||||
|
||||
public static final int LOC_LIGHTMAP_SCALE = InstancedAttribs.LOC_LIGHTMAP;
|
||||
public static final int LOC_CUBE_TEX = 15;
|
||||
|
||||
public static final int OFFSET_LIGHTMAP_SCALE = InstancedAttribs.HEAD_SIZE;
|
||||
public static final int OFFSET_CUBE_TEX = OFFSET_LIGHTMAP_SCALE + 16;
|
||||
public static final int STRIDE = OFFSET_CUBE_TEX + 16;
|
||||
|
||||
public static void pointInstanceAttribs(long base) {
|
||||
InstancedAttribs.pointHead(base, STRIDE);
|
||||
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP_SCALE, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP_SCALE);
|
||||
GLStateManager.glVertexAttribPointer(LOC_CUBE_TEX, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_CUBE_TEX);
|
||||
}
|
||||
|
||||
static void enableInstanceArrays() {
|
||||
InstancedAttribs.enableHeadArrays();
|
||||
InstancedAttribs.enableInstanced(LOC_CUBE_TEX);
|
||||
}
|
||||
|
||||
private CubeInstancedAttribs() {}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
|
||||
public record CubeParams(float minX, float minY, float minZ, float spanX, float spanY, float spanZ, float u0, float v0, float uScaleX, float vScaleY, float uScaleZ, float vScaleZ) {
|
||||
|
||||
private static final float MIN_SPAN = 1.0e-4f;
|
||||
|
||||
public CubeParams {
|
||||
if (!(spanX > MIN_SPAN && spanY > MIN_SPAN && spanZ > MIN_SPAN)) throw new IllegalArgumentException("degenerate cube span");
|
||||
}
|
||||
|
||||
public static CubeParams of(float texWidth, float texHeight, boolean mirror, int texU, int texV, float x, float y, float z, int sizeX, int sizeY, int sizeZ, float inflate) {
|
||||
if (texWidth <= 0.0f || texHeight <= 0.0f) return null;
|
||||
if (sizeX < 0 || sizeY < 0 || sizeZ < 0) return null;
|
||||
if (mirror && sizeX == 0) return null;
|
||||
final float minX = x - inflate;
|
||||
final float minY = y - inflate;
|
||||
final float minZ = z - inflate;
|
||||
final float spanX = ((x + sizeX) + inflate) - minX;
|
||||
final float spanY = ((y + sizeY) + inflate) - minY;
|
||||
final float spanZ = ((z + sizeZ) + inflate) - minZ;
|
||||
if (spanX <= MIN_SPAN || spanY <= MIN_SPAN || spanZ <= MIN_SPAN) return null;
|
||||
return new CubeParams(minX, minY, minZ, spanX, spanY, spanZ, texU / texWidth, texV / texHeight, (mirror ? -sizeX : sizeX) / texWidth, sizeY / texHeight, sizeZ / texWidth, sizeZ / texHeight);
|
||||
}
|
||||
|
||||
public void writeRows(long ptr, float[] mv, int off, float scale) {
|
||||
final float tx = minX * scale;
|
||||
final float ty = minY * scale;
|
||||
final float tz = minZ * scale;
|
||||
final float sx = spanX * scale;
|
||||
final float sy = spanY * scale;
|
||||
final float sz = spanZ * scale;
|
||||
for (int r = 0; r < 3; r++) {
|
||||
final float c0 = mv[off + r];
|
||||
final float c1 = mv[off + 4 + r];
|
||||
final float c2 = mv[off + 8 + r];
|
||||
final long row = ptr + InstancedAttribs.OFFSET_ROWS + r * 16L;
|
||||
memPutFloat(row, c0 * sx);
|
||||
memPutFloat(row + 4, c1 * sy);
|
||||
memPutFloat(row + 8, c2 * sz);
|
||||
memPutFloat(row + 12, mv[off + 12 + r] + c0 * tx + c1 * ty + c2 * tz);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTexture(long ptr) {
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, u0);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, v0);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY);
|
||||
}
|
||||
}
|
||||
@@ -103,9 +103,16 @@ public final class FFPUniformBlock {
|
||||
public static final int TEX_ENV_COLOR_2 = vec4("u_TexEnvColor2");
|
||||
public static final int TEX_ENV_COLOR_3 = vec4("u_TexEnvColor3");
|
||||
public static final int OVERLAY_COLOR = vec4("u_OverlayColor");
|
||||
public static final int SECONDARY_COLOR = vec3("u_SecondaryColor");
|
||||
public static final int FOG_PARAMS = vec4("u_FogParams");
|
||||
public static final int FOG_COLOR = vec4("u_FogColor");
|
||||
|
||||
// Rain and snow, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
|
||||
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (rainScroll, snowScroll, rainStrength, unused).
|
||||
public static final int WEATHER_PARAMS_0 = vec4("u_WeatherParams0");
|
||||
public static final int WEATHER_PARAMS_1 = vec4("u_WeatherParams1");
|
||||
public static final int WEATHER_PARAMS_2 = vec4("u_WeatherParams2");
|
||||
|
||||
public static final int SIZE;
|
||||
public static final String GLSL_DECL;
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
public final class FFPVertexLighting {
|
||||
|
||||
private static final Vector3f scratchNormal = new Vector3f();
|
||||
private static final Vector3f scratchLightVec = new Vector3f();
|
||||
private static final Vector4f scratchEyePos = new Vector4f();
|
||||
private static final Matrix3f scratchNormalMat = new Matrix3f();
|
||||
private static final Vector3f scratchVec = new Vector3f();
|
||||
|
||||
private FFPVertexLighting() {}
|
||||
|
||||
public static boolean modulatesVertexColor(Vector3f destFactor) {
|
||||
destFactor.set(1.0f);
|
||||
|
||||
if (!GLStateManager.getLightingState().isEnabled()) return false;
|
||||
if (!GLStateManager.getColorMaterial().isEnabled()) return false;
|
||||
if (VertexKey.encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue()) != VertexKey.CM_AMBIENT_AND_DIFFUSE) return false;
|
||||
|
||||
final MaterialState material = GLStateManager.getFrontMaterial();
|
||||
if (material.emission.x != 0.0f || material.emission.y != 0.0f || material.emission.z != 0.0f)
|
||||
return false;
|
||||
|
||||
final boolean materialHasSpecular = material.shininess > 0.0f && (material.specular.x != 0.0f || material.specular.y != 0.0f || material.specular.z != 0.0f);
|
||||
|
||||
final Vector4f lightModelAmbient = GLStateManager.getLightModel().ambient;
|
||||
float r = lightModelAmbient.x;
|
||||
float g = lightModelAmbient.y;
|
||||
float b = lightModelAmbient.z;
|
||||
|
||||
final var lightEnables = GLStateManager.getLightStates();
|
||||
final var lightData = GLStateManager.getLightDataStates();
|
||||
boolean normalReady = false;
|
||||
|
||||
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
|
||||
if (!lightEnables[i].isEnabled()) continue;
|
||||
final LightState light = lightData[i];
|
||||
if (materialHasSpecular && (light.specular.x != 0.0f || light.specular.y != 0.0f || light.specular.z != 0.0f))
|
||||
return false;
|
||||
if (!normalReady) {
|
||||
transformCurrentNormal();
|
||||
normalReady = true;
|
||||
}
|
||||
final float nDotVP = Math.max(scratchNormal.dot(lightDirection(light)), 0.0f);
|
||||
r += light.ambient.x + nDotVP * light.diffuse.x;
|
||||
g += light.ambient.y + nDotVP * light.diffuse.y;
|
||||
b += light.ambient.z + nDotVP * light.diffuse.z;
|
||||
}
|
||||
|
||||
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
|
||||
if (!Float.isFinite(r + g + b)) return false;
|
||||
destFactor.set(r, g, b);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void transformCurrentNormal() {
|
||||
GLStateManager.getModelViewMatrix().normal(scratchNormalMat);
|
||||
scratchNormal.set(ShaderManager.getCurrentNormal());
|
||||
scratchNormalMat.transform(scratchNormal);
|
||||
if (GLStateManager.getNormalizeState().isEnabled()) {
|
||||
scratchNormal.normalize();
|
||||
} else if (GLStateManager.getRescaleNormalState().isEnabled()) {
|
||||
scratchNormal.mul(Uniforms.rescaleFactor(scratchNormalMat, scratchVec));
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3f lightDirection(LightState light) {
|
||||
if (light.position.w == 0.0f) {
|
||||
scratchLightVec.set(light.position.x, light.position.y, light.position.z);
|
||||
} else {
|
||||
scratchEyePos.set(0.0f, 0.0f, 0.0f, 1.0f).mul(GLStateManager.getModelViewMatrix());
|
||||
scratchLightVec.set(light.position.x - scratchEyePos.x, light.position.y - scratchEyePos.y, light.position.z - scratchEyePos.z);
|
||||
}
|
||||
return scratchLightVec.normalize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Supplies {@code mc_midTexCoord}/{@code at_tangent} for mod geometry drawn through the FFPclient-array path
|
||||
* ({@code glVertexPointer}/{@code glTexCoordPointer} + {@code glDrawArrays}) out of a VBO, which bypasses the
|
||||
* Tessellator and display-list ext-injection paths.
|
||||
*
|
||||
* <p>Tangents are reconstructed from the flat face normal (handler's {@code normalOffset < 0} path).
|
||||
*/
|
||||
public final class FfpExtendedAttribs {
|
||||
|
||||
private FfpExtendedAttribs() {}
|
||||
|
||||
private static final int POSITION_LOC = Usage.POSITION.getAttributeLocation();
|
||||
private static final int UV_LOC = Usage.PRIMARY_UV.getAttributeLocation();
|
||||
private static final int NORMAL_LOC = Usage.NORMAL.getAttributeLocation();
|
||||
private static final int SRC_TEX_OFFSET = 12;
|
||||
private static final int SRC_NORMAL_OFFSET = 20;
|
||||
private static final int SRC_STRIDE_FLAT = 20;
|
||||
private static final int SRC_STRIDE_SMOOTH = 24;
|
||||
private static final int MAX_CACHE = 1024;
|
||||
private static final int MAX_EXT_VERTS = 1 << 20;
|
||||
private static final int MAX_RESPECS = 8;
|
||||
private static final int RESPEC_DECAY_FRAMES = 2;
|
||||
private static final int MIN_STREAM_SPAN_FRAMES = 4;
|
||||
private static final int STREAM_EXPIRE_FRAMES = 60;
|
||||
|
||||
private static final HashMap<Key, ExtBuffer> cache = new HashMap<>();
|
||||
private static final Key lookup = new Key();
|
||||
|
||||
private static final HashMap<EboKey, Boolean> eboPatternCache = new HashMap<>();
|
||||
private static final EboKey eboLookup = new EboKey();
|
||||
|
||||
private static final Int2ObjectMap<ObjectSet<Key>> keysBySource = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<ObjectSet<EboKey>> eboKeysBySource = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2IntMap respecCounts = new Int2IntOpenHashMap();
|
||||
private static final Int2IntMap respecFrames = new Int2IntOpenHashMap();
|
||||
private static final Int2IntMap respecRunStart = new Int2IntOpenHashMap();
|
||||
private static final IntSet streamingSources = new IntOpenHashSet();
|
||||
private static final IntSet persistentSources = new IntOpenHashSet();
|
||||
private static int frame;
|
||||
|
||||
public static void endFrame() { frame++; }
|
||||
|
||||
public static boolean isEmpty() {
|
||||
return cache.isEmpty() && eboPatternCache.isEmpty();
|
||||
}
|
||||
|
||||
public static void beginInternalDraw() { GLStateManager.ctx().internalDrawDepth++; }
|
||||
|
||||
public static void endInternalDraw() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (glCtx.internalDrawDepth > 0) glCtx.internalDrawDepth--;
|
||||
}
|
||||
|
||||
private static void beginInternalGl() { GLStateManager.ctx().internalGlDepth++; }
|
||||
|
||||
private static void endInternalGl() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (glCtx.internalGlDepth > 0) glCtx.internalGlDepth--;
|
||||
}
|
||||
|
||||
private static boolean isNotConventionalTexturedArray(VAOManager.Attrib a) {
|
||||
return a == null || !a.enabled || a.genericPointer || a.vboId == 0 || a.size < 2;
|
||||
}
|
||||
|
||||
private static boolean isStreaming(int vboId) {
|
||||
if (vboId == 0 || !streamingSources.contains(vboId)) return false;
|
||||
if (persistentSources.contains(vboId)) return true;
|
||||
if (frame - respecFrames.get(vboId) > STREAM_EXPIRE_FRAMES) {
|
||||
streamingSources.remove(vboId);
|
||||
respecFrames.remove(vboId);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean maybeBind(GLContextState glCtx, int mode, int first, int count) {
|
||||
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
|
||||
if (h == null) return false;
|
||||
if (glCtx.internalDrawDepth > 0) return false;
|
||||
final int extPrim = ImmediateExtendedAttribHandler.extPrimVerts(mode, count);
|
||||
if (extPrim == 0 || first < 0) return false;
|
||||
if (!h.wantsExtended()) return false;
|
||||
|
||||
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
|
||||
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
|
||||
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
|
||||
if (isStreaming(pos.vboId) || isStreaming(uv.vboId)) return false;
|
||||
|
||||
VAOManager.Attrib normal = extPrim == 3 ? glCtx.vaos.attrib(NORMAL_LOC) : null;
|
||||
if (normal != null && (!normal.enabled || normal.genericPointer || normal.vboId == 0 || normal.size < 3
|
||||
|| isStreaming(normal.vboId))) {
|
||||
normal = null;
|
||||
}
|
||||
|
||||
return bindExt(h, mode, first, count, extPrim, pos, uv, normal);
|
||||
}
|
||||
|
||||
public static boolean maybeBindIndexed(GLContextState glCtx, int mode, int indexCount, int indexType, long indicesOffset) {
|
||||
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
|
||||
if (h == null) return false;
|
||||
if (glCtx.internalDrawDepth > 0) return false;
|
||||
if (mode != GL11.GL_TRIANGLES || indexCount <= 0 || indexCount % 6 != 0) return false;
|
||||
if (!h.wantsExtended()) return false;
|
||||
|
||||
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
|
||||
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
|
||||
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
|
||||
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(glCtx.vaos.boundEBO)) return false;
|
||||
|
||||
final int indexSize = indexTypeBytes(indexType);
|
||||
if (indexSize == 0) return false;
|
||||
if (indicesOffset % (6L * indexSize) != 0) return false;
|
||||
final int first = (int) (indicesOffset / indexSize) / 6 * 4;
|
||||
final int count = indexCount / 6 * 4;
|
||||
|
||||
if (!isQuadPatternEbo(glCtx.vaos.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
|
||||
|
||||
// Build/bind as GL_QUADS (the real primitive): face-normal tangents, 4-vertex tiles, no per-vertex normal.
|
||||
return bindExt(h, GL11.GL_QUADS, first, count, 4, pos, uv, null);
|
||||
}
|
||||
|
||||
private static boolean isQuadPatternEbo(int ebo, long offset, int indexCount, int indexType, int indexSize, int firstVertex) {
|
||||
if (ebo == 0) return false;
|
||||
eboLookup.set(ebo, offset, indexCount, indexType);
|
||||
final Boolean cached = eboPatternCache.get(eboLookup);
|
||||
if (cached != null) return cached;
|
||||
if (eboPatternCache.size() >= MAX_CACHE) {
|
||||
eboPatternCache.clear();
|
||||
eboKeysBySource.clear();
|
||||
}
|
||||
|
||||
final ByteBuffer buf = memCalloc(indexCount, indexSize);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, buf);
|
||||
final long addr = memAddress0(buf);
|
||||
boolean match = true;
|
||||
for (int q = 0, quadCount = indexCount / 6; q < quadCount; q++) {
|
||||
final int base = firstVertex + q * 4;
|
||||
final long p = addr + (long) q * 6 * indexSize;
|
||||
if (readIndex(p, 0, indexSize) != base
|
||||
|| readIndex(p, 1, indexSize) != base + 1
|
||||
|| readIndex(p, 2, indexSize) != base + 2
|
||||
|| readIndex(p, 3, indexSize) != base + 2
|
||||
|| readIndex(p, 4, indexSize) != base + 3
|
||||
|| readIndex(p, 5, indexSize) != base) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
memFree(buf);
|
||||
final EboKey stored = eboLookup.copy();
|
||||
eboPatternCache.put(stored, match);
|
||||
ObjectSet<EboKey> ebos = eboKeysBySource.get(stored.ebo);
|
||||
if (ebos == null) eboKeysBySource.put(stored.ebo, ebos = new ObjectOpenHashSet<>());
|
||||
ebos.add(stored);
|
||||
return match;
|
||||
}
|
||||
|
||||
private static long readIndex(long base, int i, int indexSize) {
|
||||
return switch (indexSize) {
|
||||
case 1 -> memGetByte(base + i) & 0xFFL;
|
||||
case 2 -> memGetShort(base + (long) i * 2) & 0xFFFFL;
|
||||
default -> memGetInt(base + (long) i * 4) & 0xFFFFFFFFL;
|
||||
};
|
||||
}
|
||||
|
||||
private static int indexTypeBytes(int indexType) {
|
||||
return switch (indexType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> 1;
|
||||
case GL11.GL_UNSIGNED_SHORT -> 2;
|
||||
case GL11.GL_UNSIGNED_INT -> 4;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean bindExt(ImmediateExtendedAttribHandler h, int mode, int first, int count, int extPrim,
|
||||
VAOManager.Attrib pos, VAOManager.Attrib uv, VAOManager.Attrib normal) {
|
||||
final int extVbo = getOrBuild(h, pos, uv, normal, first, count, extPrim, mode);
|
||||
if (extVbo == 0) return false;
|
||||
|
||||
final int savedVBO = GLStateManager.getBoundVBO();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, extVbo);
|
||||
ImmediateExtendedAttribHandler.setupExtAttribPointers(0L, ImmediateExtendedAttribHandler.EXT_STRIDE);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void unbind() {
|
||||
GLStateManager.glDisableVertexAttribArray(ImmediateExtendedAttribHandler.LOC_MID_TEX);
|
||||
GLStateManager.glDisableVertexAttribArray(ImmediateExtendedAttribHandler.LOC_TANGENT);
|
||||
setNeutralCurrentValues();
|
||||
}
|
||||
|
||||
public static void setNeutralCurrentValues() {
|
||||
BatchStateGuard.suspend();
|
||||
try {
|
||||
GLStateManager.glVertexAttrib2f(ImmediateExtendedAttribHandler.LOC_MID_TEX, 0.5f, 0.5f);
|
||||
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
|
||||
} finally {
|
||||
BatchStateGuard.resume();
|
||||
}
|
||||
}
|
||||
|
||||
public static void onDeleteBuffer(GLContextState glCtx, int vboId) {
|
||||
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
|
||||
dropDerivedFrom(vboId);
|
||||
respecCounts.remove(vboId);
|
||||
respecFrames.remove(vboId);
|
||||
respecRunStart.remove(vboId);
|
||||
streamingSources.remove(vboId);
|
||||
persistentSources.remove(vboId);
|
||||
}
|
||||
|
||||
public static void onBufferRespecified(GLContextState glCtx, int vboId) {
|
||||
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
|
||||
if (streamingSources.contains(vboId)) {
|
||||
respecFrames.put(vboId, frame);
|
||||
return;
|
||||
}
|
||||
if (!dropDerivedFrom(vboId)) return;
|
||||
final boolean sameRun = respecCounts.containsKey(vboId) && frame - respecFrames.get(vboId) <= RESPEC_DECAY_FRAMES;
|
||||
if (!sameRun) {
|
||||
startRespecRun(vboId);
|
||||
return;
|
||||
}
|
||||
final int respecs = respecCounts.get(vboId) + 1;
|
||||
final int span = frame - respecRunStart.get(vboId);
|
||||
if (respecs >= MAX_RESPECS && span >= MIN_STREAM_SPAN_FRAMES && span <= respecs) {
|
||||
markStreaming(vboId);
|
||||
return;
|
||||
}
|
||||
if (span > respecs) {
|
||||
startRespecRun(vboId);
|
||||
return;
|
||||
}
|
||||
respecCounts.put(vboId, respecs);
|
||||
respecFrames.put(vboId, frame);
|
||||
}
|
||||
|
||||
private static void startRespecRun(int vboId) {
|
||||
respecCounts.put(vboId, 1);
|
||||
respecRunStart.put(vboId, frame);
|
||||
respecFrames.put(vboId, frame);
|
||||
}
|
||||
|
||||
private static void markStreaming(int vboId) {
|
||||
respecCounts.remove(vboId);
|
||||
respecRunStart.remove(vboId);
|
||||
respecFrames.put(vboId, frame);
|
||||
if (streamingSources.add(vboId)) dropDerivedFrom(vboId);
|
||||
}
|
||||
|
||||
public static void markPersistent(GLContextState glCtx, int vboId) {
|
||||
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
|
||||
markStreaming(vboId);
|
||||
persistentSources.add(vboId);
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
invalidateAll();
|
||||
respecCounts.clear();
|
||||
respecFrames.clear();
|
||||
respecRunStart.clear();
|
||||
streamingSources.clear();
|
||||
persistentSources.clear();
|
||||
}
|
||||
|
||||
private static boolean dropDerivedFrom(int srcId) {
|
||||
final ObjectSet<EboKey> ebos = eboKeysBySource.remove(srcId);
|
||||
if (ebos != null) {
|
||||
for (EboKey k : ebos) eboPatternCache.remove(k);
|
||||
}
|
||||
final ObjectSet<Key> keys = keysBySource.get(srcId);
|
||||
if (keys == null || keys.isEmpty()) return ebos != null;
|
||||
IntArrayList toFree = null;
|
||||
for (Key k : keys.toArray(new Key[0])) {
|
||||
final ExtBuffer buf = cache.remove(k);
|
||||
if (buf != null && buf.vbo != 0) {
|
||||
if (toFree == null) toFree = new IntArrayList();
|
||||
toFree.add(buf.vbo);
|
||||
}
|
||||
unindexKey(k);
|
||||
}
|
||||
freeExtVbos(toFree);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void indexKey(Key k) {
|
||||
addToBucket(k.posVBO, k);
|
||||
if (k.uvVBO != k.posVBO) addToBucket(k.uvVBO, k);
|
||||
if (k.normalVBO != k.posVBO && k.normalVBO != k.uvVBO) addToBucket(k.normalVBO, k);
|
||||
}
|
||||
|
||||
private static void unindexKey(Key k) {
|
||||
removeFromBucket(k.posVBO, k);
|
||||
if (k.uvVBO != k.posVBO) removeFromBucket(k.uvVBO, k);
|
||||
if (k.normalVBO != k.posVBO && k.normalVBO != k.uvVBO) removeFromBucket(k.normalVBO, k);
|
||||
}
|
||||
|
||||
private static void addToBucket(int src, Key k) {
|
||||
if (src == 0) return;
|
||||
ObjectSet<Key> set = keysBySource.get(src);
|
||||
if (set == null) keysBySource.put(src, set = new ObjectOpenHashSet<>());
|
||||
set.add(k);
|
||||
}
|
||||
|
||||
private static void removeFromBucket(int src, Key k) {
|
||||
if (src == 0) return;
|
||||
final ObjectSet<Key> set = keysBySource.get(src);
|
||||
if (set != null && set.remove(k) && set.isEmpty()) keysBySource.remove(src);
|
||||
}
|
||||
|
||||
private static int getOrBuild(ImmediateExtendedAttribHandler h, VAOManager.Attrib pos, VAOManager.Attrib uv,
|
||||
VAOManager.Attrib normal, int first, int count, int extPrim, int mode) {
|
||||
if (first + count > MAX_EXT_VERTS) return 0;
|
||||
lookup.set(pos, uv, normal, mode, first % extPrim);
|
||||
ExtBuffer buf = cache.get(lookup);
|
||||
if (buf == null) {
|
||||
if (cache.size() >= MAX_CACHE) invalidateExtBuffers();
|
||||
buf = new ExtBuffer();
|
||||
final Key stored = lookup.copy();
|
||||
cache.put(stored, buf);
|
||||
indexKey(stored);
|
||||
}
|
||||
|
||||
final long range = (long) first << 32 | (count & 0xFFFFFFFFL);
|
||||
if (buf.vbo != 0 && buf.builtRanges.contains(range)) return buf.vbo;
|
||||
if (!ensureCapacity(buf, first + count)) return 0;
|
||||
buildRange(h, buf.vbo, pos, uv, normal, first, count, extPrim);
|
||||
buf.builtRanges.add(range);
|
||||
return buf.vbo;
|
||||
}
|
||||
|
||||
private static boolean ensureCapacity(ExtBuffer buf, int neededVerts) {
|
||||
if (buf.vbo != 0 && buf.capacityVerts >= neededVerts) return true;
|
||||
final int extStride = ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
final int newCap = Math.min(MAX_EXT_VERTS, Math.max(neededVerts, buf.capacityVerts * 2));
|
||||
beginInternalGl();
|
||||
try {
|
||||
final int vbo = GLStateManager.glGenBuffers();
|
||||
if (vbo == 0) return false;
|
||||
final int savedVBO = GLStateManager.getBoundVBO();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, (long) newCap * extStride, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
|
||||
if (buf.vbo != 0) {
|
||||
final int savedRead = GLStateManager.getBoundCopyReadBuffer();
|
||||
final int savedWrite = GLStateManager.getBoundCopyWriteBuffer();
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, buf.vbo);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, vbo);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0L, 0L,
|
||||
(long) buf.capacityVerts * extStride);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, savedRead);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, savedWrite);
|
||||
GLStateManager.glDeleteBuffers(buf.vbo);
|
||||
}
|
||||
buf.vbo = vbo;
|
||||
buf.capacityVerts = newCap;
|
||||
return true;
|
||||
} finally {
|
||||
endInternalGl();
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildRange(ImmediateExtendedAttribHandler h, int extVbo, VAOManager.Attrib pos,
|
||||
VAOManager.Attrib uv, VAOManager.Attrib normal, int first, int count, int extPrim) {
|
||||
final int posStride = pos.effectiveStride();
|
||||
final int uvStride = uv.effectiveStride();
|
||||
final boolean smooth = normal != null;
|
||||
final int normalStride = smooth ? normal.effectiveStride() : 0;
|
||||
final int srcStride = smooth ? SRC_STRIDE_SMOOTH : SRC_STRIDE_FLAT;
|
||||
final int srcNormalOffset = smooth ? SRC_NORMAL_OFFSET : -1;
|
||||
|
||||
final ByteBuffer posBuf = readRange(pos, first, count, posStride);
|
||||
final ByteBuffer uvBuf = readRange(uv, first, count, uvStride);
|
||||
final ByteBuffer normalBuf = smooth ? readRange(normal, first, count, normalStride) : null;
|
||||
|
||||
final ByteBuffer src = memAlloc(count * srcStride).order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int pb = i * posStride, ub = i * uvStride, d = i * srcStride;
|
||||
src.putFloat(d, pos.readComponent(posBuf, pb, 0));
|
||||
src.putFloat(d + 4, pos.readComponent(posBuf, pb, 1));
|
||||
src.putFloat(d + 8, pos.size >= 3 ? pos.readComponent(posBuf, pb, 2) : 0f);
|
||||
src.putFloat(d + SRC_TEX_OFFSET, uv.readComponent(uvBuf, ub, 0));
|
||||
src.putFloat(d + SRC_TEX_OFFSET + 4, uv.readComponent(uvBuf, ub, 1));
|
||||
if (smooth) {
|
||||
final int nb = i * normalStride;
|
||||
src.putInt(d + SRC_NORMAL_OFFSET, NormI8.pack(
|
||||
normal.readComponent(normalBuf, nb, 0),
|
||||
normal.readComponent(normalBuf, nb, 1),
|
||||
normal.readComponent(normalBuf, nb, 2)));
|
||||
}
|
||||
}
|
||||
|
||||
final int extStride = ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
final ByteBuffer ext = memCalloc(count, extStride);
|
||||
h.buildPacked(memAddress0(src), srcStride, 0, SRC_TEX_OFFSET, srcNormalOffset, count, extPrim, memAddress0(ext), extStride);
|
||||
|
||||
beginInternalGl();
|
||||
try {
|
||||
final int savedVBO = GLStateManager.getBoundVBO();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, extVbo);
|
||||
ext.position(0).limit(count * extStride);
|
||||
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * extStride, ext);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
|
||||
} finally {
|
||||
endInternalGl();
|
||||
}
|
||||
|
||||
memFree(posBuf);
|
||||
memFree(uvBuf);
|
||||
if (normalBuf != null) memFree(normalBuf);
|
||||
memFree(src);
|
||||
memFree(ext);
|
||||
}
|
||||
|
||||
private static ByteBuffer readRange(VAOManager.Attrib a, int first, int count, int stride) {
|
||||
final int elementBytes = a.size * a.typeSizeBytes();
|
||||
final int bytes = (count - 1) * stride + elementBytes;
|
||||
final ByteBuffer dst = memAlloc(bytes).order(ByteOrder.nativeOrder());
|
||||
dst.position(0).limit(bytes);
|
||||
final int savedVBO = GLStateManager.getBoundVBO();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, a.offset + (long) first * stride, dst);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
|
||||
dst.position(0).limit(bytes);
|
||||
return dst;
|
||||
}
|
||||
|
||||
private static void invalidateAll() {
|
||||
eboPatternCache.clear();
|
||||
eboKeysBySource.clear();
|
||||
invalidateExtBuffers();
|
||||
}
|
||||
|
||||
private static void invalidateExtBuffers() {
|
||||
if (cache.isEmpty()) return;
|
||||
final IntArrayList toFree = new IntArrayList(cache.size());
|
||||
for (ExtBuffer buf : cache.values()) {
|
||||
if (buf.vbo != 0) toFree.add(buf.vbo);
|
||||
}
|
||||
cache.clear();
|
||||
keysBySource.clear();
|
||||
freeExtVbos(toFree);
|
||||
}
|
||||
|
||||
private static void freeExtVbos(IntArrayList vbos) {
|
||||
if (vbos == null || vbos.isEmpty()) return;
|
||||
beginInternalGl();
|
||||
try {
|
||||
for (int i = 0; i < vbos.size(); i++) {
|
||||
GLStateManager.glDeleteBuffers(vbos.getInt(i));
|
||||
}
|
||||
} finally {
|
||||
endInternalGl();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ExtBuffer {
|
||||
int vbo;
|
||||
int capacityVerts;
|
||||
final LongOpenHashSet builtRanges = new LongOpenHashSet();
|
||||
}
|
||||
|
||||
private static final class EboKey {
|
||||
int ebo, count, type;
|
||||
long offset;
|
||||
|
||||
void set(int ebo, long offset, int count, int type) {
|
||||
this.ebo = ebo;
|
||||
this.offset = offset;
|
||||
this.count = count;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
EboKey copy() {
|
||||
final EboKey k = new EboKey();
|
||||
k.set(ebo, offset, count, type);
|
||||
return k;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof EboKey k)) return false;
|
||||
return ebo == k.ebo && count == k.count && type == k.type && offset == k.offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = ebo;
|
||||
h = h * 31 + count;
|
||||
h = h * 31 + type;
|
||||
h = h * 31 + Long.hashCode(offset);
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Key {
|
||||
int posVBO, uvVBO, normalVBO, posStride, uvStride, normalStride, posFmt, uvFmt, normalFmt, mode, phase;
|
||||
long posOffset, uvOffset, normalOffset;
|
||||
|
||||
void set(VAOManager.Attrib pos, VAOManager.Attrib uv, VAOManager.Attrib normal, int mode, int phase) {
|
||||
posVBO = pos.vboId; posOffset = pos.offset; posStride = pos.effectiveStride(); posFmt = fmt(pos);
|
||||
uvVBO = uv.vboId; uvOffset = uv.offset; uvStride = uv.effectiveStride(); uvFmt = fmt(uv);
|
||||
if (normal != null) {
|
||||
normalVBO = normal.vboId; normalOffset = normal.offset;
|
||||
normalStride = normal.effectiveStride(); normalFmt = fmt(normal);
|
||||
} else {
|
||||
normalVBO = 0; normalOffset = 0; normalStride = 0; normalFmt = 0;
|
||||
}
|
||||
this.mode = mode;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
private static int fmt(VAOManager.Attrib a) {
|
||||
return (a.size & 0x7F) | (a.normalized ? 0x80 : 0) | (a.type << 8);
|
||||
}
|
||||
|
||||
Key copy() {
|
||||
final Key k = new Key();
|
||||
k.posVBO = posVBO; k.posOffset = posOffset; k.posStride = posStride; k.posFmt = posFmt;
|
||||
k.uvVBO = uvVBO; k.uvOffset = uvOffset; k.uvStride = uvStride; k.uvFmt = uvFmt;
|
||||
k.normalVBO = normalVBO; k.normalOffset = normalOffset; k.normalStride = normalStride; k.normalFmt = normalFmt;
|
||||
k.mode = mode; k.phase = phase;
|
||||
return k;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Key k)) return false;
|
||||
return posVBO == k.posVBO && uvVBO == k.uvVBO && normalVBO == k.normalVBO
|
||||
&& posStride == k.posStride && uvStride == k.uvStride && normalStride == k.normalStride
|
||||
&& posFmt == k.posFmt && uvFmt == k.uvFmt && normalFmt == k.normalFmt
|
||||
&& mode == k.mode && phase == k.phase
|
||||
&& posOffset == k.posOffset && uvOffset == k.uvOffset && normalOffset == k.normalOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = posVBO;
|
||||
h = h * 31 + uvVBO;
|
||||
h = h * 31 + normalVBO;
|
||||
h = h * 31 + posStride;
|
||||
h = h * 31 + uvStride;
|
||||
h = h * 31 + normalStride;
|
||||
h = h * 31 + posFmt;
|
||||
h = h * 31 + uvFmt;
|
||||
h = h * 31 + normalFmt;
|
||||
h = h * 31 + mode;
|
||||
h = h * 31 + phase;
|
||||
h = h * 31 + Long.hashCode(posOffset);
|
||||
h = h * 31 + Long.hashCode(uvOffset);
|
||||
h = h * 31 + Long.hashCode(normalOffset);
|
||||
return h;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1397 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user