Compare commits

...
95 Commits
Author SHA1 Message Date
Eclipse-Sol fe67a22d5a Improve shader support for custom block rendering (#2176) 2026-10-01 20:37:51 -07:00
Ranzu 66d2a548fc Add more targets for darkmode transformer (#2178) 2026-10-01 20:36:51 -07:00
Death Fuel d16c1e7cf6 Internal format fix (#2179) 2026-10-01 20:36:21 -07:00
Eclipse-Sol a8e95f70c1 Improve biome blending and improve compat (#2180) 2026-10-01 20:35:02 -07:00
9cb35c67cd Dark mode text recoloring with resource packs configs (#2118)
Co-authored-by: Ranzu <[email protected]>
Co-authored-by: Alexdoru <[email protected]>
Co-authored-by: Jason Mitchell <[email protected]>
2026-09-30 16:12:54 -07:00
Li XiangrongandEclipse-Sol cbd16b1a76 Fix shadow pass receiving a full-bright lightmap constant and misreporting terrain renderStage (#2170)
* Fix shadow pass lightmap availability

* Report per-layer terrain renderStage

---------

Co-authored-by: Eclipse-Sol <[email protected]>
2026-09-30 12:51:38 -04:00
Jason Mitchell cd0a9608d3 Fix async atlas with KaizPatchX (#2175) 2026-09-30 08:53:41 -07:00
KAMKEEL fa71eda3db Font Texture Crash Fix on Splash Screen (#2169) 2026-09-29 19:18:16 -07:00
KAMKEEL 9705722469 Fix Depth Mask Issue for Nametags, etc for Text (#2168) 2026-09-29 18:49:05 -07:00
Lumarin 977db3d604 Add option to change mouse movement from Cinematic to Standard while in Angelica Zoom (#2166) 2026-09-29 17:02:55 -07:00
Jason Mitchell c10d30b2da Parallelize atlas loading and fix SDL-GPU mip-level targets (#2167) 2026-09-29 16:57:53 -07:00
micvogandEclipse-Sol 2f4ac2a9b7 Apply the baked transform to vertices written directly into BakedTransformCapture (#2164)
While a direct capture is active, Tessellator.instance is the capture itself, so
RenderBlocks.renderBlockAsItem writes its vertices directly instead of going
through interceptDraw. Its own rotate/translate was therefore never baked into
the cached block mesh, which left held and dropped blocks off-center (e.g. the
snow golem's pumpkin).

Co-authored-by: Eclipse-Sol <[email protected]>
2026-09-29 18:26:31 -04:00
Death Fuel 9081f4c3e8 Various font improvements and fixes (#2165) 2026-09-29 14:58:53 -07:00
Eclipse-Sol 1f274a64ea TESR state and batching leak fixes (#2162) 2026-09-28 19:28:45 -07:00
micvog 9814a96b10 Fix attrib stack overflow from world-less TESR item renderers (#2159) 2026-09-28 07:05:46 -07:00
Eclipse-Sol af599f292e Fix Iris issues with instancing and batching (#2158) 2026-09-27 17:21:39 -07:00
Jason Mitchell 7bb5bfd45c Emulate color logic ops and fix pixel readback on SDL-GPU (#2157) 2026-09-27 15:33:04 -07:00
Jason Mitchell 74ea5c4709 Add in-game Tracy capture (#2156) 2026-09-27 14:16:01 -07:00
Eclipse-Sol 233c74e0d7 Better Biome Blending (#2152) 2026-09-27 13:19:48 -07:00
Jason Mitchell e17b45c842 Fix NEI items vanishing with ModularUI2 screens open on SDL-GPU (#2153) 2026-09-27 12:26:53 -07:00
Jason Mitchell c9d46e70e0 Update to Celeritas 2.5.12 with translucency sorting v3 (#2151) 2026-09-27 09:37:40 -07:00
Jason Mitchell a8c29fa3f9 Give each GL drawable its own GLSM context (#2142) 2026-09-26 22:31:07 -07:00
Eclipse-Sol 7c28a69bfe Optimize Enchant Glint Rendering (#2140)
* initial commit for enchant-glint-optimizations

* Fix spacing issues and remove GlintCompatibility.java
2026-09-26 19:21:21 -04:00
Eclipse-Sol 7f3fb35ebb Use actual camera when doing cloud related culling and fuctions (#2139) 2026-09-26 14:58:03 -07:00
Jason Mitchell 44c930f4ab Fix scissor clears wiping the whole screen on SDL-GPU (#2138) 2026-09-26 13:54:10 -07:00
Jason Mitchell b76284048a Fix shader packs in modded dimensions (#2137)
* Use world0 for unmapped dimensions
* Fix iris warnings for shaderpacks

Fixes https://github.com/GTNewHorizons/Angelica/issues/2128
2026-09-26 13:18:17 -04:00
Jason Mitchell 762c5661eb Stop blocking chunk workers on deferred ISBRH blocks (#2136) 2026-09-26 08:44:22 -07:00
Jason Mitchell 1babfd968c Properly reset/flush state for crash interceptors (#2133) 2026-09-25 20:43:05 -07:00
Eclipse-Sol 888a4de0c9 Add Galaxy Space compat and fix culling issues (#2131) 2026-09-25 19:18:45 -07:00
DarkShadow44 a23cefba92 Only enable shader DH compat if DH is enabled (#2130) 2026-09-23 17:42:45 -07:00
Eclipse-Sol f2c07dea28 Weather instancing (#2127)
* Initial commit for weather-instancing

* attempt to make the builder less ugly
2026-09-22 19:27:05 -04:00
Jason Mitchell 53776684c8 Properly handle display lists with the push/pop optimizations (#2126) 2026-09-22 09:23:49 -07:00
Jason Mitchell 9dd60f6a75 Render pass and state stack optimizations (#2124) 2026-09-21 20:52:51 -07:00
Alexdoru 87c83f0645 fix asm class dumper following rfb changes (#2123) 2026-09-21 12:35:37 -07:00
Eclipse-Sol c578970a66 Fix edge cases with cloud wraps (#2122) 2026-09-20 17:16:03 +00:00
Eclipse-Sol ef68af5850 Fix texture filtering sampling regression (#2115) 2026-09-19 10:12:14 -07:00
James b6a4497fb3 Redirected Util.checkGLError to respect disableErrorChecks (#2110) 2026-09-18 22:33:17 -07:00
Eclipse-Sol 939e31461a More Cloud Optimizations (#2114) 2026-09-18 21:53:50 -07:00
Eclipse-Sol 02f0b0fca1 Fix mipmaps bleeding (#2112) 2026-09-17 20:29:53 -07:00
Nick Smith b7d439e6b1 Added abutting block detection and processing (#2068) 2026-09-16 16:09:40 -07:00
KAMKEEL c36647af34 Hex Text and other Font Mod Compat System (#1972) 2026-09-16 16:01:16 -07:00
Eclipse-Sol f787801a0e Fix borking modded mobs with broken eyes (#2108) 2026-09-16 16:00:09 -07:00
Algent f192793395 Fix microblock transparency in the hotbar (#2107) 2026-09-16 15:48:26 -07:00
Jason Mitchell 3876f7e693 Instance more things (#2104)
* Flyby: pin the start origin and fly just above the route's terrain

* Instancing: particles, dropped items, entity cubes + shadow batching and some SDL-GPU fixes
2026-09-16 14:56:24 -07:00
danyadev 94ee37bfed fix resize handling on non-main thread for lwjgl2 (#2106) 2026-09-16 13:59:15 -07:00
Eclipse-Sol 26c161f0e8 Improve upon Texture Filtering Work (#2096) 2026-09-15 16:00:43 -07:00
Jason Mitchell 75639d3b73 Flyby improvements (#2100) 2026-09-14 19:32:31 -07:00
Jason Mitchell a07cecb879 Fix some GLSM bypasses (#2099) 2026-09-14 14:22:19 -07:00
kurrycat 81b82748ce SDL: flush vertex attribs + deferred texture uploads before readback (#2095) 2026-09-13 11:00:20 -07:00
Arvid JunehagandMartin Robertz 8d0c90c423 Updated the list of blocks to get rotated (#2094)
Co-authored-by: Martin Robertz <[email protected]>
2026-09-13 01:33:21 +00:00
kurrycat 8ce6a2f8c6 Fix cloud elevation setting (#2093) 2026-09-12 14:31:26 -07:00
DarkShadow44 2aa4ac7509 DH: Fix shaderpack fading (#2091) 2026-09-12 09:43:28 -07:00
DarkShadow44 8f98151049 Remove superflous glVertexAttribPointer call (#2090) 2026-09-12 09:16:27 -07:00
Eclipse-Sol 2b70318327 Fix issues with Schematica (#2088) 2026-09-09 17:51:39 -07:00
Jason Mitchell 3b4ebcf611 Update Celeritas (#2087) 2026-09-09 08:31:31 -07:00
Jason Mitchell 2845c9e078 FPS pacer work (#2086) 2026-09-08 23:01:21 -07:00
danyadev 6f054a09a9 properly clear PBRAtlasTexture (#2085) 2026-09-08 17:38:54 -07:00
Eldrinn Elantey 37a10eee3f Fix tile entity pass markers skipped on newer class files (#2075) 2026-09-06 16:03:08 -07:00
Caedis 744c1f971f Replace natural texture rotation hash (#2073) 2026-09-06 10:29:28 -07:00
Philip Xu 32d1c27b9c Fix for texture being mistakenly turned off by Iris (#2072) 2026-09-06 09:53:49 -07:00
Philip Xu f180894da7 Fixes TESR texts (travel anchors) depth test bug (#2071) 2026-09-06 09:44:29 -07:00
danyadev bb67f82701 optimize resource reload (#2070) 2026-09-06 09:43:18 -07:00
danyadev 73f5556595 support custom fonts on macos (#2066) 2026-09-05 21:09:41 -07:00
Jarnexis 19bc7cbaed Add Ukrainian (uk_UA) localization and translate documentation (#1990) 2026-09-06 03:40:54 +00:00
danyadev d189a644e2 Fix custom font rendering on BetterLoadingScreen splash (#2063) 2026-09-04 16:03:43 -07:00
danyadev 94ba40ae4d Activate FFP when enabling with program 0 (#2064) 2026-09-04 16:03:13 -07:00
Jason Mitchell 961b23fd2d Fix tests (#2065) 2026-09-04 15:45:27 -07:00
Nick Smith 61b5301f1a CTM Transitions - neighbor detection and influence (#2047) 2026-09-03 22:33:18 -07:00
Jakob Erber 0e539ae034 Synchronize CubicChunks tile entity mutations (#2031) 2026-09-03 22:32:49 -07:00
Chitak 985 e581326641 Fix animate-only-visible-textures setting (#2054) 2026-09-03 22:32:14 -07:00
kurrycat c707cc51f1 Use bound texture instead of hardcoded vanilla textures for cached vanilla model rendering (#2037) 2026-09-03 22:31:41 -07:00
Nick Smith 7d890bf3e7 Enable CTM mining/breaking overlays (#2052) 2026-09-03 22:30:39 -07:00
Eclipse-SolandJason Mitchell ffc324bab6 POM Support (#1927)
Co-authored-by: Jason Mitchell <[email protected]>
2026-09-03 22:28:20 -07:00
Jason Mitchell 0dfffca88e Flyby and OSX improvements (#2061) 2026-09-03 22:27:35 -07:00
Alexdoru a66db1517a fix world client leaks in bop fog handlers (#2056) 2026-09-02 11:15:25 -07:00
Jason Mitchell 269b7245d3 Make dynamic lights config less heavy (#2051) 2026-08-30 13:02:07 -07:00
Wokafishh 67f2484976 Blockrotations/Natural Textures (#2020) 2026-08-30 11:16:21 -07:00
KAMKEELandJason Mitchell 1c8687e6fd Add Secondary Color Support to Angelica (#2046)
Co-authored-by: Jason Mitchell <[email protected]>
2026-08-30 10:26:08 -07:00
Jason Mitchell e6bbd155da Stop hardcoding some translations (#2049) 2026-08-30 09:40:27 -07:00
Ranzu 2f64fef6a6 Fix color leak getting into guis (Broken ColorUtils) (#2045) 2026-08-27 14:19:55 -07:00
boubou19 b0fd1ea46e Fix some CTM Allocations (#2044) 2026-08-27 11:59:02 -07:00
ah-OOG-ah 6d8cbf9c2f Add a flag to StateAwareTesselator for per-quad directional shading (#1989)
* Add a flag for no directional shading

* Mark StateAwareTessellator as an API

* Reset internal state better
2026-08-19 18:16:04 +00:00
boubou19 096dd01446 Avoid rescanning atlas for each CTM tile (#2029) 2026-08-19 09:32:42 -07:00
Eclipse-SolandMartin Robertz 22c9479946 Many bug fixes (#2017)
Co-authored-by: Martin Robertz <[email protected]>
2026-08-19 08:55:43 -07:00
Jason Mitchell b2f597a73a SDL presenter fixes and frame pacer adjustments (#2025) 2026-08-16 17:59:33 -07:00
Jason Mitchell 553dec5b6d Mark tiles at transform time instead of using reflection (#2024) 2026-08-16 16:05:04 -07:00
Jason Mitchell bc8116006c Tracy 0.14 (#2019) 2026-08-15 19:09:36 -07:00
Jason Mitchell bf9492dffb Defer ticking on TEs we eagerly create until they get a description packet (#2018) 2026-08-15 17:04:50 -07:00
Jason Mitchell 69aed42b89 Sign Fix (#2016) 2026-08-15 14:32:50 -07:00
KAMKEELandJason Mitchell ffe54f61b9 Fixes World Lighting to Text Rendering so its not Fullbright (#1976)
* Fixes World Lighting to Text Rendering so its not Fullbright

* Carry lightmap state per text segment

* Text lighting: per-segment lightmap uniform, fix GL_RESCALE_NORMAL
* per segment uniform instead of stride increase
* Shade text based on GL light state
* fix u_NormalScale
* GL_LIGHT > 1 usage warnings (not currently handled)

---------

Co-authored-by: Jason Mitchell <[email protected]>
2026-08-14 21:34:58 -07:00
Jason Mitchell c86e1de9f6 TE and world access thread safety for rendering, and remesh on first TE update packet. (#2014) 2026-08-14 20:21:13 -07:00
brandyn 6af401c834 fix starminer rotation & optimize starminer rotateCorpseByGravity (#2005)
* fix starminer rotation & optimize starminer rotateCorpseByGravity

fixes #487

* remove Object[] allocation

* add shared helper,  fix NPEs, fallbacks
2026-08-13 11:27:43 -07:00
Jason Mitchell b17bb42c49 vsync adjustments: on/mailbox/off + windows input lag fix + glsl parsing optimizations (#2009) 2026-08-12 21:20:10 -07:00
Jason Mitchell 3659b59db9 Adjust vsync and fps limiters, and fix WSR with player models (#2002) 2026-08-09 20:06:53 -07:00
Jason Mitchell 8c31eec525 SDL followup fixes (#2001)
* Deploy fixes
* Narrow transformer exclusions for Legends mods, and mixin priority for CustomNPC
* Journeymap 6 stencil/depth fix
* OpenLTW swizzle workaround
2026-08-09 13:43:46 -07:00
950 changed files with 75478 additions and 13315 deletions

No files matched your search

+8
View File
@@ -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
+79 -18
View File
@@ -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"
+13
View File
@@ -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[@]}"
+14 -25
View File
@@ -6,10 +6,6 @@ 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
@@ -20,25 +16,25 @@ 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 +52,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,8 +59,9 @@ 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
@@ -89,4 +78,4 @@ jobs:
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew :tracy-client:publish
run: ./gradlew -PpublishTracy=true :tracy-client:publish
+1 -1
View File
@@ -3,7 +3,7 @@ name: Release tagged build
on:
push:
tags: [ '*' ]
tags: [ '*', '!tracy-v*' ]
permissions:
contents: write
+25
View File
@@ -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
+5
View File
@@ -1,5 +1,6 @@
.gradle
.kotlin/
.lwjgl/
.settings
/.idea/
/.vs/
@@ -40,5 +41,9 @@ 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/
+2
View File
@@ -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
+56
View File
@@ -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
+4
View File
@@ -2,6 +2,8 @@
<sup>**[CurseForge](https://www.curseforge.com/minecraft/mc-mods/angelica) | [Modrinth](https://modrinth.com/mod/angelica)**</sup>
🌐 **Languages:** [English](README.MD) | [Українська](README_uk.MD)
* The little Angle [sic] that supports shaders while saving your fps from a certain death -boubou
* Is it obtuse, or accute? -Caedis
* It's a cute angle -mitchej123
@@ -99,3 +101,5 @@ Targets GL 3.3+ core profile
* 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
View File
@@ -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)
+12 -83
View File
@@ -1,18 +1,16 @@
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") }
val lwjglDebug = false
val gpuHud = false
val renderdoc = false
val rgp = false
extra["lwjglDebug"] = lwjglDebug
minecraft {
extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
@@ -23,24 +21,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 +43,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 +77,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"
@@ -186,6 +130,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(
@@ -202,6 +149,7 @@ fun Test.configureAngelicaJava8() {
tasks.test {
useJUnitPlatform { excludeTags = setOf("gl-core") }
configureAngelicaJava8()
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
}
@@ -228,32 +176,13 @@ val glCoreTest by tasks.registering(Test::class) {
}
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:.*:.*"))
}
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")
}
apply(from = "gradle/angelica-shadow-common.gradle.kts")
tasks.withType<TaskPublishCurseForge>().configureEach {
uploadArtifacts.forEach { it.addGameVersion("Client") }
}
tasks.withType<TaskModrinthUpload>().configureEach {
setDependsOn(dependsOn.filterNot { it == "build" } + "assemble")
}
+10 -4
View File
@@ -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,7 @@ DependencyHandlerScope.of(dependencies).apply {
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
devOnlyNonPublishable(libs.async.profiler)
shadowImplementation(libs.eventbus)
shadowImplementation(libs.jcpp) // Apache 2.0
@@ -104,7 +105,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 +161,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 +178,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")
@@ -199,11 +202,14 @@ afterEvaluate {
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
View File
@@ -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.
+37
View File
@@ -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); втім, його можна використовувати для модифікованих блоків, яким потрібні кілька різних значень відтінку в одному блоці.
+32 -2
View File
@@ -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)
}
@@ -224,6 +251,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 {
@@ -285,7 +315,7 @@ 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")
@@ -20,6 +20,7 @@ public final class SystemProperties {
public static final boolean USE_SDL_GPU = Boolean.getBoolean(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);
@@ -34,20 +35,35 @@ public final class SystemProperties {
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 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 boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
// Debug
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
@@ -55,18 +71,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 +122,25 @@ public final class SystemProperties {
FATAL
}
public enum FlybyPacing {
UNCAPPED,
CONFIGURED
}
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 +152,11 @@ public final class SystemProperties {
}
}
private static Boolean parseBooleanOverride(String key) {
final String raw = System.getProperty(key);
return raw == null ? null : Boolean.parseBoolean(raw);
}
private static double parseDouble(String key) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return 0.0D;
@@ -115,7 +167,7 @@ public final class SystemProperties {
}
}
private static boolean isDeobf() {
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);
}
}
}
@@ -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()) {
@@ -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,6 +93,7 @@ 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
// Current color index
@@ -122,6 +117,7 @@ public class Feature {
, GLStateManager.getBlendMode()
, GLStateManager.getColorLogicOpState()
, GLStateManager.getColorMaterial()
, GLStateManager.getColorSumState()
, GLStateManager.getCullState()
, GLStateManager.getDepthTest()
, GLStateManager.getDitherState()
@@ -189,14 +185,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
@@ -255,10 +266,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
));
@@ -291,7 +302,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()
@@ -324,22 +335,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());
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
@@ -48,6 +47,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 +129,32 @@ 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();
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();
final int stride = positionStride(glCtx);
final int elementSize = GLTypes.sizeBytes(type);
final int indexByteCount = indexCount * elementSize;
@@ -165,12 +170,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 +209,8 @@ public class FeedbackManager {
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final int stride = positionStride();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
@@ -213,7 +219,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 +251,18 @@ 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 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,53 @@ 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.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.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.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.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 +88,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;
@@ -77,10 +105,17 @@ class GLContextState {
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 int attribDepth = 0;
@SuppressWarnings("unchecked")
public final List<IStateStack<?>>[] modifiedAtDepth = new List[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
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 +125,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));
@@ -129,10 +170,10 @@ 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 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 +198,106 @@ 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 int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
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 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 wideLineEmulationActive = 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
@@ -2,8 +2,10 @@ package com.gtnewhorizons.angelica.glsm;
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.Token;
import org.antlr.v4.runtime.WritableToken;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
@@ -88,6 +90,23 @@ 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) {
@@ -162,21 +181,32 @@ public class GlslTransformUtils {
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 +216,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 +224,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.PixelUnpackState;
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(PixelUnpackState.DEFAULT);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oneTexel);
GLStateManager.restorePixelUnpackBuffer();
GLStateManager.restorePixelUnpackState(PixelUnpackState.DEFAULT);
}
}
@@ -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;
@@ -47,6 +49,7 @@ public class RenderSystem {
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
@Getter private static int maxCombinedTextureImageUnits;
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
@@ -59,8 +62,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 +110,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,7 +156,7 @@ 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);
}
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
@@ -159,13 +169,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 +413,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 +484,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);
}
@@ -527,7 +556,7 @@ public class RenderSystem {
}
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) {
@@ -596,7 +625,7 @@ public class RenderSystem {
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return RENDER_BACKEND.genSamplers();
return GLStateManager.glGenSamplers();
}
public static void destroySampler(int sampler) {
@@ -606,22 +635,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);
}
}
}
@@ -0,0 +1,244 @@
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 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)
),
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM,
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)
),
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT,
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_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);
}
}
@@ -10,6 +10,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;
@@ -39,7 +40,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 +115,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 +652,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);
@@ -0,0 +1,112 @@
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;
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) {
runPumpOnMainThread();
}
}
pollBody.run();
} finally {
Tracy.endZone();
}
}
private void runPumpOnMainThread() {
final Thread self = Thread.currentThread();
callerLoader = self.getContextClassLoader();
failure = null;
done = false;
mainExecutor.execute(task);
boolean interrupted = false;
boolean stallReported = false;
final long stallAt = System.nanoTime() + STALL_WARN_NANOS;
while (!done) {
try {
lock.wait(PARK_MILLIS);
} catch (InterruptedException e) {
interrupted = true;
}
if (!done && !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) self.interrupt();
final Throwable t = failure;
if (t != null) {
throw asUnchecked(t);
}
}
private void runPump() {
final Thread self = Thread.currentThread();
final ClassLoader saved = self.getContextClassLoader();
try {
self.setContextClassLoader(callerLoader);
pumpBody.run();
} catch (Throwable t) {
failure = t;
} finally {
try {
self.setContextClassLoader(saved);
} finally {
synchronized (lock) {
done = true;
lock.notifyAll();
}
}
}
}
private static RuntimeException asUnchecked(Throwable t) {
if (t instanceof RuntimeException runtime) return runtime;
if (t instanceof Error error) throw error;
return new RuntimeException(t);
}
}
@@ -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.
@@ -62,7 +70,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,19 +247,28 @@ 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 boolean bindVoxelizationRegion(int ssboBinding, 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 endVoxelizationBatch(long pass) {}
@@ -170,6 +356,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 +463,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);
@@ -0,0 +1,11 @@
package com.gtnewhorizons.angelica.glsm.backend;
public enum VSyncMode {
OFF,
ON,
MAILBOX;
public boolean tearFree() {
return this != OFF;
}
}
@@ -104,7 +104,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);
}
}
@@ -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;
}
}
}
@@ -2,8 +2,10 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import java.util.Arrays;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -12,7 +14,7 @@ import org.lwjgl.opengl.GL13;
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (12) + unit 0 (47 bits at offset 12)
* long[0]: global bits (14) + unit 0 (47 bits at offset 14)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,7 +65,9 @@ public final class FragmentKey {
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 12;
private static final int GLOBAL_BITS = 14;
private static final int BIT_COMBINED_GLINT = 61;
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
@@ -71,6 +75,8 @@ public final class FragmentKey {
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int BIT_LINE_STIPPLE = 11; // 1 bit
private static final int BIT_COLOR_SUM = 12; // 1 bit
private static final int BIT_OVERLAY_INSTANCED = 13;
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -102,11 +108,17 @@ public final class FragmentKey {
* @return number of significant longs (1..4)
*/
public static int packFromState(long[] out) {
return packFromState(out, GLStateManager.ctx());
}
public static int packFromState(long[] out, GLContextState glCtx) {
long global = 0;
if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT;
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
// Fog
if (GLStateManager.getFogMode().isEnabled()) {
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
if (glCtx.fogMode.isEnabled()) {
final int fogMode = switch (glCtx.fogState.getFogMode()) {
case GL11.GL_LINEAR -> FOG_LINEAR;
case GL11.GL_EXP -> FOG_EXP;
case GL11.GL_EXP2 -> FOG_EXP2;
@@ -116,35 +128,40 @@ public final class FragmentKey {
}
// Alpha test
if (GLStateManager.getAlphaTest().isEnabled()) {
if (glCtx.alphaTest.isEnabled()) {
global |= (1L << BIT_ALPHA_TEST);
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
global |= ((long) (encodeAlphaFunc(glCtx.alphaState.getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
}
// Damage overlay
if (GLStateManager.getOverlayA() != 0.0f) {
if (glCtx.ffpInstancing.hasInstanceHead()) {
global |= (1L << BIT_OVERLAY_INSTANCED);
} else if (glCtx.overlayA != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
if (GLStateManager.lineStippleActive) {
if (glCtx.lineStippleActive) {
global |= (1L << BIT_LINE_STIPPLE);
}
// Separate specular
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
// Separate specular / color sum
if (glCtx.lightingState.isEnabled()
&& glCtx.lightModel.colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
global |= (1L << BIT_SEPARATE_SPECULAR);
} else if (glCtx.colorSumState.isEnabled()) {
global |= (1L << BIT_COLOR_SUM);
}
// Per-unit state
final TextureUnitArray textures = glCtx.textures;
int highestEnabled = -1;
long unit0Bits = 0;
for (int i = 0; i < MAX_UNITS; i++) {
// Per Mesa - an enabled unit with no complete texture bound counts as disabled.
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled() && GLStateManager.getTextures().getTextureUnitBindings(i).getBinding() != 0;
final boolean texEnabled = textures.getTextureUnitStates(i).isEnabled() && textures.getTextureUnitBindings(i).getBinding() != 0;
if (texEnabled) highestEnabled = i;
final long unitBits = packUnit(i, texEnabled);
final long unitBits = packUnit(i, texEnabled, textures);
if (i == 0) {
unit0Bits = unitBits;
} else {
@@ -160,10 +177,10 @@ public final class FragmentKey {
return Math.max(1, nrEnabled);
}
private static long packUnit(int unitIndex, boolean texEnabled) {
private static long packUnit(int unitIndex, boolean texEnabled, TextureUnitArray textures) {
if (!texEnabled) return 0;
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
final TexEnvState envState = textures.getTexEnvState(unitIndex);
final int mode = encodeTexEnvMode(envState.mode);
long bits = 1L; // enabled
@@ -235,12 +252,16 @@ public final class FragmentKey {
}
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public boolean colorSum() { return ((packed[0] >> BIT_COLOR_SUM) & 1) != 0; }
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
public boolean overlayInstanced() { return ((packed[0] >> BIT_OVERLAY_INSTANCED) & 1) != 0; }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
@@ -374,10 +395,9 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b overlay=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), overlayEnabled(), nrEnabledUnits()));
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(),
overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.lwjgl.opengl.GL11;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
/**
* Generates GLSL 330 core fragment shaders for FFP emulation.
@@ -17,6 +18,15 @@ public final class FragmentShaderGenerator {
emitInputs(sb, key);
emitUniforms(sb, key);
if (key.combinedGlint()) {
sb.append("#define GLINT_REPLACE_ALPHA ").append(key.glintReplaceAlpha() ? 1 : 0).append('\n');
sb.append("#define GLINT_FOG_MODE ").append(key.fogMode()).append('\n');
sb.append("#define GLINT_ALPHA_FUNC ").append(key.alphaTestEnabled() ? key.alphaTestFunc() : 7).append('\n');
sb.append("#define GLINT_OVERLAY ").append(key.overlayInstanced() ? 2 : key.overlayEnabled() ? 1 : 0).append('\n');
sb.append("#define GLINT_LIGHTMAP ").append(key.nrEnabledUnits() > 1 && key.unitEnabled(1) ? 1 : 0).append('\n');
sb.append(ShaderLoader.getShaderSource("angelica:combined_glint.frag.glsl"));
return sb.toString();
}
sb.append("out vec4 fragColor;\n\n");
sb.append("void main() {\n");
@@ -35,6 +45,9 @@ public final class FragmentShaderGenerator {
private static void emitInputs(StringBuilder sb, FragmentKey key) {
sb.append("// Inputs from vertex shader\n");
sb.append("in vec4 v_Color;\n");
if (key.overlayInstanced()) {
sb.append("in vec4 v_Overlay;\n");
}
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor;\n");
}
@@ -116,8 +129,11 @@ public final class FragmentShaderGenerator {
* Emit the modern-style damage overlay mix.
*/
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
if (!key.overlayEnabled()) return;
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
if (key.overlayInstanced()) {
sb.append(" color.rgb = mix(color.rgb, v_Overlay.rgb, v_Overlay.a);\n");
} else if (key.overlayEnabled()) {
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
}
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
@@ -276,6 +292,10 @@ public final class FragmentShaderGenerator {
sb.append(" // Add separate specular\n");
sb.append(" color.rgb += v_SpecularColor;\n");
}
if (key.colorSum()) {
sb.append(" // Color sum - GL_COLOR_SUM with the current secondary color\n");
sb.append(" color.rgb += u_SecondaryColor;\n");
}
}
private static void emitAlphaTest(StringBuilder sb, FragmentKey key) {
@@ -1,18 +1,89 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
public final class InstancedAttribs {
public static final int LOC_MATRIX_COL0 = 7;
public static final int LOC_MATRIX_COL1 = 8;
public static final int LOC_MATRIX_COL2 = 9;
public static final int LOC_MATRIX_COL3 = 10;
public static final int LOC_COLOR = 11;
public static final int LOC_LIGHTMAP = 12;
public static final int LOC_ROW0 = 5;
public static final int LOC_ROW1 = 6;
public static final int LOC_ROW2 = 7;
public static final int LOC_COLOR = 8;
public static final int LOC_OVERLAY = 9;
public static final int LOC_LIGHTMAP = 10;
public static final int LOC_ENTITY = 11;
public static final int OFFSET_MATRIX = 0;
public static final int OFFSET_COLOR = 64;
public static final int OFFSET_LIGHTMAP = 68;
public static final int STRIDE = 80;
public static final int OFFSET_ROWS = 0;
public static final int OFFSET_COLOR = 48;
public static final int OFFSET_OVERLAY = 52;
public static final int OFFSET_ENTITY = 56;
public static final int HEAD_SIZE = 64;
public static final int OFFSET_LIGHTMAP = HEAD_SIZE;
public static final int STRIDE = 72;
public static void writeHead(long ptr, float[] colMajor, int off, int colorABGR, int overlayABGR, long entityInfo) {
writeRows(ptr, colMajor, off);
writeTail(ptr, colorABGR, overlayABGR, entityInfo);
}
private static void writeRows(long ptr, float[] colMajor, int off) {
for (int r = 0; r < 3; r++) {
final long row = ptr + OFFSET_ROWS + r * 16L;
memPutFloat(row, colMajor[off + r]);
memPutFloat(row + 4, colMajor[off + 4 + r]);
memPutFloat(row + 8, colMajor[off + 8 + r]);
memPutFloat(row + 12, colMajor[off + 12 + r]);
}
}
public static void writeTail(long ptr, int colorABGR, int overlayABGR, long entityInfo) {
memPutInt(ptr + OFFSET_COLOR, colorABGR);
memPutInt(ptr + OFFSET_OVERLAY, overlayABGR);
memPutLong(ptr + OFFSET_ENTITY, entityInfo);
}
public static void writeLightmap(long ptr, int packedLight) {
memPutFloat(ptr, packedLight & 0xFFFF);
memPutFloat(ptr + 4, (packedLight >>> 16) & 0xFFFF);
}
public static long packEntityInfo(int entity, int blockEntity, int item) {
return (entity & 0xFFFFL) | ((blockEntity & 0xFFFFL) << 16) | ((item & 0xFFFFL) << 32);
}
public static int unpackId(long info, int shift) {
final int id = (int) (info >>> shift) & 0xFFFF;
return id == 0xFFFF ? -1 : id;
}
public static void pointHead(long base, int stride) {
for (int r = 0; r < 3; r++) {
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
}
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_UNSIGNED_SHORT, stride, base + OFFSET_ENTITY);
}
public static void pointTemplate(long base) {
pointHead(base, STRIDE);
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP);
}
public static void enableHeadArrays() {
for (int loc = LOC_ROW0; loc <= LOC_ENTITY; loc++) {
enableInstanced(loc);
}
}
static void enableInstanced(int loc) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribDivisor(loc, 1);
}
private InstancedAttribs() {}
}
@@ -0,0 +1,145 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import java.util.ArrayList;
public final class InstancedGlslHelpers {
private InstancedGlslHelpers() {}
public static String mat4FromRows(String row0, String row1, String row2) {
return "mat4("
+ "vec4(" + row0 + ".x, " + row1 + ".x, " + row2 + ".x, 0.0), "
+ "vec4(" + row0 + ".y, " + row1 + ".y, " + row2 + ".y, 0.0), "
+ "vec4(" + row0 + ".z, " + row1 + ".z, " + row2 + ".z, 0.0), "
+ "vec4(" + row0 + ".w, " + row1 + ".w, " + row2 + ".w, 1.0))";
}
public static String mat3FromRows(String row0, String row1, String row2) {
return "mat3("
+ "normalize(vec3(" + row0 + ".x, " + row1 + ".x, " + row2 + ".x)), "
+ "normalize(vec3(" + row0 + ".y, " + row1 + ".y, " + row2 + ".y)), "
+ "normalize(vec3(" + row0 + ".z, " + row1 + ".z, " + row2 + ".z)))";
}
public static String particlePos(String centerHalf, String offset) {
return "vec4(" + centerHalf + ".xyz + " + offset + " * " + centerHalf + ".w, 1.0)";
}
public static String particleUv(String uv, String corner) {
return "vec4("
+ "mix(" + uv + ".x, " + uv + ".z, " + corner + ".x), "
+ "mix(" + uv + ".y, " + uv + ".w, " + corner + ".y), "
+ "0.0, 1.0)";
}
// wHalfWidth is vanilla's rainXCoords table.
private static final String WEATHER_PROLOGUE = """
vec2 {p}wColumn = {span}.xy;
float {p}wBottom = {span}.z;
float {p}wTop = {span}.w;
vec2 {p}wLightmap = {params}.xy;
float {p}wHash = {params}.z;
bool {p}wSnow = {params}.w > 0.5;
vec3 {p}wTranslate = {p}WeatherParams0.xyz;
float {p}wInvRadius = {p}WeatherParams0.w;
vec2 {p}wCameraFrac = {p}WeatherParams1.xy;
float {p}wPartialTicks = {p}WeatherParams1.z;
float {p}wAge = {p}WeatherParams1.w;
float {p}wRainScroll = {p}WeatherParams2.x;
float {p}wSnowScroll = {p}WeatherParams2.y;
float {p}wStrength = {p}WeatherParams2.z;
vec2 {p}wHalfWidth = vec2(-{p}wColumn.y, {p}wColumn.x) * (0.5 / sqrt(dot({p}wColumn, {p}wColumn)));
vec2 {p}wCornerXZ = {p}wColumn + vec2(0.5) + ({corner}.x * 2.0 - 1.0) * {p}wHalfWidth;
float {p}wCornerY = mix({p}wBottom, {p}wTop, {corner}.y);
{vertex} = vec4(vec3({p}wCornerXZ.x, {p}wCornerY, {p}wCornerXZ.y) + {p}wTranslate, 1.0);
float {p}wDistance = length({p}wColumn + vec2(0.5) - {p}wCameraFrac) * {p}wInvRadius;
float {p}wFade = 1.0 - {p}wDistance * {p}wDistance;
float {p}wScroll;
vec2 {p}wDrift;
float {p}wAlpha;
if ({p}wSnow) {
{p}wScroll = ({p}wSnowScroll + {p}wPartialTicks) / 512.0;
{p}wDrift = vec2({jitter}.x + {p}wAge * 0.01 * {jitter}.y, {jitter}.z + {p}wAge * {jitter}.w * 0.001);
{p}wAlpha = {p}wFade * 0.3 + 0.5;
} else {
{p}wScroll = (mod({p}wRainScroll + {p}wHash, 32.0) + {p}wPartialTicks) / 32.0 * (3.0 + {jitter}.x);
{p}wDrift = vec2(0.0);
{p}wAlpha = {p}wFade * 0.5 + 0.5;
}
{tex0} = vec4({corner}.x + {p}wDrift.x, {p}wCornerY * 0.25 + {p}wScroll + {p}wDrift.y, 0.0, 1.0);
{tex1} = vec4({p}wLightmap, 0.0, 1.0);
{color} = vec4(1.0, 1.0, 1.0, {p}wAlpha * {p}wStrength);""";
public static String weatherPrologue(String uniformPrefix, String attribPrefix, String corner,
String vertex, String color, String tex0, String tex1, String sep) {
return WEATHER_PROLOGUE
.replace("{p}", uniformPrefix)
.replace("{span}", attribPrefix + "InstColumnSpan")
.replace("{jitter}", attribPrefix + "InstJitter")
.replace("{params}", attribPrefix + "InstWeatherParams")
.replace("{corner}", corner)
.replace("{vertex}", vertex)
.replace("{color}", color)
.replace("{tex0}", tex0)
.replace("{tex1}", tex1)
.replace("\n", sep);
}
public static String cubePrelude(String prefix, String mid, String delta, String normal, String tex, String sep) {
return "float " + prefix + "cubeMirror = " + tex + ".z < 0.0 ? 1.0 : 0.0;" + sep
+ "vec2 " + prefix + "cubeMidU = " + mid + ".xy - " + prefix + "cubeMirror * " + normal + ".x;" + sep
+ "vec2 " + prefix + "cubeU = " + prefix + "cubeMidU + (1.0 - 2.0 * " + prefix + "cubeMirror) * " + delta + ".xy;" + sep
+ "vec2 " + prefix + "cubeV = " + mid + ".zw + " + delta + ".zw;";
}
public static String cubeUv(String tex, String lightScale, String uA, String uZ, String vA, String vZ) {
return "vec4("
+ tex + ".x + " + uA + " * abs(" + tex + ".z) + " + uZ + " * " + lightScale + ".z, "
+ tex + ".y + " + vA + " * " + tex + ".w + " + vZ + " * " + lightScale + ".w, "
+ "0.0, 1.0)";
}
public static String[] attributeDecls(String prefix, Instancing kind) {
final ArrayList<String> decls = new ArrayList<>(9);
switch (kind) {
case NONE -> {}
case TEMPLATE -> addHead(decls, prefix, "vec2", "InstLightmap");
case CUBE -> {
decls.add(decl(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), "vec4", prefix + "CubeMid"));
decls.add(decl(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation(), "vec4", prefix + "CubeDelta"));
addHead(decls, prefix, "vec4", "InstLightmapScale");
decls.add(decl(CubeInstancedAttribs.LOC_CUBE_TEX, "vec4", prefix + "CubeTex"));
}
case PARTICLE -> {
decls.add(decl(ParticleInstancedAttribs.LOC_CENTER_HALF, "vec4", prefix + "InstCenterHalf"));
decls.add(decl(ParticleInstancedAttribs.LOC_UV, "vec4", prefix + "InstUv"));
decls.add(decl(ParticleInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
decls.add(decl(ParticleInstancedAttribs.LOC_LIGHTMAP, "vec2", prefix + "InstLightmap"));
}
case WEATHER -> {
decls.add(decl(WeatherInstancedAttribs.LOC_COLUMN_SPAN, "vec4", prefix + "InstColumnSpan"));
decls.add(decl(WeatherInstancedAttribs.LOC_JITTER, "vec4", prefix + "InstJitter"));
decls.add(decl(WeatherInstancedAttribs.LOC_PARAMS, "vec4", prefix + "InstWeatherParams"));
}
}
return decls.toArray(String[]::new);
}
private static void addHead(ArrayList<String> decls, String prefix, String lightmapType, String lightmapName) {
decls.add(decl(InstancedAttribs.LOC_ROW0, "vec4", prefix + "InstRow0"));
decls.add(decl(InstancedAttribs.LOC_ROW1, "vec4", prefix + "InstRow1"));
decls.add(decl(InstancedAttribs.LOC_ROW2, "vec4", prefix + "InstRow2"));
decls.add(decl(InstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
decls.add(decl(InstancedAttribs.LOC_OVERLAY, "vec4", prefix + "InstOverlay"));
decls.add(decl(InstancedAttribs.LOC_LIGHTMAP, lightmapType, prefix + lightmapName));
}
private static String decl(int loc, String type, String name) {
return "layout(location = " + loc + ") in " + type + " " + name + ";";
}
}
@@ -0,0 +1,21 @@
package com.gtnewhorizons.angelica.glsm.ffp;
public enum Instancing {
NONE(""),
TEMPLATE("_instanced"),
CUBE("_cubeinstanced"),
PARTICLE("_particleinstanced"),
WEATHER("_weatherinstanced");
public static final Instancing[] VALUES = values();
public final String variantSuffix;
Instancing(String variantSuffix) {
this.variantSuffix = variantSuffix;
}
public boolean hasInstanceHead() {
return this == TEMPLATE || this == CUBE;
}
}
@@ -0,0 +1,50 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class ParticleInstancedAttribs {
public static final int LOC_CENTER_HALF = 5;
public static final int LOC_UV = 6;
public static final int LOC_COLOR = InstancedAttribs.LOC_COLOR;
public static final int LOC_LIGHTMAP = InstancedAttribs.LOC_LIGHTMAP;
public static final int OFFSET_CENTER_HALF = 0;
public static final int OFFSET_UV = 16;
public static final int OFFSET_COLOR = 32;
public static final int OFFSET_LIGHTMAP = 36;
public static final int STRIDE = 48;
public static void pointInstanceAttribs(long base) {
GLStateManager.glVertexAttribPointer(LOC_CENTER_HALF, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_CENTER_HALF);
GLStateManager.glVertexAttribPointer(LOC_UV, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_UV);
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP);
}
static void enableInstanceArrays() {
InstancedAttribs.enableInstanced(LOC_CENTER_HALF);
InstancedAttribs.enableInstanced(LOC_UV);
InstancedAttribs.enableInstanced(LOC_COLOR);
InstancedAttribs.enableInstanced(LOC_LIGHTMAP);
}
public static void writeInstance(long ptr, float cx, float cy, float cz, float half, float u0, float v0, float u1, float v1, int colorABGR, int packedLight) {
memPutFloat(ptr + OFFSET_CENTER_HALF, cx);
memPutFloat(ptr + OFFSET_CENTER_HALF + 4, cy);
memPutFloat(ptr + OFFSET_CENTER_HALF + 8, cz);
memPutFloat(ptr + OFFSET_CENTER_HALF + 12, half);
memPutFloat(ptr + OFFSET_UV, u0);
memPutFloat(ptr + OFFSET_UV + 4, v0);
memPutFloat(ptr + OFFSET_UV + 8, u1);
memPutFloat(ptr + OFFSET_UV + 12, v1);
memPutInt(ptr + OFFSET_COLOR, colorABGR);
InstancedAttribs.writeLightmap(ptr + OFFSET_LIGHTMAP, packedLight);
}
private ParticleInstancedAttribs() {}
}
@@ -0,0 +1,160 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class ParticleQuadMesh {
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
public static final int VERTEX_COUNT = 4;
private static final int VERTEX_STRIDE = 24;
private static final int OFFSET_OFFSET = 0;
private static final int OFFSET_CORNER = 12;
private static final int OFFSET_TANGENT = 20;
private static final float[] CORNER_A = { -1.0f, -1.0f, 1.0f, 1.0f };
private static final float[] CORNER_B = { -1.0f, 1.0f, 1.0f, -1.0f };
private static int vao;
private static int vbo;
private static ByteBuffer staging;
private static boolean written;
private static float lastRotX, lastRotXZ, lastRotZ, lastRotYZ, lastRotXY;
private ParticleQuadMesh() {}
public static float cornerA(int corner) {
return CORNER_A[corner];
}
public static float cornerB(int corner) {
return CORNER_B[corner];
}
public static float offsetX(float a, float b, float rotX, float rotYZ) {
return a * rotX + b * rotYZ;
}
public static float offsetY(float b, float rotXZ) {
return b * rotXZ;
}
public static float offsetZ(float a, float b, float rotZ, float rotXY) {
return a * rotZ + b * rotXY;
}
public static int vao() {
if (vao == 0) build();
return vao;
}
public static void update(float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
if (vao == 0) build();
if (written && rotX == lastRotX && rotXZ == lastRotXZ && rotZ == lastRotZ && rotYZ == lastRotYZ && rotXY == lastRotXY) {
return;
}
lastRotX = rotX;
lastRotXZ = rotXZ;
lastRotZ = rotZ;
lastRotYZ = rotYZ;
lastRotXY = rotXY;
written = true;
write(memAddress0(staging), rotX, rotXZ, rotZ, rotYZ, rotXY);
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
staging.position(0).limit(VERTEX_COUNT * VERTEX_STRIDE);
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, staging);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
private static void write(long dst, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
final int tangent = packTangent(rotX, 0.0f, rotZ);
long ptr = dst;
for (int i = 0; i < VERTEX_COUNT; i++) {
final float a = cornerA(i);
final float b = cornerB(i);
memPutFloat(ptr + OFFSET_OFFSET, offsetX(a, b, rotX, rotYZ));
memPutFloat(ptr + OFFSET_OFFSET + 4, offsetY(b, rotXZ));
memPutFloat(ptr + OFFSET_OFFSET + 8, offsetZ(a, b, rotZ, rotXY));
memPutFloat(ptr + OFFSET_CORNER, (a + 1.0f) * 0.5f);
memPutFloat(ptr + OFFSET_CORNER + 4, (b + 1.0f) * 0.5f);
memPutInt(ptr + OFFSET_TANGENT, tangent);
ptr += VERTEX_STRIDE;
}
}
private static int packTangent(float x, float y, float z) {
final float lenSq = x * x + y * y + z * z;
final float nx, ny, nz;
if (lenSq < 1.0e-12f) {
nx = 1.0f;
ny = 0.0f;
nz = 0.0f;
} else {
final float inv = (float) (1.0 / Math.sqrt(lenSq));
nx = x * inv;
ny = y * inv;
nz = z * inv;
}
return NormI8.pack(nx, ny, nz) | (127 << 24);
}
private static void build() {
final int savedVAO = GLStateManager.getBoundVAO();
final int savedVBO = GLStateManager.getBoundVBO();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, (long) VERTEX_COUNT * VERTEX_STRIDE, GL15.GL_DYNAMIC_DRAW);
attrib(VertexFormatElement.Usage.POSITION.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_OFFSET);
attrib(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), 2, GL11.GL_FLOAT, false, OFFSET_CORNER);
attrib(ImmediateExtendedAttribHandler.LOC_TANGENT, 4, GL11.GL_BYTE, true, OFFSET_TANGENT);
ParticleInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(savedVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
if (staging == null) staging = memAlloc(VERTEX_COUNT * VERTEX_STRIDE);
written = false;
}
private static void attrib(int loc, int size, int type, boolean normalized, int offset) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, VERTEX_STRIDE, offset);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
if (staging != null) {
memFree(staging);
staging = null;
}
written = false;
}
}
@@ -102,7 +102,11 @@ public class Program {
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) backend.uniform1i(ffpProgram.locSampler[i], i);
}
backend.useProgram(previousProgram);
if (previousProgram != 0) {
backend.useProgram(previousProgram);
} else {
ShaderManager.getInstance().invalidateProgram();
}
return ffpProgram;
} catch (RuntimeException e) {
@@ -9,7 +9,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.IdentityHashMap;
@@ -271,25 +270,6 @@ public class ShaderCache {
}
}
/** Destroy all cached programs and clear caches. */
public void destroy() {
for (int i = 0; i < capacity; i++) {
if (programs[i] != null) {
programs[i].destroy();
}
}
Arrays.fill(keys, 0);
Arrays.fill(programs, null);
Arrays.fill(fragSources, null);
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
GLStateManager.LOGGER.debug("FFP shader cache cleared");
}
public int getProgramCount() {
return count;
}
@@ -4,12 +4,13 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
@@ -26,45 +27,42 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
*/
public final class ShaderManager {
private static final class Holder {
static final ShaderManager INSTANCE = new ShaderManager();
}
private final ShaderCache cache = new ShaderCache();
private static final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
@Getter
private boolean active = false;
private boolean active;
private Program currentProgram = null;
private int lastBoundProgramId = -1;
private int lastBoundProgramId;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
private int currentFKLen = 0;
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
private static final Vector4f[] currentTexCoords = {
public final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
public final Vector4f[] currentTexCoords = {
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
};
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0]);
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
public final Vec3fStack normalStack = new Vec3fStack(currentNormal, StackIdAllocator.nextId());
public final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0], StackIdAllocator.nextId());
public int normalGeneration;
public int texCoordGeneration;
private int preDrawCalls;
private int lastFramePreDrawCalls;
public static long variantSwitches;
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
public static Vector3f getCurrentNormal() { return GLStateManager.ctx().ffp.currentNormal; }
public static Vector4f getCurrentTexCoord() { return GLStateManager.ctx().ffp.currentTexCoords[0]; }
public static Vec3fStack getNormalStack() { return GLStateManager.ctx().ffp.normalStack; }
public static Vec4fStack getTexCoordStack() { return GLStateManager.ctx().ffp.texCoordStack; }
@Getter private static boolean enabled = false;
private ShaderManager() {
static {
cache.setDumpDir(SystemProperties.shaderDumpDir("ffp"));
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
VAOManager.setCurrentVertexFlags(format.getVertexFlags());
@@ -72,14 +70,20 @@ public final class ShaderManager {
});
}
public static ShaderManager getInstance() {
return Holder.INSTANCE;
public ShaderManager() {
active = enabled;
lastBoundProgramId = enabled ? 0 : -1;
}
public void enable() {
public static ShaderManager getInstance() {
return GLStateManager.ctx().ffp;
}
public static void enable() {
warmUp();
enabled = true;
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.activeProgram == 0) glCtx.ffp.activate();
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
@@ -93,6 +97,8 @@ public final class ShaderManager {
final long vkPacked = VertexKey.packFromState(true, true, true, true, fragMask);
final VertexKey vk = VertexKey.fromPacked(vkPacked);
VertexShaderGenerator.generate(vk);
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.CUBE)));
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.PARTICLE)));
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
GeometryShaderGenerator.generate(vk);
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
@@ -104,13 +110,21 @@ public final class ShaderManager {
}
}
public void disable() {
public static void disable() {
enabled = false;
}
public void activate() {
active = true;
lastBoundProgramId = GLStateManager.getActiveProgram();
invalidateProgram();
}
/** Invalidate both binding and state key so the next draw selects and binds an FFP variant. */
public void invalidateProgram() {
lastBoundProgramId = -1;
currentProgram = null;
currentVertexKeyPacked = Long.MIN_VALUE;
uniforms.invalidateBinding();
}
public void deactivate() {
@@ -122,44 +136,46 @@ public final class ShaderManager {
GLStateManager.forceAttribDefaultsDirty();
}
public void preDraw() {
public void preDraw(GLContextState glCtx) {
GLSMHooks.resolvePendingProgram();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
// Handle FFP & Iris uniforms
final int currentProgramId = GLStateManager.getActiveProgram();
final int currentProgramId = glCtx.activeProgram;
if (currentProgramId != 0) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId, glCtx)) {
return; // Don't emulate FFP on non-iris core shaders
}
}
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final int vertexFlags = glCtx.vaos.getVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
GLStateManager.flushDeferredVertexAttribs(glCtx, hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
preDrawCalls++;
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fkLen = FragmentKey.packFromState(currentFKScratch, glCtx);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask, glCtx);
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
commitVariant(vkPacked, fkLen);
}
uploadUniforms();
uploadUniforms(glCtx);
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
// Building a variant can invalidate the previous binding and key.
currentVertexKeyPacked = vkPacked;
final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) {
if (Tracy.ENABLED) variantSwitches++;
@@ -168,47 +184,38 @@ public final class ShaderManager {
}
}
private void uploadUniforms() {
private void uploadUniforms(GLContextState glCtx) {
if (currentProgram != null) {
uniforms.upload();
uniforms.upload(glCtx);
}
}
public static void endFrame() {
final ShaderManager sm = Holder.INSTANCE;
final ShaderManager sm = GLStateManager.ctx().ffp;
sm.lastFramePreDrawCalls = sm.preDrawCalls;
sm.preDrawCalls = 0;
sm.uniforms.endFrame();
FfpExtendedAttribs.endFrame();
}
public static void setCurrentNormal(float x, float y, float z) {
public void setNormal(float x, float y, float z) {
normalStack.beforeModify();
currentNormal.set(x, y, z);
normalGeneration++;
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
public void setTexCoord(float s, float t, float r, float q) {
texCoordStack.beforeModify();
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
public void setTexCoord(int unit, float s, float t, float r, float q) {
if (unit == 0) texCoordStack.beforeModify();
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
}
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void destroy() {
cache.destroy();
uniforms.destroy();
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
QuadConverter.destroy();
active = false;
currentProgram = null;
}
public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants) | UBO: %d/%d draws wrote (mat %d, light %d, frag %d, misc %d)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount(),
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.FogState;
@@ -7,9 +8,9 @@ import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.LineState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
@@ -27,7 +28,6 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Maintains the shared {@link FFPUniformBlock} image from GLSM cached state and streams it to the UBO ring.
@@ -48,6 +48,7 @@ public class Uniforms {
private float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
private int lineStipple = -1;
private int weatherGen = -1;
int blockWrites;
int blockSkips;
@@ -82,66 +83,67 @@ public class Uniforms {
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
public void upload() {
public void upload(GLContextState glCtx) {
boolean dirty = false;
contentChanged = false;
final int mvG = GLStateManager.getMvGeneration();
final int projG = GLStateManager.getProjGeneration();
final int mvG = glCtx.mvGeneration;
final int projG = glCtx.projGeneration;
final boolean mvChanged = mvG != mvGen;
final boolean projChanged = projG != projGen;
if (mvChanged || projChanged) {
stageMatrices(mvChanged, projChanged);
stageMatrices(mvChanged, projChanged, glCtx);
mvGen = mvG;
projGen = projG;
stagedMatrices++;
dirty = true;
}
final int mvLinG = GLStateManager.getMvLinearGeneration();
final int mvLinG = glCtx.mvLinearGeneration;
if (mvLinG != mvLinearGen) {
stageNormalMatrix();
stageNormalMatrix(glCtx);
mvLinearGen = mvLinG;
dirty = true;
}
final int texMatG = GLStateManager.getTexMatrixGeneration();
final int texMatG = glCtx.texMatrixGeneration;
if (texMatG != texMatGen) {
stageTextureMatrices();
stageTextureMatrices(glCtx);
texMatGen = texMatG;
dirty = true;
}
final int litG = GLStateManager.getLightingGeneration();
final int litG = glCtx.lightingGeneration;
if (litG != lightingGen) {
stageLighting();
stageLighting(glCtx);
lightingGen = litG;
stagedLighting++;
dirty = true;
}
final int colG = GLStateManager.getColorGeneration();
final int colG = glCtx.colorGeneration;
if (colG != colorGen) {
stageCurrentColor();
stageCurrentColor(glCtx);
colorGen = colG;
stagedColor++;
dirty = true;
}
final int normG = ShaderManager.getNormalGeneration();
final ShaderManager ffp = glCtx.ffp;
final int normG = ffp.normalGeneration;
if (normG != normalGen) {
final Vector3f normal = ShaderManager.getCurrentNormal();
final Vector3f normal = ffp.currentNormal;
putVec3(FFPUniformBlock.CURRENT_NORMAL, normal.x, normal.y, normal.z);
normalGen = normG;
stagedNormal++;
dirty = true;
}
final int tcG = ShaderManager.getTexCoordGeneration();
final int tcG = ffp.texCoordGeneration;
if (tcG != texCoordGen) {
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ShaderManager.getCurrentTexCoord(0));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ShaderManager.getCurrentTexCoord(2));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ShaderManager.getCurrentTexCoord(3));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ffp.currentTexCoords[0]);
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ffp.currentTexCoords[2]);
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ffp.currentTexCoords[3]);
texCoordGen = tcG;
stagedTexCoord++;
dirty = true;
}
final float brightX = GLSMConfig.lastBrightnessX;
final float brightY = GLSMConfig.lastBrightnessY;
final float brightX = glCtx.lastBrightnessX;
final float brightY = glCtx.lastBrightnessY;
if (brightX != lightmapX || brightY != lightmapY) {
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD, brightX);
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD + 4, brightY);
@@ -150,35 +152,36 @@ public class Uniforms {
stagedLightmap++;
dirty = true;
}
final int tgG = GLStateManager.getTexGenGeneration();
final int tgG = glCtx.texGenGeneration;
if (tgG != texGenGen) {
stageTexGen();
stageTexGen(glCtx);
texGenGen = tgG;
stagedTexGen++;
dirty = true;
}
final int cpG = GLStateManager.getClipPlaneGeneration();
final int cpG = glCtx.clipPlaneGeneration;
if (cpG != clipPlaneGen) {
stageClipPlanes();
stageClipPlanes(glCtx);
clipPlaneGen = cpG;
stagedClipPlanes++;
dirty = true;
}
final int fragG = GLStateManager.getFragmentGeneration();
final int fragG = glCtx.fragmentGeneration;
if (fragG != fragmentGen) {
stageFragment();
stageFragment(glCtx);
fragmentGen = fragG;
stagedFragment++;
dirty = true;
}
final float lw = GLStateManager.getLineState().getWidth();
final LineState line = glCtx.lineState;
final float lw = line.getWidth();
if (lw != lineWidth) {
putFloat(FFPUniformBlock.LINE_WIDTH, lw);
lineWidth = lw;
stagedMisc++;
dirty = true;
}
final ViewportState vp = GLStateManager.getViewportState();
final ViewportState vp = glCtx.viewportState;
if (vp.x != viewportX || vp.y != viewportY || vp.width != viewportWidth || vp.height != viewportHeight) {
putFloat(FFPUniformBlock.VIEWPORT_SIZE, vp.width);
putFloat(FFPUniformBlock.VIEWPORT_SIZE + 4, vp.height);
@@ -190,8 +193,13 @@ public class Uniforms {
stagedMisc++;
dirty = true;
}
final LineState line = GLStateManager.getLineState();
final int stippleFactor = line.getStippleFactor() < 1 ? 1 : line.getStippleFactor();
if (WeatherParams.generation != weatherGen) {
stageWeatherParams();
weatherGen = WeatherParams.generation;
stagedMisc++;
dirty = true;
}
final int stippleFactor = Math.max(line.getStippleFactor(), 1);
final int stipplePacked = (line.getStipplePattern() & 0xFFFF) | (stippleFactor << 16);
if (stipplePacked != lineStipple) {
putInt(FFPUniformBlock.LINE_STIPPLE, stipplePacked);
@@ -209,16 +217,16 @@ public class Uniforms {
ring = new UniformRingBuffer(RING_CAPACITY, FFPUniformBlock.SIZE);
}
final int offset = ring.writeBlock(staging);
RENDER_BACKEND.bindBufferRange(GL31.GL_UNIFORM_BUFFER, FFPUniformBlock.BINDING_POINT,
GLStateManager.glBindBufferRange(GL31.GL_UNIFORM_BUFFER, FFPUniformBlock.BINDING_POINT,
ring.getBufferId(), offset, FFPUniformBlock.SIZE);
bound = true;
blockWrites++;
}
}
private void stageMatrices(boolean mvChanged, boolean projChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
private void stageMatrices(boolean mvChanged, boolean projChanged, GLContextState glCtx) {
final Matrix4f mv = glCtx.modelViewMatrix;
final Matrix4f proj = glCtx.projectionMatrix;
if (mvChanged) {
putMat4(FFPUniformBlock.MODEL_VIEW_MATRIX, mv);
}
@@ -229,25 +237,30 @@ public class Uniforms {
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
}
private void stageNormalMatrix() {
GLStateManager.getModelViewMatrix().normal(normalMatrix);
private void stageNormalMatrix(GLContextState glCtx) {
glCtx.modelViewMatrix.normal(normalMatrix);
putMat3(FFPUniformBlock.NORMAL_MATRIX, normalMatrix);
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
putFloat(FFPUniformBlock.NORMAL_SCALE, scale);
putFloat(FFPUniformBlock.NORMAL_SCALE, rescaleFactor(normalMatrix, tempVec3));
}
private void stageTextureMatrices() {
putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, GLStateManager.getTextures().getTextureUnitMatrix(0));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, GLStateManager.getTextures().getTextureUnitMatrix(2));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, GLStateManager.getTextures().getTextureUnitMatrix(3));
putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, GLStateManager.getTextures().getTextureUnitMatrix(1));
static float rescaleFactor(Matrix3f normalMatrix, Vector3f scratch) {
final float f = normalMatrix.getColumn(2, scratch).lengthSquared();
return f > 1.0e-12f ? 1.0f / (float) Math.sqrt(f) : 1.0f;
}
private void stageLighting() {
final MaterialState mat = GLStateManager.getFrontMaterial();
final Vector4f lmAmbient = GLStateManager.getLightModel().ambient;
final LightState light0 = GLStateManager.getLightDataStates()[0];
final LightState light1 = GLStateManager.getLightDataStates()[1];
private void stageTextureMatrices(GLContextState glCtx) {
final TextureUnitArray textures = glCtx.textures;
putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, textures.getTextureUnitMatrix(0));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, textures.getTextureUnitMatrix(2));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, textures.getTextureUnitMatrix(3));
putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, textures.getTextureUnitMatrix(1));
}
private void stageLighting(GLContextState glCtx) {
final MaterialState mat = glCtx.frontMaterial;
final Vector4f lmAmbient = glCtx.lightModel.ambient;
final LightState light0 = glCtx.lightDataStates[0];
final LightState light1 = glCtx.lightDataStates[1];
putVec4(FFPUniformBlock.LIGHT_MODEL_AMBIENT, lmAmbient);
putVec4(FFPUniformBlock.MATERIAL_EMISSION, mat.emission);
@@ -281,8 +294,8 @@ public class Uniforms {
putVec3(offset, lightVal.x * materialVal.x, lightVal.y * materialVal.y, lightVal.z * materialVal.z);
}
private void stageCurrentColor() {
final var color = GLStateManager.getColor();
private void stageCurrentColor(GLContextState glCtx) {
final var color = glCtx.color;
putVec4(FFPUniformBlock.CURRENT_COLOR,
Math.clamp(0f, 1f, color.getRed()),
Math.clamp(0f, 1f, color.getGreen()),
@@ -290,8 +303,8 @@ public class Uniforms {
Math.clamp(0f, 1f, color.getAlpha()));
}
private void stageTexGen() {
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
private void stageTexGen(GLContextState glCtx) {
final TexGenState tg = glCtx.textures.getTexGenState(0);
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_S, tg.getObjectPlane(GL11.GL_S));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_T, tg.getObjectPlane(GL11.GL_T));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_R, tg.getObjectPlane(GL11.GL_R));
@@ -302,8 +315,8 @@ public class Uniforms {
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_Q, tg.getEyePlane(GL11.GL_Q));
}
private void stageClipPlanes() {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
private void stageClipPlanes(GLContextState glCtx) {
final ClipPlaneState cps = glCtx.clipPlaneState;
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
@@ -314,21 +327,24 @@ public class Uniforms {
}
}
private void stageFragment() {
putFloat(FFPUniformBlock.ALPHA_REF, GLStateManager.getAlphaState().getReference());
private void stageFragment(GLContextState glCtx) {
putFloat(FFPUniformBlock.ALPHA_REF, glCtx.alphaState.getReference());
for (int i = 0; i < 4; i++) {
final var envState = GLStateManager.getTextures().getTexEnvState(i);
final var envState = glCtx.textures.getTexEnvState(i);
putVec4(FFPUniformBlock.TEX_ENV_COLOR_0 + i * 16,
envState.envColorR, envState.envColorG, envState.envColorB, envState.envColorA);
}
putVec4(FFPUniformBlock.OVERLAY_COLOR,
GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
glCtx.overlayR, glCtx.overlayG,
glCtx.overlayB, glCtx.overlayA);
final var secondary = glCtx.secondaryColor;
putVec3(FFPUniformBlock.SECONDARY_COLOR, secondary.getRed(), secondary.getGreen(), secondary.getBlue());
// Mesa STATE_FOG_PARAMS_OPTIMIZED
final FogState fog = GLStateManager.getFogState();
final FogState fog = glCtx.fogState;
final float start = fog.getStart();
final float end = fog.getEnd();
final float density = fog.getDensity();
@@ -370,6 +386,15 @@ public class Uniforms {
putVec3(offset + 32, m.m20(), m.m21(), m.m22());
}
private void stageWeatherParams() {
putVec4(FFPUniformBlock.WEATHER_PARAMS_0, WeatherParams.translateX, WeatherParams.translateY,
WeatherParams.translateZ, WeatherParams.invRadius);
putVec4(FFPUniformBlock.WEATHER_PARAMS_1, WeatherParams.cameraFracX, WeatherParams.cameraFracZ,
WeatherParams.partialTicks, WeatherParams.age);
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.rainScroll, WeatherParams.snowScroll,
WeatherParams.rainStrength, 0.0f);
}
private void putVec4(int offset, Vector4f v) {
putVec4(offset, v.x, v.y, v.z, v.w);
}
@@ -421,6 +446,10 @@ public class Uniforms {
stagedMisc = 0;
}
void invalidateBinding() {
bound = false;
}
UniformRingBuffer getRing() {
return ring;
}
@@ -0,0 +1,161 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class UnitCubeMesh {
public static final int VERTEX_FLAGS = VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.NORMAL_BIT;
public static final int FACE_COUNT = 6;
public static final int VERTEX_COUNT = 24;
public static final int VERTEX_STRIDE = 64;
public static final int OFFSET_POSITION = 0;
private static final int OFFSET_COLOR = 12;
public static final int OFFSET_MID = 16;
public static final int OFFSET_DELTA = 32;
public static final int OFFSET_NORMAL = 48;
public static final int OFFSET_TANGENT = 60;
public static final float[][] CORNERS = {
{ 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 }, { 0, 1, 0 },
{ 0, 0, 1 }, { 1, 0, 1 }, { 1, 1, 1 }, { 0, 1, 1 },
};
public record Span(int a, int z) {
public int texels(int sizeA, int sizeZ) {
return a * sizeA + z * sizeZ;
}
}
public record Net(Span uLow, Span uHigh, Span vLow, Span vHigh) {
public Span u(int vertex) {
return vertex == 0 || vertex == 3 ? uHigh : uLow;
}
public Span v(int vertex) {
return vertex >= 2 ? vHigh : vLow;
}
}
public record Face(int[] corners, float[] normal, float[] tangent, Net net) {}
public static final Face[] FACES = {
new Face(new int[] { 5, 1, 2, 6 }, new float[] { 1, 0, 0 }, new float[] { 0, 0, 1, 1 }, new Net(new Span(1, 1), new Span(1, 2), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 0, 4, 7, 3 }, new float[] { -1, 0, 0 }, new float[] { 0, 0, -1, 1 }, new Net(new Span(0, 0), new Span(0, 1), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 5, 4, 0, 1 }, new float[] { 0, -1, 0 }, new float[] { 1, 0, 0, 1 }, new Net(new Span(0, 1), new Span(1, 1), new Span(0, 0), new Span(0, 1))),
new Face(new int[] { 2, 3, 7, 6 }, new float[] { 0, 1, 0 }, new float[] { 1, 0, 0, -1 }, new Net(new Span(1, 1), new Span(2, 1), new Span(0, 1), new Span(0, 0))),
new Face(new int[] { 1, 0, 3, 2 }, new float[] { 0, 0, -1 }, new float[] { 1, 0, 0, 1 }, new Net(new Span(0, 1), new Span(1, 1), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 4, 5, 6, 7 }, new float[] { 0, 0, 1 }, new float[] { -1, 0, 0, 1 }, new Net(new Span(1, 2), new Span(2, 2), new Span(0, 1), new Span(1, 1))),
};
private static int vao;
private static int vbo;
private UnitCubeMesh() {}
public static void write(long dst) {
long ptr = dst;
for (int f = 0; f < FACE_COUNT; f++) {
final Face face = FACES[f];
final Net net = face.net();
final float midUA = (net.uLow().a() + net.uHigh().a()) * 0.5f;
final float midUZ = (net.uLow().z() + net.uHigh().z()) * 0.5f;
final float midVA = (net.vLow().a() + net.vHigh().a()) * 0.5f;
final float midVZ = (net.vLow().z() + net.vHigh().z()) * 0.5f;
for (int c = 0; c < 4; c++) {
final float[] corner = CORNERS[face.corners()[c]];
final Span u = net.u(c);
final Span v = net.v(c);
memPutFloat(ptr + OFFSET_POSITION, corner[0]);
memPutFloat(ptr + OFFSET_POSITION + 4, corner[1]);
memPutFloat(ptr + OFFSET_POSITION + 8, corner[2]);
memPutInt(ptr + OFFSET_COLOR, 0xFFFFFFFF);
memPutFloat(ptr + OFFSET_MID, midUA);
memPutFloat(ptr + OFFSET_MID + 4, midUZ);
memPutFloat(ptr + OFFSET_MID + 8, midVA);
memPutFloat(ptr + OFFSET_MID + 12, midVZ);
memPutFloat(ptr + OFFSET_DELTA, u.a() - midUA);
memPutFloat(ptr + OFFSET_DELTA + 4, u.z() - midUZ);
memPutFloat(ptr + OFFSET_DELTA + 8, v.a() - midVA);
memPutFloat(ptr + OFFSET_DELTA + 12, v.z() - midVZ);
memPutFloat(ptr + OFFSET_NORMAL, face.normal()[0]);
memPutFloat(ptr + OFFSET_NORMAL + 4, face.normal()[1]);
memPutFloat(ptr + OFFSET_NORMAL + 8, face.normal()[2]);
final float[] tangent = face.tangent();
memPutInt(ptr + OFFSET_TANGENT, packTangent(tangent[0], tangent[1], tangent[2], tangent[3]));
ptr += VERTEX_STRIDE;
}
}
}
private static int packTangent(float x, float y, float z, float w) {
return NormI8.pack(x, y, z) | (((int) (w * 127.0f) & 0xFF) << 24);
}
public static int vao() {
if (vao == 0) build();
return vao;
}
private static void build() {
final int savedVAO = GLStateManager.getBoundVAO();
final int savedVBO = GLStateManager.getBoundVBO();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
final int bytes = VERTEX_COUNT * VERTEX_STRIDE;
final ByteBuffer data = memAlloc(bytes);
try {
write(memAddress0(data));
data.position(0).limit(bytes);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
} finally {
memFree(data);
}
attrib(VertexFormatElement.Usage.POSITION.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_POSITION);
attrib(VertexFormatElement.Usage.COLOR.getAttributeLocation(), 4, GL11.GL_UNSIGNED_BYTE, true, OFFSET_COLOR);
attrib(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), 4, GL11.GL_FLOAT, false, OFFSET_MID);
attrib(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation(), 4, GL11.GL_FLOAT, false, OFFSET_DELTA);
attrib(VertexFormatElement.Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_NORMAL);
attrib(ImmediateExtendedAttribHandler.LOC_TANGENT, 4, GL11.GL_BYTE, true, OFFSET_TANGENT);
CubeInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(savedVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
private static void attrib(int loc, int size, int type, boolean normalized, int offset) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, VERTEX_STRIDE, offset);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
}
@@ -14,30 +14,34 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
public final class VAOManager {
public static final int MAX_ATTRIBS = 16;
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
public final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
private static VAOData current = null;
private static int currentVertexFlags = 0;
private static Attrib[] currentAttribs = null;
public static int boundEBO = 0;
private VAOData current = null;
private int currentVertexFlags = 0;
private Attrib[] currentAttribs = null;
public int boundEBO = 0;
private static int clientSideEnabledCount = 0;
private int clientSideEnabledCount = 0;
public static void init(int defaultVAO) {
public void init(int defaultVAO) {
final VAOData data = new VAOData();
current = data;
currentAttribs = data.attribs;
vaoMap.put(defaultVAO, data);
}
public static void onBindEBO(int ebo) {
public boolean isInitialized() {
return current != null;
}
public void onBindEBO(int ebo) {
boundEBO = ebo;
}
public static void onBindVertexArrayPre(int vaoId) {
public void onBindVertexArrayPre(int vaoId) {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
@@ -62,14 +66,14 @@ public final class VAOManager {
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
public void onDeleteVertexArray(int vaoId) {
final VAOData data = vaoMap.remove(vaoId);
if (data == null) return;
removeEboOwner(data.ebo, data);
if (data == current) current = null;
}
public static void onDeleteBuffer(int buffer) {
public void onDeleteBuffer(int buffer) {
if (buffer == 0) return;
VAOData owner = eboOwners.remove(buffer);
while (owner != null) {
@@ -80,12 +84,12 @@ public final class VAOManager {
}
}
private static void addEboOwner(int ebo, VAOData data) {
private void addEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
data.nextEboOwner = eboOwners.put(ebo, data);
}
private static void removeEboOwner(int ebo, VAOData data) {
private void removeEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
VAOData node = eboOwners.get(ebo);
if (node == data) {
@@ -105,10 +109,11 @@ public final class VAOManager {
}
}
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public static int getCurrentVertexFlags() { return currentVertexFlags; }
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
public void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public int getVertexFlags() { return currentVertexFlags; }
public void setVertexFlags(int flags) { currentVertexFlags = flags; }
public static void setCurrentVertexFlags(int flags) { GLStateManager.ctx().vaos.currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
@@ -122,7 +127,7 @@ public final class VAOManager {
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
public void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
@@ -139,7 +144,7 @@ public final class VAOManager {
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
public void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
@@ -156,19 +161,19 @@ public final class VAOManager {
}
}
public static boolean isGenericPointerEnabled(int index) {
public boolean isGenericPointerEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public static void markConventional(int index) {
public void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public static void setEnabled(int index, boolean enabled) {
public void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled != enabled) {
@@ -177,7 +182,7 @@ public final class VAOManager {
a.enabled = enabled;
}
public static void enableAttribute(int index) {
public void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
@@ -186,7 +191,7 @@ public final class VAOManager {
a.enabled = true;
}
public static void disableAttribute(int index) {
public void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
@@ -196,7 +201,7 @@ public final class VAOManager {
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private static Attrib getAttrib(int index) {
private Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
@@ -205,28 +210,32 @@ public final class VAOManager {
return a;
}
public static boolean isClientSideAttrib(int index) {
public boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
}
public static int getAttribEffectiveStride(int index) {
public Attrib attrib(int index) {
return currentAttribs[index];
}
public int attribEffectiveStride(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.effectiveStride() : 0;
}
public static long getAttribOffset(int index) {
public long attribOffset(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.offset : 0;
}
public static int getAttribVBO(int index) {
public int attribVBO(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.vboId : 0;
}
public static @Nullable Attrib get(int index) {
return currentAttribs[index];
return GLStateManager.ctx().vaos.attrib(index);
}
@@ -236,7 +245,7 @@ public final class VAOManager {
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
public boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
@@ -244,15 +253,15 @@ public final class VAOManager {
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
private int clientArraysVBO = 0;
private int clientArraysVBOCapacity = 0;
private final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
public static void uploadClientArraysToVBO() {
public void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = currentAttribs[i];
if (a == null || !a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
@@ -275,7 +284,7 @@ public final class VAOManager {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = currentAttribs[i];
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
@@ -20,38 +21,43 @@ public final class VertexKey {
public static final int MAX_UNITS = 4;
public static final int FFP_LIGHT_COUNT = 2;
private final long packed;
private static final int BIT_LIGHTING = 0;
private static final int BIT_LIGHT0 = 1;
private static final int BIT_LIGHT1 = 2;
private static final int BIT_LIGHT0_DIR = 3;
private static final int BIT_LIGHT1_DIR = 4;
private static final int BIT_COLOR_MATERIAL = 5;
private static final int BIT_SEPARATE_SPECULAR = 6;
private static final int BIT_FOG = 7;
private static final int BIT_FOG_DIST_MODE = 8; // 2 bits: 8-9
private static final int BIT_NORMALIZE = 10;
private static final int BIT_RESCALE_NORMAL = 11;
// Per-unit texcoord enable: bits 12-15 (unit 0..3). Unit 1 doubles as the lightmap flag.
private static final int BIT_UNIT_TEX_BASE = 12;
private static final int BIT_HAS_VERTEX_COLOR = 16;
private static final int BIT_HAS_VERTEX_NORMAL = 17;
private static final int BIT_HAS_VERTEX_TEX = 18;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 19;
private static final int BIT_COLOR_MAT_MODE = 20; // 3 bits: 20-22
// TexGen modes (unit 0 only, for now): 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR).
private static final int BIT_TEXGEN_S = 23; // 3 bits: 23-25
private static final int BIT_TEXGEN_T = 26; // 3 bits: 26-28
private static final int BIT_TEXGEN_R = 29; // 3 bits: 29-31
private static final int BIT_TEXGEN_Q = 32; // 3 bits: 32-34
private static final int BIT_CLIP_PLANES = 35;
private static final int BIT_WIDE_LINE = 36;
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
private static final int BIT_UNIT_TEXMAT_BASE = 37;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41;
private static final int BIT_LINE_STIPPLE = 42;
private static final int BIT_INSTANCED = 44;
private static final int BIT_LIGHT_BASE = BIT_LIGHTING + 1;
private static final int BIT_LIGHT_DIR_BASE = BIT_LIGHT_BASE + FFP_LIGHT_COUNT;
private static final int BIT_COLOR_MATERIAL = BIT_LIGHT_DIR_BASE + FFP_LIGHT_COUNT;
private static final int BIT_SEPARATE_SPECULAR = BIT_COLOR_MATERIAL + 1;
private static final int BIT_FOG = BIT_SEPARATE_SPECULAR + 1;
private static final int BIT_FOG_DIST_MODE = BIT_FOG + 1;
private static final int BIT_NORMALIZE = BIT_FOG_DIST_MODE + 2;
private static final int BIT_RESCALE_NORMAL = BIT_NORMALIZE + 1;
private static final int BIT_UNIT_TEX_BASE = BIT_RESCALE_NORMAL + 1;
private static final int BIT_HAS_VERTEX_COLOR = BIT_UNIT_TEX_BASE + MAX_UNITS;
private static final int BIT_HAS_VERTEX_NORMAL = BIT_HAS_VERTEX_COLOR + 1;
private static final int BIT_HAS_VERTEX_TEX = BIT_HAS_VERTEX_NORMAL + 1;
private static final int BIT_HAS_VERTEX_LIGHTMAP = BIT_HAS_VERTEX_TEX + 1;
private static final int BIT_COLOR_MAT_MODE = BIT_HAS_VERTEX_LIGHTMAP + 1;
// TexGen modes are unit 0 only, for now.
private static final int BIT_TEXGEN_S = BIT_COLOR_MAT_MODE + 3;
private static final int BIT_TEXGEN_T = BIT_TEXGEN_S + 3;
private static final int BIT_TEXGEN_R = BIT_TEXGEN_T + 3;
private static final int BIT_TEXGEN_Q = BIT_TEXGEN_R + 3;
private static final int BIT_CLIP_PLANES = BIT_TEXGEN_Q + 3;
private static final int BIT_WIDE_LINE = BIT_CLIP_PLANES + 1;
private static final int BIT_UNIT_TEXMAT_BASE = BIT_WIDE_LINE + 1;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
private static final long INSTANCING_MASK = 0x7L;
static {
if (Instancing.VALUES.length > INSTANCING_MASK + 1) {
throw new IllegalStateException("Instancing has outgrown the VertexKey field; widen INSTANCING_MASK");
}
}
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -70,10 +76,8 @@ public final class VertexKey {
public long pack() { return packed; }
public boolean lightingEnabled() { return bit(BIT_LIGHTING); }
public boolean light0Enabled() { return bit(BIT_LIGHT0); }
public boolean light1Enabled() { return bit(BIT_LIGHT1); }
public boolean light0Directional() { return bit(BIT_LIGHT0_DIR); }
public boolean light1Directional() { return bit(BIT_LIGHT1_DIR); }
public boolean lightEnabled(int light) { return bit(BIT_LIGHT_BASE + light); }
public boolean lightDirectional(int light) { return bit(BIT_LIGHT_DIR_BASE + light); }
public boolean colorMaterialEnabled() { return bit(BIT_COLOR_MATERIAL); }
public boolean separateSpecular() { return bit(BIT_SEPARATE_SPECULAR); }
public boolean fogEnabled() { return bit(BIT_FOG); }
@@ -101,7 +105,7 @@ public final class VertexKey {
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public boolean instancedDraw() { return bit(BIT_INSTANCED); }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -130,59 +134,66 @@ public final class VertexKey {
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
return packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask, GLStateManager.ctx());
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask, GLContextState glCtx) {
long bits = 0;
final boolean lighting = GLStateManager.getLightingState().isEnabled();
final boolean lighting = glCtx.lightingState.isEnabled();
if (lighting) {
bits |= (1L << BIT_LIGHTING);
if (GLStateManager.getLightStates()[0].isEnabled()) {
bits |= (1L << BIT_LIGHT0);
for (int i = 0; i < FFP_LIGHT_COUNT; i++) {
if (!glCtx.lightStates[i].isEnabled()) continue;
bits |= (1L << (BIT_LIGHT_BASE + i));
// position.w == 0 means directional
if (GLStateManager.getLightDataStates()[0].position.w == 0.0f) {
bits |= (1L << BIT_LIGHT0_DIR);
}
}
if (GLStateManager.getLightStates()[1].isEnabled()) {
bits |= (1L << BIT_LIGHT1);
if (GLStateManager.getLightDataStates()[1].position.w == 0.0f) {
bits |= (1L << BIT_LIGHT1_DIR);
if (glCtx.lightDataStates[i].position.w == 0.0f) {
bits |= (1L << (BIT_LIGHT_DIR_BASE + i));
}
}
if (GLStateManager.getColorMaterial().isEnabled()) {
if (glCtx.colorMaterial.isEnabled()) {
bits |= (1L << BIT_COLOR_MATERIAL);
final int cmMode = encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue());
final int cmMode = encodeColorMaterialMode(glCtx.colorMaterialParameter.getValue());
bits |= ((long) cmMode & 0x7) << BIT_COLOR_MAT_MODE;
}
if (GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
if (glCtx.lightModel.colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
bits |= (1L << BIT_SEPARATE_SPECULAR);
}
}
if (GLStateManager.getFogMode().isEnabled()) {
if (glCtx.fogMode.isEnabled()) {
bits |= (1L << BIT_FOG);
final int fogDistMode = GLStateManager.getFogState().getFogDistanceMode();
final int fogDistMode = glCtx.fogState.getFogDistanceMode();
bits |= ((long) fogDistMode & 0x3) << BIT_FOG_DIST_MODE;
}
if (GLStateManager.getNormalizeState().isEnabled()) {
if (glCtx.normalizeState.isEnabled()) {
bits |= (1L << BIT_NORMALIZE);
}
if (GLStateManager.getRescaleNormalState().isEnabled()) {
if (glCtx.rescaleNormalState.isEnabled()) {
bits |= (1L << BIT_RESCALE_NORMAL);
}
final TextureUnitArray texUnit = GLStateManager.getTextures();
final TextureUnitArray texUnit = glCtx.textures;
for (int i = 0; i < MAX_UNITS; i++) {
if ((fragUnitMask & (1 << i)) != 0) {
bits |= (1L << (BIT_UNIT_TEX_BASE + i));
}
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
bits |= (1L << (BIT_UNIT_TEXMAT_BASE + i));
}
}
if (texUnit.texMatIdentityGen != glCtx.texMatrixGeneration) {
int mask = 0;
for (int i = 0; i < MAX_UNITS; i++) {
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
mask |= 1 << i;
}
}
texUnit.texMatIdentityMask = mask;
texUnit.texMatIdentityGen = glCtx.texMatrixGeneration;
}
bits |= ((long) texUnit.texMatIdentityMask) << BIT_UNIT_TEXMAT_BASE;
// TexGen (unit 0 only) - per-coordinate mode if enabled.
// TODO: per-unit texgen for units 2/3. Deferred - no MC code path uses glTexGen on those.
@@ -219,29 +230,31 @@ public final class VertexKey {
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw() && hasTexCoord) {
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw(glCtx) && hasTexCoord) {
bits |= (1L << BIT_UNIT23_UV_FROM_UNIT0);
}
if (GLStateManager.anyClipPlaneEnabled()) {
if (GLStateManager.anyClipPlaneEnabled(glCtx)) {
bits |= (1L << BIT_CLIP_PLANES);
}
if (GLStateManager.wideLineEmulationActive) {
if (glCtx.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
if (GLStateManager.lineStippleActive) {
if (glCtx.lineStippleActive) {
bits |= (1L << BIT_LINE_STIPPLE);
}
if (GLStateManager.instancedFfpDrawActive) {
bits |= (1L << BIT_INSTANCED);
}
bits |= (long) glCtx.ffpInstancing.ordinal() << BIT_INSTANCING;
return bits;
}
static long withInstancing(long packed, Instancing kind) {
return (packed & ~(INSTANCING_MASK << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
}
@@ -264,12 +277,6 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%b]",
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancedDraw());
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancing());
}
}
@@ -21,6 +21,10 @@ public final class VertexShaderGenerator {
emitOutputs(sb, key);
sb.append("void main() {\n");
if (key.instancing() == Instancing.WEATHER) {
sb.append(" ").append(InstancedGlslHelpers.weatherPrologue("u_", "a_", "a_TexCoord0",
"vec4 weatherVertex", "vec4 weatherColor", "vec4 weatherTex0", "vec4 weatherTex1", "\n ")).append('\n');
}
emitPositionTransform(sb, key);
if (key.lightingEnabled()) {
emitNormalTransform(sb, key);
@@ -55,31 +59,44 @@ public final class VertexShaderGenerator {
}
private static void emitAttributes(StringBuilder sb, VertexKey key) {
final Instancing kind = key.instancing();
sb.append("// Vertex attributes\n");
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
if (key.hasVertexColor()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
}
if (key.hasVertexTexCoord()) {
if (kind == Instancing.PARTICLE || kind == Instancing.WEATHER) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
} else {
if (key.hasVertexColor()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
}
if (kind != Instancing.CUBE) {
if (key.hasVertexTexCoord()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
}
if (key.hasVertexLightmap()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
}
}
if (key.hasVertexNormal() || kind == Instancing.CUBE) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
}
if (key.hasVertexLightmap()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
}
if (key.hasVertexNormal()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
if (key.instancedDraw()) {
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL0).append(") in vec4 a_InstCol0;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL1).append(") in vec4 a_InstCol1;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL2).append(") in vec4 a_InstCol2;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL3).append(") in vec4 a_InstCol3;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_COLOR).append(") in vec4 a_InstColor;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_LIGHTMAP).append(") in vec2 a_InstLightmap;\n");
for (String d : InstancedGlslHelpers.attributeDecls("a_", kind)) {
sb.append(d).append('\n');
}
sb.append('\n');
}
private static final String PARTICLE_UV = InstancedGlslHelpers.particleUv("a_InstUv", "a_TexCoord0");
private static String unit0Source(VertexKey key) {
return switch (key.instancing()) {
case CUBE -> "cubeUv";
case PARTICLE -> PARTICLE_UV;
case WEATHER -> "weatherTex0";
case NONE, TEMPLATE -> key.hasVertexTexCoord() ? "vec4(a_TexCoord0, 0.0, 1.0)" : "u_CurrentTexCoord0";
};
}
private static void emitLineStart(StringBuilder sb) {
sb.append(" vec2 ndc = gl_Position.xy / gl_Position.w;\n");
sb.append(" v_LineStart = u_Viewport.xy + (ndc * 0.5 + 0.5) * u_Viewport.zw;\n\n");
@@ -93,6 +110,10 @@ public final class VertexShaderGenerator {
private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n");
if (key.instancing().hasInstanceHead()) {
sb.append("out vec4 v_Overlay;\n");
sb.append("invariant gl_Position;\n");
}
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
@@ -116,11 +137,18 @@ public final class VertexShaderGenerator {
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Position transform\n");
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
if (key.instancedDraw()) {
sb.append(" mat4 instMV = mat4(a_InstCol0, a_InstCol1, a_InstCol2, a_InstCol3);\n");
if (key.instancing() == Instancing.PARTICLE) {
sb.append(" vec4 pos4 = ").append(InstancedGlslHelpers.particlePos("a_InstCenterHalf", "a_Position")).append(";\n");
} else if (key.instancing() == Instancing.WEATHER) {
sb.append(" vec4 pos4 = weatherVertex;\n");
} else {
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
}
if (key.instancing().hasInstanceHead()) {
sb.append(" mat4 instMV = ").append(InstancedGlslHelpers.mat4FromRows("a_InstRow0", "a_InstRow1", "a_InstRow2")).append(";\n");
sb.append(" vec4 eyePos = instMV * pos4;\n");
sb.append(" gl_Position = u_ProjectionMatrix * eyePos;\n");
sb.append(" v_Overlay = a_InstOverlay;\n");
} else {
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
@@ -134,8 +162,8 @@ public final class VertexShaderGenerator {
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n");
final String srcNormal = key.hasVertexNormal() ? "a_Normal" : "u_CurrentNormal";
if (key.instancedDraw()) {
final String srcNormal = key.hasVertexNormal() && key.instancing() != Instancing.PARTICLE ? "a_Normal" : "u_CurrentNormal";
if (key.instancing().hasInstanceHead()) {
sb.append(" vec3 normal = normalize(mat3(instMV) * ").append(srcNormal).append(");\n");
sb.append('\n');
return;
@@ -156,9 +184,13 @@ public final class VertexShaderGenerator {
if (key.colorMaterialEnabled()) {
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
sb.append(" vec4 matColor = ");
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancedDraw()) {
sb.append(" * a_InstColor");
if (key.instancing() == Instancing.PARTICLE) {
sb.append("a_InstColor");
} else {
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancing().hasInstanceHead()) {
sb.append(" * a_InstColor");
}
}
sb.append(";\n");
@@ -182,11 +214,10 @@ public final class VertexShaderGenerator {
}
// Per-light accumulation
if (key.light0Enabled()) {
emitLightContribution(sb, key, 0);
}
if (key.light1Enabled()) {
emitLightContribution(sb, key, 1);
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
if (key.lightEnabled(i)) {
emitLightContribution(sb, key, i);
}
}
sb.append(" v_Color = vec4(clamp(color0, 0.0, 1.0), outAlpha);\n");
@@ -198,7 +229,7 @@ public final class VertexShaderGenerator {
private static void emitLightContribution(StringBuilder sb, VertexKey key, int lightIndex) {
final String li = String.valueOf(lightIndex);
final boolean directional = (lightIndex == 0) ? key.light0Directional() : key.light1Directional();
final boolean directional = key.lightDirectional(lightIndex);
final String posUniform = "u_Light" + li + "Position";
sb.append(" { // Light ").append(li).append('\n');
@@ -256,22 +287,28 @@ public final class VertexShaderGenerator {
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
sb.append(" // Color passthrough (no lighting)\n");
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
if (key.instancedDraw()) {
sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
} else {
sb.append(" v_Color = ").append(src).append(";\n");
switch (key.instancing()) {
case PARTICLE -> sb.append(" v_Color = a_InstColor;\n");
case WEATHER -> sb.append(" v_Color = weatherColor;\n");
case TEMPLATE, CUBE -> sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
case NONE -> sb.append(" v_Color = ").append(src).append(";\n");
}
sb.append('\n');
}
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
final boolean cube = key.instancing() == Instancing.CUBE;
final boolean unit0FromCube = cube && key.unitTexCoordEnabled(0) && !key.texGenEnabled();
final boolean unit23FromCube = cube && key.unit23UvFromUnit0() && (key.unitTexCoordEnabled(2) || key.unitTexCoordEnabled(3));
if (unit0FromCube || unit23FromCube) {
emitCubeTexCoord(sb);
}
if (key.texGenEnabled()) {
emitTexGenCoordGeneration(sb, key);
} else if (key.unitTexCoordEnabled(0)) {
sb.append(" // Texture coordinates - unit 0\n");
final String src = key.hasVertexTexCoord()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord0";
final String src = unit0Source(key);
if (key.unitTexMatEnabled(0)) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
} else {
@@ -280,20 +317,18 @@ public final class VertexShaderGenerator {
}
if (key.lightmapEnabled()) {
if (key.instancedDraw()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_InstLightmap, 0.0, 1.0);\n");
} else if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
final String lightmap = switch (key.instancing()) {
case PARTICLE, TEMPLATE -> "a_InstLightmap";
case CUBE -> "a_InstLightmapScale.xy";
case WEATHER -> "weatherTex1.xy";
case NONE -> key.hasVertexLightmap() ? "a_TexCoord1" : "u_CurrentLightmapCoord";
};
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(").append(lightmap).append(", 0.0, 1.0);\n");
}
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = key.unit23UvFromUnit0()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord" + i;
final String src = key.unit23UvFromUnit0() ? unit0Source(key) : "u_CurrentTexCoord" + i;
if (!key.unit23UvFromUnit0() && key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
@@ -302,6 +337,12 @@ public final class VertexShaderGenerator {
}
}
private static void emitCubeTexCoord(StringBuilder sb) {
sb.append(" // Cube texture net\n");
sb.append(" ").append(InstancedGlslHelpers.cubePrelude("", "a_CubeMid", "a_CubeDelta", "a_Normal", "a_CubeTex", "\n ")).append('\n');
sb.append(" vec4 cubeUv = ").append(InstancedGlslHelpers.cubeUv("a_CubeTex", "a_InstLightmapScale", "cubeU.x", "cubeU.y", "cubeV.x", "cubeV.y")).append(";\n\n");
}
/**
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
/**
* One rain or snow column per instance.
*/
public final class WeatherInstancedAttribs {
public static final int LOC_COLUMN_SPAN = ParticleInstancedAttribs.LOC_CENTER_HALF;
public static final int LOC_JITTER = ParticleInstancedAttribs.LOC_UV;
public static final int LOC_PARAMS = InstancedAttribs.LOC_COLOR;
public static final int OFFSET_COLUMN_SPAN = 0;
public static final int OFFSET_JITTER = 16;
public static final int OFFSET_PARAMS = 32;
public static final int STRIDE = 48;
public static void pointInstanceAttribs(long base) {
GLStateManager.glVertexAttribPointer(LOC_COLUMN_SPAN, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLUMN_SPAN);
GLStateManager.glVertexAttribPointer(LOC_JITTER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_JITTER);
GLStateManager.glVertexAttribPointer(LOC_PARAMS, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_PARAMS);
}
static void enableInstanceArrays() {
InstancedAttribs.enableInstanced(LOC_COLUMN_SPAN);
InstancedAttribs.enableInstanced(LOC_JITTER);
InstancedAttribs.enableInstanced(LOC_PARAMS);
}
private WeatherInstancedAttribs() {}
}
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* The per-frame half of the rain and snow instancing, set once per weather draw.
*/
public final class WeatherParams {
public static float translateX;
public static float translateY;
public static float translateZ;
public static float invRadius;
public static float cameraFracX;
public static float cameraFracZ;
public static float partialTicks;
public static float age;
public static float rainScroll;
public static float snowScroll;
public static float rainStrength;
public static int generation;
private WeatherParams() {}
public static void set(float translateX, float translateY, float translateZ, float invRadius,
float cameraFracX, float cameraFracZ, float partialTicks, float age,
float rainScroll, float snowScroll, float rainStrength) {
WeatherParams.translateX = translateX;
WeatherParams.translateY = translateY;
WeatherParams.translateZ = translateZ;
WeatherParams.invRadius = invRadius;
WeatherParams.cameraFracX = cameraFracX;
WeatherParams.cameraFracZ = cameraFracZ;
WeatherParams.partialTicks = partialTicks;
WeatherParams.age = age;
WeatherParams.rainScroll = rainScroll;
WeatherParams.snowScroll = snowScroll;
WeatherParams.rainStrength = rainStrength;
generation++;
}
}
@@ -0,0 +1,69 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The four corners every rain and snow column is expanded from. x picks the end of the billboard, y picks
* the bottom or top edge.
*/
public final class WeatherQuadMesh {
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
public static final int VERTEX_COUNT = 4;
private static final float[] CORNERS = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
private static int vao;
private static int vbo;
private WeatherQuadMesh() {}
public static int vao() {
if (vao != 0) return vao;
final ByteBuffer data = ByteBuffer.allocateDirect(CORNERS.length * Float.BYTES).order(ByteOrder.nativeOrder());
for (float corner : CORNERS) data.putFloat(corner);
data.flip();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
final int corner = VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation();
GLStateManager.glEnableVertexAttribArray(corner);
GLStateManager.glVertexAttribPointer(corner, 2, GL11.GL_FLOAT, false, 2 * Float.BYTES, 0L);
WeatherInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return vao;
}
public static void bindInstanceBuffer(int bufferId) {
GLStateManager.glBindVertexArray(vao());
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
WeatherInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
}
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
/** Drains deferred geometry before changing state that is not stored with each batch. */
public final class BatchStateGuard {
public static Runnable flush;
private static int suspended;
private BatchStateGuard() {}
public static void suspend() { suspended++; }
public static void resume() { suspended--; }
public static boolean isSuspended() { return suspended != 0; }
public static void beforeChange() {
if (flush == null || suspended != 0 || !GLStateManager.isMainThread()
|| DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.COMPILE) return;
final CommandRecorder recorder = DisplayListManager.isRecording() ? DisplayListManager.pauseRecording() : null;
suspend();
try {
flush.run();
} finally {
resume();
if (recorder != null) DisplayListManager.resumeRecording(recorder);
}
}
}
@@ -2,6 +2,6 @@ package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredAlphaHandler {
boolean isAlphaTestLocked();
void deferAlphaTestToggle(boolean enabled);
void deferAlphaFunc(int function, float reference);
boolean deferAlphaTestToggle(boolean enabled);
boolean deferAlphaFunc(int function, float reference);
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredBlendHandler {
boolean isBlendLocked();
boolean isOverrideHeld();
void deferBlendModeToggle(boolean enabled);
void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha);
void flushDeferredBlend();
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredDepthColorHandler {
boolean isDepthColorLocked();
boolean isOverrideHeld();
void deferDepthEnable(boolean enabled);
void deferColorMask(boolean r, boolean g, boolean b, boolean a);
}
@@ -0,0 +1,61 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class FrameHooks {
private FrameHooks() {}
private static boolean gameLoopStarted;
private static long frameCounter;
private static boolean frameGroupActive;
public static void bootstrapFirstFrame() {
DisplayListManager.abortIfLeaked();
if (!gameLoopStarted) {
gameLoopStarted = true;
RENDER_BACKEND.onFrameBegin();
pushFrameGroup();
}
}
public static void frameEnd() {
popFrameGroup();
RENDER_BACKEND.onFrameEnd();
}
public static void frameBegin() {
if (gameLoopStarted) {
RENDER_BACKEND.onFrameBegin();
pushFrameGroup();
}
}
public static void shutdown() {
BackendManager.shutdown();
}
public static void splashFinished() {
GLStateManager.markSplashComplete("SplashProgress.finish");
}
private static void pushFrameGroup() {
CaptureGate.refresh();
GLDebug.pushGroup("frame:", frameCounter);
frameGroupActive = true;
}
private static void popFrameGroup() {
if (frameGroupActive) {
GLDebug.popGroup();
frameGroupActive = false;
frameCounter++;
}
}
}
@@ -1,14 +1,24 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
public final class GLSMConfig {
// Lightmap (replaces OpenGlHelper.lastBrightnessX/Y)
public static float lastBrightnessX;
public static float lastBrightnessY;
public static int packBrightness(float x, float y) {
return ((int) y << 16) | ((int) x & 0xFFFF);
}
public static int packedLastBrightness() {
final GLContextState glCtx = GLStateManager.ctx();
return packBrightness(glCtx.lastBrightnessX, glCtx.lastBrightnessY);
}
public static boolean hudCacheOverride;
public static boolean extendedAttribsExpected;
public static volatile boolean expandVertexFormats;
public static int workerThreadCount;
private GLSMConfig() {}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.hooks.events.*;
import net.minecraftforge.eventbus.api.bus.EventBus;
@@ -9,10 +10,16 @@ public final class GLSMHooks {
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ShaderWorkSubmitter shaderWorkSubmitter;
public static ImmediateExtendedAttribHandler immediateExtendedHandler;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
public static PendingProgramSelection pendingProgramSelection;
public static void resolvePendingProgram() {
final PendingProgramSelection p = pendingProgramSelection;
if (p != null && !DisplayListManager.isRecording()) p.resolvePendingProgram();
}
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
@@ -25,6 +32,8 @@ public final class GLSMHooks {
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
public static final EventBus<ForeignDrawEndEvent> FOREIGN_DRAW_END = EventBus.create(ForeignDrawEndEvent.class);
public static final EventBus<VanillaBlendChangeEvent> VANILLA_BLEND_CHANGE = EventBus.create(VanillaBlendChangeEvent.class);
public static final EventBus<LoadingCheckpointEvent> LOADING_CHECKPOINT = EventBus.create(LoadingCheckpointEvent.class);
// Reusable event instances
@@ -38,6 +47,8 @@ public final class GLSMHooks {
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
public static final ForeignDrawEndEvent foreignDrawEndEvent = new ForeignDrawEndEvent();
public static final VanillaBlendChangeEvent vanillaBlendChangeEvent = new VanillaBlendChangeEvent();
public static final LoadingCheckpointEvent loadingCheckpointEvent = new LoadingCheckpointEvent();
private GLSMHooks() {}
@@ -1,7 +1,9 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import lombok.Getter;
import java.util.function.Consumer;
@@ -10,11 +12,10 @@ public final class GLSMInitConfig {
@Getter private final boolean lwjglDebug;
@Getter private final StreamingUploader.UploadStrategy streamingUploadStrategy;
@Getter private final Consumer<DirectTessellator> directDrawer;
@Getter private final Runnable streamingDrawerDestroy;
@Getter private final int displayWidth;
@Getter private final int displayHeight;
@Getter private final Runnable postInitCallback;
@Getter private final boolean DSAEnabled;
private final boolean dsaEnabled;
@Getter private final boolean noErrorChecks;
private GLSMInitConfig(Builder builder) {
@@ -22,11 +23,14 @@ public final class GLSMInitConfig {
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.noErrorChecks = builder.noErrorChecks;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
this.DSAEnabled = builder.enableDSA;
this.dsaEnabled = builder.dsaEnabled;
}
public boolean isDSAEnabled() {
return dsaEnabled;
}
public static Builder builder() {
@@ -34,14 +38,13 @@ public final class GLSMInitConfig {
}
public static final class Builder {
private boolean lwjglDebug = false;
private boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private Consumer<DirectTessellator> directDrawer = null;
private Runnable streamingDrawerDestroy = null;
private Consumer<DirectTessellator> directDrawer = TessellatorStreamingDrawer::drawDirect;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
private boolean enableDSA = false;
private boolean dsaEnabled = true;
private boolean noErrorChecks;
private Builder() {}
@@ -66,11 +69,6 @@ public final class GLSMInitConfig {
return this;
}
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
this.streamingDrawerDestroy = streamingDrawerDestroy;
return this;
}
public Builder displaySize(int width, int height) {
this.displayWidth = width;
this.displayHeight = height;
@@ -83,7 +81,7 @@ public final class GLSMInitConfig {
}
public Builder enableDSA(boolean enableDSA) {
this.enableDSA = enableDSA;
this.dsaEnabled = enableDSA;
return this;
}
@@ -0,0 +1,68 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
public interface ImmediateExtendedAttribHandler {
int EXT_STRIDE = 12;
int LOC_MC_ENTITY = 11;
int LOC_MID_TEX = 12;
int LOC_TANGENT = 13;
int LOC_MIDBLOCK = 14;
int RAW_NORMAL_INDEX = 6;
boolean wantsExtended();
boolean wantsTangent();
boolean wantsExtendedCapture();
boolean wantsFaceNormals();
void writeFaceNormals(int[] rawBuffer, int vertexCount);
void build(int[] rawBuffer, int vertexCount, int vertsPerPrim, int normalIntIndex, long dstAddr, int dstStride);
void buildPacked(long srcBase, int stride, int posOffset, int texOffset, int normalOffset,
int vertexCount, int vertsPerPrim, long dstAddr, int dstStride);
static int extPrimVerts(int drawMode) {
if (drawMode == GL11.GL_QUADS) return 4;
if (drawMode == GL11.GL_TRIANGLES) return 3;
return 0;
}
static int extPrimVerts(int drawMode, int vertexCount) {
final int p = extPrimVerts(drawMode);
return (p != 0 && vertexCount > 0 && vertexCount % p == 0) ? p : 0;
}
static int texOffset(VertexFormat format) {
int offset = 0;
for (VertexFormatElement element : format.elementsArray) {
if (element.getUsage() == VertexFormatElement.Usage.PRIMARY_UV) return offset;
offset += element.getByteSize();
}
return -1;
}
static int normalOffset(VertexFormat format) {
int offset = 0;
for (VertexFormatElement element : format.elementsArray) {
if (element.getUsage() == VertexFormatElement.Usage.NORMAL) return offset;
offset += element.getByteSize();
}
return -1;
}
static void setupExtAttribPointers(long baseOffset, int stride) {
GLStateManager.glEnableVertexAttribArray(LOC_MID_TEX);
GLStateManager.glVertexAttribPointer(LOC_MID_TEX, 2, GL11.GL_FLOAT, false, stride, baseOffset);
GLStateManager.glEnableVertexAttribArray(LOC_TANGENT);
GLStateManager.glVertexAttribPointer(LOC_TANGENT, 4, GL11.GL_BYTE, true, stride, baseOffset + 8);
}
}
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface PendingProgramSelection {
void resolvePendingProgram();
}
@@ -1,8 +1,13 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
import org.taumc.glsl.grammar.GLSLParser;
@FunctionalInterface
public interface ShaderTransformPostProcessor {
void onTransformed(String transformedSource, ShaderType shaderType);
default void onTransformed(String transformedSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, ShaderType shaderType) {
onTransformed(transformedSource, shaderType);
}
}
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public interface ShaderWorkSubmitter {
<T> CompletableFuture<T> submit(Supplier<T> work);
}
@@ -0,0 +1,126 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
public final class TextureUtilHooks {
private static int pausedTexture;
private static int pausedMin;
private static int pausedMag;
private static float pausedAniso;
private static int nearestTexture;
private static int nearestMin;
private static int nearestMag;
private static float nearestAniso;
private TextureUtilHooks() {}
public static void setMaxLevel(int mipmapLevels) {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
}
public static void captureBoundFilterTexture() {
final GLContextState ctx = GLStateManager.ctx();
final int id = GLStateManager.getBoundTextureForServerState();
ctx.savedFilterTextureId = id;
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(id);
if (info != null) {
ctx.savedFilterMin = info.getMinFilter();
ctx.savedFilterMag = info.getMagFilter();
ctx.savedFilterAniso = info.getMaxAnisotropy();
} else {
ctx.savedFilterMin = -1;
}
}
public static void clearSavedFilterTexture() {
GLStateManager.ctx().savedFilterTextureId = 0;
}
public static void restoreFilter(int min, int mag) {
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
} else {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
}
}
public static void restoreAniso(float aniso) {
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
} else {
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
}
}
public static boolean pauseTemporaryFilter() {
final GLContextState ctx = GLStateManager.ctx();
final int id = ctx.savedFilterTextureId;
if (id == 0 || pausedTexture != 0 || ctx.savedFilterMin < 0
|| DisplayListManager.getRecordMode() != DisplayListManager.RecordMode.NONE) return false;
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(id);
if (info == null) return false;
pausedTexture = id;
pausedMin = info.getMinFilter();
pausedMag = info.getMagFilter();
pausedAniso = info.getMaxAnisotropy();
setFilter(id, ctx.savedFilterMin, ctx.savedFilterMag, ctx.savedFilterAniso);
return true;
}
public static void resumeTemporaryFilter() {
if (pausedTexture == 0) return;
final int id = pausedTexture;
pausedTexture = 0;
setFilter(id, pausedMin, pausedMag, pausedAniso);
}
/** TextureUtil.func_152777_a(false, false, 1) without using vanilla's single save slot, which the caller may hold. */
public static void forceNearestFilter() {
final int id = GLStateManager.getBoundTextureForServerState();
final TextureInfo info = id != 0 ? TextureInfoCache.INSTANCE.getInfo(id) : null;
if (info == null) {
nearestTexture = 0;
return;
}
nearestTexture = id;
nearestMin = info.getMinFilter();
nearestMag = info.getMagFilter();
nearestAniso = info.getMaxAnisotropy();
setFilter(id, GL11.GL_NEAREST, GL11.GL_NEAREST, 1.0f);
}
public static void restoreFilterAfterNearest() {
if (nearestTexture == 0) return;
final int id = nearestTexture;
nearestTexture = 0;
setFilter(id, nearestMin, nearestMag, nearestAniso);
}
private static void setFilter(int id, int min, int mag, float aniso) {
final boolean anisotropic = BackendManager.RENDER_BACKEND.isAnisotropicSupported();
if (DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
if (anisotropic) RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
} else {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
if (anisotropic) GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
}
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface VanillaBooleanLayer {
boolean isOverrideHeld();
boolean getVanilla();
void setVanilla(boolean enabled);
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface VanillaStateLayer<T> {
boolean isOverrideHeld();
void readVanilla(T into);
void writeVanilla(T from);
static boolean isHeld(VanillaStateLayer<?> layer) {
return layer != null && layer.isOverrideHeld();
}
static <T> void capture(VanillaStateLayer<T> layer, T slot) {
if (isHeld(layer)) layer.readVanilla(slot);
}
static <T> boolean restore(VanillaStateLayer<T> layer, T saved) {
if (!isHeld(layer)) return false;
layer.writeVanilla(saved);
return true;
}
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ForeignDrawEndEvent extends MutableEvent {
}
@@ -4,5 +4,6 @@ import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureUnitStateEvent extends MutableEvent {
public int unit;
public int cap;
public boolean enabled;
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class VanillaBlendChangeEvent extends MutableEvent {
}
@@ -0,0 +1,27 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizon.gtnhlib.asm.ASMUtil;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
public final class ClassDump {
public static final ClassDump DISABLED = new ClassDump(false);
private final boolean enabled;
public ClassDump(boolean enabled) {
this.enabled = enabled;
}
public void dumpClass(String className, byte[] originalBytes, byte[] transformedBytes, Object transformer) {
if (!enabled) return;
ASMUtil.saveAsRawClassFile(originalBytes, className + "_PRE", transformer);
ASMUtil.saveAsRawClassFile(transformedBytes, className + "_POST", transformer);
}
public void dumpRFBClass(String className, ClassNodeHandle classNode, Object transformer) {
if (!enabled) return;
classNode.markDirty();
dumpClass(className, classNode.getOriginalBytes(), classNode.computeBytes(), transformer);
}
}
@@ -1,5 +1,10 @@
package com.gtnewhorizons.angelica.glsm.loading;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.Logger;
import javax.swing.JOptionPane;
import java.awt.GraphicsEnvironment;
import java.util.List;
/**
@@ -15,17 +20,64 @@ public final class DependencyVerifier {
*/
public record Check(String resourcePath, String errorMessage) {}
public interface FatalDialog {
void show(String title, String message);
}
/**
* Verify all checks pass. Throws RuntimeException on first failure.
* @param anchor Class whose classloader to use for resource lookups
* @param checks Dependency checks to run
*/
public static void verify(Class<?> anchor, List<Check> checks) {
final Check missing = firstMissing(anchor, checks);
if (missing != null) {
throw new RuntimeException(missing.errorMessage());
}
}
/** @return the first check whose resource is absent, or null when all pass. */
public static Check firstMissing(Class<?> anchor, List<Check> checks) {
for (Check check : checks) {
if (anchor.getResource(check.resourcePath()) == null) {
throw new RuntimeException(check.errorMessage());
return check;
}
}
return null;
}
public static List<Check> gtnhLibChecks(String productName) {
return List.of(
new Check("/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class", "Missing dependency: " + productName + " requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
new Check("/com/gtnewhorizon/gtnhlib/client/renderer/VertexCallbackManager.class", "GTNHLib is outdated: " + productName + " requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib"),
new Check("/it/unimi/dsi/fastutil/objects/ObjectBooleanBiConsumer.class", "GTNHLib is outdated: " + productName + " requires GTNHLib 0.11.19 or newer (fastutil 8.5.18+)! Download: https://modrinth.com/mod/gtnhlib"));
}
public static void verifyOrHalt(Class<?> anchor, String productName, List<Check> checks, Logger logger, FatalDialog sdlDialog) {
final String message;
try {
verify(anchor, checks);
return;
} catch (RuntimeException ex) {
message = ex.getMessage();
}
final String title = productName + " - Missing Dependency";
System.err.println("FATAL: " + message);
try {
logger.fatal(message);
} catch (Throwable ignored) {}
final int lwjgl3ifyMajor = ((Integer) Launch.blackboard.getOrDefault("lwjgl3ify:major-version", Integer.MIN_VALUE));
try {
if (lwjgl3ifyMajor >= 3) {
sdlDialog.show(title, message);
} else if (!GraphicsEnvironment.isHeadless()) {
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
}
} catch (Throwable ignored) {}
Runtime.getRuntime().halt(1);
}
private DependencyVerifier() {}
@@ -1,54 +1,8 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class EarlyRedirectorCore {
private final GLSMRedirector core;
private final String[] targets;
private final String[] exclusions;
public final class EarlyRedirectorCore extends ModRedirector {
public EarlyRedirectorCore(String... extraExclusions) {
this.core = new GLSMRedirector();
this.targets = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone();
final List<String> excl = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
if (extraExclusions != null && extraExclusions.length > 0) {
Collections.addAll(excl, extraExclusions);
}
this.exclusions = excl.toArray(new String[0]);
}
public byte[] transform(String transformedName, byte[] basicClass) {
if (basicClass == null) return null;
if (!startsWithAny(transformedName, targets)) return basicClass;
if (startsWithAny(transformedName, exclusions)) return basicClass;
if (!core.shouldTransform(basicClass)) return basicClass;
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (core.transformClassNode(transformedName, cn)) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
return basicClass;
}
private static boolean startsWithAny(String name, String[] prefixes) {
for (String p : prefixes) {
if (name.startsWith(p)) return true;
}
return false;
super(ClassDump.DISABLED, EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone(), extraExclusions);
}
}
@@ -21,11 +21,11 @@ public final class EcosystemNarrowRules {
"Reika.DragonAPI.ASM.StructureLootHooks"
)),
new NarrowRule("Xaeros_common", "xaero.common.core", List.of(
new NarrowRule("Xaeros", "xaero.common.core", List.of(
"xaero.common.core.transformer"
)),
new NarrowRule("Xaeros_map", "xaero.map.core", List.of(
new NarrowRule("Xaeros", "xaero.map.core", List.of(
"xaero.map.core.transformer"
)),
@@ -78,15 +78,21 @@ public final class EcosystemNarrowRules {
"alfheim.common.core.asm.hook.fixes.CorporeaInputFix",
"alfheim.common.core.asm.hook.fixes.GodAttributesHooks",
"alfheim.common.core.asm.hook.fixes.RecipeAncientWillsFix"
)),
new NarrowRule("LegendsMod", "com.tihyo.legends.management.asm", List.of(
"com.tihyo.legends.management.asm.LegendsLoadingPlugin",
"com.tihyo.legends.management.asm.AbstractTransformer",
"com.tihyo.legends.management.asm.ASMHelper",
"com.tihyo.legends.management.asm.Transformer"
))
);
public static final String[] LWJGL3IFY_EXCLUSIONS_SHARED = {
"org.embeddedt.embeddium",
"org.taumc.celeritas",
"com.mitchej123.lwjgl.lwjgl3",
"com.mitchej123.glsm",
"com.gtnewhorizons.angelica.lwjgl3",
"com.gtnewhorizons.angelica.sdlgpu",
};
public static final String[] EARLY_REDIRECTOR_TARGETS = {
@@ -0,0 +1,19 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
public final class Lwjgl3ifyExclusions {
public static void apply() {
final var handle = SharedConfig.getRfbTransformers().stream()
.filter(transformer -> transformer.id().equals("lwjgl3ify:redirect"))
.findFirst()
.orElse(null);
if (handle == null) return;
for (String exclusion : EcosystemNarrowRules.LWJGL3IFY_EXCLUSIONS_SHARED) {
handle.exclusions().add(exclusion);
}
}
private Lwjgl3ifyExclusions() {}
}
@@ -0,0 +1,82 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ModRedirector {
private final GLSMRedirector core = new GLSMRedirector();
private final ClassDump dump;
private final String[] targets;
private final String[] allExclusions;
public ModRedirector(ClassDump dump, String... extraExclusions) {
this(dump, (String[]) null, extraExclusions);
}
public ModRedirector(ClassDump dump, String[] targets, String[] extraExclusions) {
this.dump = dump;
this.targets = targets;
final List<String> exclusions = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
exclusions.add("com.gtnewhorizons.angelica.lwjgl3.");
Collections.addAll(exclusions, extraExclusions);
this.allExclusions = exclusions.toArray(new String[0]);
}
public ClassDump getDump() {
return dump;
}
public String[] getTransformerExclusions() {
return allExclusions;
}
public String[] getConstantPoolSearchStrings() {
return core.getConstantPoolSearchStrings();
}
public boolean shouldTransform(byte[] basicClass) {
return core.shouldTransform(basicClass);
}
public boolean transformClassNode(String transformedName, ClassNode cn, boolean lwjgl3Aware) {
return core.transformClassNode(transformedName, cn, lwjgl3Aware);
}
public byte[] transform(String transformedName, byte[] basicClass, Object transformer) {
if (basicClass == null) return null;
if (targets != null && !startsWithAny(transformedName, targets)) return basicClass;
if (startsWithAny(transformedName, allExclusions)) return basicClass;
if (!core.shouldTransform(basicClass)) {
return basicClass;
}
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (!core.transformClassNode(transformedName, cn)) {
return basicClass;
}
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
final byte[] bytes = cw.toByteArray();
dump.dumpClass(transformedName, basicClass, bytes, transformer);
return bytes;
}
private static boolean startsWithAny(String name, String[] prefixes) {
for (String prefix : prefixes) {
if (name.startsWith(prefix)) return true;
}
return false;
}
}
@@ -0,0 +1,84 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.retrofuturabootstrap.api.BytePatternMatcher;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassHeaderMetadata;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
public abstract class RfbModRedirector implements RfbClassTransformer {
private static final Attributes.Name LWJGL3_AWARE = new Attributes.Name("Lwjgl3ify-Aware");
private final String id;
private final ModRedirector inner;
private final BytePatternMatcher cstPoolMatcher;
protected RfbModRedirector(@Pattern("[a-z0-9-]+") String id, ModRedirector inner) {
this.id = id;
this.inner = inner;
this.cstPoolMatcher = new BytePatternMatcher(inner.getConstantPoolSearchStrings(), BytePatternMatcher.Mode.StartsWith);
}
@Override
public @NotNull String id() {
return id;
}
private static boolean isLwjgl3Aware(@Nullable Manifest manifest) {
return manifest != null && "true".equals(manifest.getMainAttributes().getValue(LWJGL3_AWARE));
}
@Override
public @NotNull String @Nullable [] sortAfter() {
return new String[] {"*", "mixin:mixin"};
}
@Override
public @NotNull String @Nullable [] sortBefore() {
return new String[] {"lwjgl3ify:redirect"};
}
@Override
public @NotNull String @Nullable [] additionalExclusions() {
return inner.getTransformerExclusions();
}
@Override
public boolean shouldTransformClass(@NotNull ExtensibleClassLoader classLoader,
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
@NotNull ClassNodeHandle classNode) {
if (!classNode.isPresent()) {
return false;
}
final ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
if (metadata == null) {
return inner.shouldTransform(classNode.getOriginalBytes());
}
return metadata.matchesBytes(classNode.getOriginalBytes(), cstPoolMatcher);
}
@Override
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader,
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
@NotNull ClassNodeHandle classNode) {
final boolean changed = inner.transformClassNode(className, classNode.getNode(), isLwjgl3Aware(manifest));
if (changed) {
classNode.computeMaxs();
inner.getDump().dumpRFBClass(className, classNode, this);
}
return changed;
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
transformClassIfNeeded(classLoader, context, manifest, className, classNode);
}
}
@@ -25,7 +25,7 @@ public final class TransformerNarrower {
/**
* A rule that replaces a broad transformer exclusion with narrower ones.
* @param name Human-readable name for logging (e.g. "DragonAPI")
* @param name Blackboard/config key suffix. (e.g. "DragonAPI").
* @param broad The broad exclusion to remove (e.g. "Reika.DragonAPI.ASM")
* @param narrow Narrower exclusions to add in its place
*/
@@ -0,0 +1,92 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import java.util.ArrayList;
import java.util.List;
public final class SectionRegistry {
public interface Emitter {
long enter(int category, String text);
void leave(long nativeId);
void setup(int category, String name);
boolean isConnected();
}
private static final class Category {
final int id;
final String name;
Category(int id, String name) {
this.id = id;
this.name = name;
}
}
private static final class Section {
final long token;
final int category;
final String text;
long nativeId;
Section(long token, int category, String text, long nativeId) {
this.token = token;
this.category = category;
this.text = text;
this.nativeId = nativeId;
}
}
private final Emitter emitter;
private final List<Category> categories = new ArrayList<>();
private final List<Section> open = new ArrayList<>();
private long nextToken;
private boolean connected;
public SectionRegistry(Emitter emitter) {
this.emitter = emitter;
}
public synchronized void registerCategory(int category, String name) {
categories.add(new Category(category, name));
}
public synchronized long enter(int category, String text) {
syncConnection();
final long token = ++nextToken;
open.add(new Section(token, category, text, emitter.enter(category, text)));
return token;
}
public synchronized void leave(long token) {
for (int i = 0; i < open.size(); i++) {
final Section section = open.get(i);
if (section.token == token) {
open.remove(i);
if (section.nativeId != 0L) emitter.leave(section.nativeId);
return;
}
}
}
public synchronized void poll() {
syncConnection();
}
private void syncConnection() {
final boolean now = emitter.isConnected();
if (now == connected) return;
connected = now;
if (now) replay();
}
private void replay() {
for (int i = 0; i < categories.size(); i++) {
final Category category = categories.get(i);
emitter.setup(category.id, category.name);
}
for (int i = 0; i < open.size(); i++) {
final Section section = open.get(i);
section.nativeId = emitter.enter(section.category, section.text);
}
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.sun.management.ThreadMXBean;
@@ -44,6 +43,18 @@ public final class Tracy {
public static final int COLOR_IRIS = 0x9E862E; // gold
public static final int COLOR_FFP = 0x7A4E9E; // purple
public static final int SECTION_WORLD = 1;
public static final int SECTION_SHADERS = 2;
public static final int SECTION_BENCHMARK = 3;
public static final int SECTION_UI = 4;
private static final SectionRegistry SECTIONS = new SectionRegistry(new SectionRegistry.Emitter() {
@Override public long enter(int category, String text) { return BACKEND.sectionEnter(category, text); }
@Override public void leave(long nativeId) { BACKEND.sectionLeave(nativeId); }
@Override public void setup(int category, String name) { BACKEND.sectionSetup(category, name); }
@Override public boolean isConnected() { return BACKEND.isConnected(); }
});
private static final ThreadLocal<ZoneStack> STACK = new ThreadLocal<>() {
@Override
protected ZoneStack initialValue() {
@@ -54,14 +65,18 @@ public final class Tracy {
static {
TracyBackend backend = null;
if (SystemProperties.TRACY) {
if (TracyOptions.enabled()) {
backend = loadAndInit();
}
BACKEND = backend;
ENABLED = backend != null;
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
FINE_ZONES = ENABLED && TracyOptions.fineZones();
if (ENABLED) {
Runtime.getRuntime().addShutdownHook(new Thread(Tracy::shutdown, "Tracy-Shutdown"));
SECTIONS.registerCategory(SECTION_WORLD, "World");
SECTIONS.registerCategory(SECTION_SHADERS, "Shaders");
SECTIONS.registerCategory(SECTION_BENCHMARK, "Benchmark");
SECTIONS.registerCategory(SECTION_UI, "UI");
LOGGER.info("Tracy profiling enabled");
}
}
@@ -70,6 +85,18 @@ public final class Tracy {
private static void shutdown() {
SHUTTING_DOWN = true;
if (isCaptureActive(BACKEND.captureState())) {
BACKEND.captureStop();
final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (isCaptureActive(BACKEND.captureState()) && System.nanoTime() < deadline) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
final Thread worker = new Thread(BACKEND::shutdown, "Tracy-Shutdown-Worker");
worker.setDaemon(true);
worker.start();
@@ -80,6 +107,10 @@ public final class Tracy {
}
}
private static boolean isCaptureActive(int state) {
return state == TracyBackend.CAPTURE_CONNECTING || state == TracyBackend.CAPTURE_RECORDING || state == TracyBackend.CAPTURE_SAVING;
}
private static TracyBackend loadAndInit() {
TracyBackend found = null;
try {
@@ -88,13 +119,14 @@ public final class Tracy {
found = it.next();
}
} catch (ServiceConfigurationError | LinkageError e) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but backend unavailable: {}", e.getMessage());
LOGGER.warn("Tracy: backend unavailable: {}", e.getMessage());
return null;
}
if (found == null) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but no TracyBackend service present");
LOGGER.warn("Tracy: enabled but the angelica-tracy jar is not installed");
return null;
}
if (!implementsCurrentAbi(found)) return null;
final TracyBackend backend = found;
final CountDownLatch done = new CountDownLatch(1);
final AtomicReference<Boolean> result = new AtomicReference<>();
@@ -103,7 +135,7 @@ public final class Tracy {
init.start();
try {
if (!done.await(15, TimeUnit.SECONDS) && result.compareAndSet(null, Boolean.FALSE)) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but init timed out; profiling disabled");
LOGGER.warn("Tracy: init timed out; profiling disabled");
return null;
}
} catch (InterruptedException e) {
@@ -114,16 +146,34 @@ public final class Tracy {
return null;
}
if (!Boolean.TRUE.equals(result.get())) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but native init failed; profiling disabled");
LOGGER.warn("Tracy: native init failed; profiling disabled");
return null;
}
return backend;
}
private static final String ABI_SENTINEL = "sectionEnter";
private static boolean implementsCurrentAbi(TracyBackend backend) {
try {
backend.getClass().getMethod(ABI_SENTINEL, int.class, String.class);
return true;
} catch (NoSuchMethodException e) {
LOGGER.warn("Tracy backend {} was built against an older TracyBackend interface; profiling disabled", backend.getClass().getName());
return false;
}
}
private record InitRunner(TracyBackend backend, CountDownLatch done, AtomicReference<Boolean> result) implements Runnable {
@Override
public void run() {
final boolean ok = backend.init();
boolean ok;
try {
ok = backend.init();
} catch (LinkageError e) {
LOGGER.warn("Tracy backend {} failed to link; profiling disabled: {}", backend.getClass().getName(), e);
ok = false;
}
if (!result.compareAndSet(null, ok) && ok) {
backend.shutdown();
}
@@ -168,7 +218,7 @@ public final class Tracy {
final String name = stack.poppedName();
if (name != null && WARNED_LEAKS.add(name)) {
LOGGER.warn("Tracy: unbalanced profiler section '{}' left open (leaked by its caller); closed at next root", name);
BACKEND.message("unbalanced profiler section '" + name + "' closed at root");
BACKEND.message("unbalanced profiler section '" + name + "' closed at root", TracyBackend.SEVERITY_WARNING);
}
}
}
@@ -274,6 +324,7 @@ public final class Tracy {
threadNameRefreshed = true;
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
}
SECTIONS.poll();
BACKEND.frameMark();
}
@@ -330,14 +381,53 @@ public final class Tracy {
}
public static void message(String text) {
message(text, TracyBackend.SEVERITY_INFO);
}
public static void message(String text, int severity) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.message(text);
BACKEND.message(text, severity);
}
public static long sectionEnter(int category, String text) {
if (!ENABLED || SHUTTING_DOWN) return 0L;
return SECTIONS.enter(category, text);
}
public static void sectionLeave(long token) {
if (token == 0L || !ENABLED || SHUTTING_DOWN) return;
SECTIONS.leave(token);
}
public static boolean isConnected() {
return ENABLED && BACKEND.isConnected();
}
public static String captureStart(String path, int seconds) {
if (!ENABLED) return "Tracy not enabled";
return BACKEND.captureStart(path, seconds);
}
public static void captureStop() {
if (!ENABLED) return;
BACKEND.captureStop();
}
public static int captureState() {
if (!ENABLED) return 0;
return BACKEND.captureState();
}
public static long captureElapsedMs() {
if (!ENABLED) return 0;
return BACKEND.captureElapsedMs();
}
public static String captureError() {
if (!ENABLED) return "";
return BACKEND.captureError();
}
private static boolean gpuInitAttempted;
private static boolean gpuReady;
@Setter private static boolean gpuZonesEnabled = true;
@@ -5,6 +5,20 @@ public interface TracyBackend {
int PLOT_FORMAT_MEMORY = 1;
int PLOT_FORMAT_PERCENTAGE = 2;
int SEVERITY_TRACE = 0;
int SEVERITY_DEBUG = 1;
int SEVERITY_INFO = 2;
int SEVERITY_WARNING = 3;
int SEVERITY_ERROR = 4;
int SEVERITY_FATAL = 5;
int CAPTURE_IDLE = 0;
int CAPTURE_CONNECTING = 1;
int CAPTURE_RECORDING = 2;
int CAPTURE_SAVING = 3;
int CAPTURE_DONE = 4;
int CAPTURE_FAILED = 5;
boolean init();
long internSrcLoc(String name, int color);
@@ -24,7 +38,14 @@ public interface TracyBackend {
void plotInt(long namePtr, long value);
void plot(long namePtr, double value);
void message(String text);
void message(String text, int severity);
default void message(String text) { message(text, SEVERITY_INFO); }
long sectionEnter(int category, String text);
void sectionLeave(long id);
void sectionSetup(int category, String name);
void setCurrentThreadName(String name);
boolean isConnected();
void shutdown();
@@ -33,4 +54,12 @@ public interface TracyBackend {
boolean gpuBeginZone(long srcLoc);
void gpuEndZone();
void gpuCollect();
default String captureStart(String path, int seconds) {
return "Tracy capture unsupported by this angelica-tracy build";
}
default void captureStop() {}
default int captureState() { return CAPTURE_IDLE; }
default long captureElapsedMs() { return 0; }
default String captureError() { return ""; }
}
@@ -0,0 +1,64 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
public final class TracyOptions {
private static boolean configEnabled;
private static boolean configRemote;
private static boolean configFineZones;
private static int configMaxSrcLocs = 4096;
private TracyOptions() {}
public static void latch(boolean enabled, boolean allowRemote, boolean fineZones, int maxSrcLocs) {
configEnabled = enabled;
configRemote = allowRemote;
configFineZones = fineZones;
configMaxSrcLocs = maxSrcLocs;
}
public static boolean enabled() {
return resolveBoolean(SystemProperties.TRACY_OVERRIDE, configEnabled);
}
public static boolean allowRemote() {
return configRemote;
}
public static boolean fineZones() {
return resolveBoolean(SystemProperties.TRACY_FINE_ZONES_OVERRIDE, configFineZones);
}
public static int maxSrcLocs() {
return resolveMaxSrcLocs(SystemProperties.TRACY_MAX_SRC_LOCS_OVERRIDE, configMaxSrcLocs);
}
public static boolean configEnabled() {
return configEnabled;
}
public static boolean configRemote() {
return configRemote;
}
public static boolean configFineZones() {
return configFineZones;
}
public static int configMaxSrcLocs() {
return configMaxSrcLocs;
}
public static boolean backendPresent() {
return TracyOptions.class.getResource("/com/gtnewhorizons/angelica/tracy/TracyClientBackend.class") != null;
}
static boolean resolveBoolean(Boolean override, boolean config) {
return override != null ? override : config;
}
static int resolveMaxSrcLocs(Integer override, int config) {
return Math.max(16, override != null ? override : config);
}
}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.profiling;
public final class ZoneStack {
public static final long EMPTY = -1L;
static final int MAX_DEPTH = 512;
public static final int MAX_DEPTH = 512;
private long[] stack = new long[64];
private boolean[] gpu = new boolean[64];
@@ -68,6 +68,7 @@ public final class AttribSnapshot {
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
final VAOManager vaos = GLStateManager.ctx().vaos;
boolean success = false;
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
@@ -78,7 +79,7 @@ public final class AttribSnapshot {
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = vaos.attrib(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * GLTypes.sizeBytes(a.type);
@@ -114,7 +115,7 @@ public final class AttribSnapshot {
try {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = vaos.attrib(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
@@ -97,7 +97,8 @@ public final class CommandBufferExecutor {
ptr += 4;
}
case GLCommand.USE_PROGRAM -> {
GLStateManager.glUseProgram(memGetInt(ptr));
final int recordedProgram = memGetInt(ptr);
GLStateManager.glUseProgram(compiledDisplayList.resolveProgram(recordedProgram));
ptr += 4;
}
case GLCommand.PUSH_ATTRIB -> {
@@ -252,6 +253,11 @@ public final class CommandBufferExecutor {
}
// === Float commands ===
case GLCommand.VERTEX_ATTRIB -> {
GLStateManager.glVertexAttrib4f(memGetInt(ptr), memGetFloat(ptr + 4), memGetFloat(ptr + 8),
memGetFloat(ptr + 12), memGetFloat(ptr + 16));
ptr += 20;
}
case GLCommand.POINT_SIZE -> {
GLStateManager.glPointSize(memGetFloat(ptr));
ptr += 4;
@@ -274,6 +280,13 @@ public final class CommandBufferExecutor {
ptr += 16;
GLStateManager.glColor4f(r, g, b, a);
}
case GLCommand.SECONDARY_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glSecondaryColor3f(r, g, b);
}
case GLCommand.CLEAR_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
@@ -10,6 +10,8 @@ import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
@@ -21,6 +23,13 @@ public final class CommandRecorder {
private long writePointer;
private final ArrayList<DisplayListCommand> complexObjects = new ArrayList<>();
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
private Set<DisplayListCommand> onceCommands;
private DisplayListCommand lastComplexCommand;
private int lastComplexCommandEnd = -1;
public boolean isLastCommand(DisplayListCommand command) {
return size() == lastComplexCommandEnd && command.equals(lastComplexCommand);
}
public CommandRecorder() {
this(DEFAULT_CAPACITY);
@@ -32,6 +41,11 @@ public final class CommandRecorder {
this.writePointer = basePointer;
}
public boolean markRecordedOnce(DisplayListCommand command) {
if (onceCommands == null) onceCommands = new HashSet<>();
return onceCommands.add(command);
}
// === Low-level write operations ===
private void writeInt(int value) {
@@ -356,6 +370,24 @@ public final class CommandRecorder {
writeFloat(a);
}
public void writeSecondaryColor(float r, float g, float b) {
ensureCapacity(16);
writeInt(GLCommand.SECONDARY_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
}
public void writeVertexAttrib(int index, float x, float y, float z, float w) {
ensureCapacity(24);
writeInt(GLCommand.VERTEX_ATTRIB);
writeInt(index);
writeFloat(x);
writeFloat(y);
writeFloat(z);
writeFloat(w);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
@@ -641,6 +673,8 @@ public final class CommandRecorder {
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
lastComplexCommand = cmd;
lastComplexCommandEnd = size();
}
// === Lifecycle ===
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -35,6 +36,24 @@ public final class CompiledDisplayList {
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
private Int2IntMap replayPrograms;
private long programLifetimeGeneration = -1;
int resolveProgram(int program) {
if (program == 0) return 0;
final long generation = GLStateManager.getProgramLifetimeGeneration();
if (replayPrograms == null) replayPrograms = new Int2IntOpenHashMap();
if (programLifetimeGeneration != generation) {
replayPrograms.clear();
programLifetimeGeneration = generation;
}
if (replayPrograms.containsKey(program)) return replayPrograms.get(program);
final boolean valid = GLStateManager.glIsProgram(program);
if (!valid || !GLStateManager.isProgramPendingDeletion(program)) {
replayPrograms.put(program, valid ? program : 0);
}
return valid ? program : 0;
}
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.lwjgl.opengl.GL15;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
/**
@@ -9,15 +13,26 @@ import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
public final class DisplayListVBO {
private final SubVBO[] vbos;
private final int[] extVbos;
DisplayListVBO(SubVBO[] vbos) {
this(vbos, null);
}
DisplayListVBO(SubVBO[] vbos, int[] extVbos) {
this.vbos = vbos;
this.extVbos = extVbos;
}
public void delete() {
for (SubVBO vbo : vbos) {
vbo.delete();
}
if (extVbos != null) {
for (int extVbo : extVbos) {
if (extVbo != 0) GLStateManager.glDeleteBuffers(extVbo);
}
}
}
public SubVBO[] getVBOs() {
@@ -39,6 +54,9 @@ public final class DisplayListVBO {
private final int start;
private final int count;
private final int vertexFlags;
private int pendingExtVbo = 0;
void setPendingExtVbo(int extVbo) { this.pendingExtVbo = extVbo; }
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count, int vertexFlags) {
this.vao = vao;
@@ -67,9 +85,20 @@ public final class DisplayListVBO {
public void render() {
if (vao == null) return;
vao.bind();
if (pendingExtVbo != 0) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, pendingExtVbo);
ImmediateExtendedAttribHandler.setupExtAttribPointers(0L, ImmediateExtendedAttribHandler.EXT_STRIDE);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
pendingExtVbo = 0;
}
if (count <= 0) { vao.unbind(); return; }
VAOManager.setCurrentVertexFlags(vertexFlags);
vao.draw(drawMode, start, count);
FfpExtendedAttribs.beginInternalDraw();
try {
vao.draw(drawMode, start, count);
} finally {
FfpExtendedAttribs.endInternalDraw();
}
vao.unbind();
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.BaseVAO;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexedVAO;
@@ -8,7 +9,13 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -16,6 +23,7 @@ import java.util.List;
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.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.nmemFree;
@@ -48,15 +56,29 @@ public final class DisplayListVBOBuilder {
}
public DisplayListVBO build() {
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
final DisplayListVBO.SubVBO[] vbos = new DisplayListVBO.SubVBO[count];
final ArrayList<ByteBuffer> allBuffers = new ArrayList<>();
final IntArrayList extVbos = new IntArrayList();
for (int i = 0; i < VertexFlags.BITSET_SIZE; i++) {
List<FormatData> formatData = formats[i];
if (formatData == null) continue;
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[i];
final boolean wantExt = extHandler != null && format.hasTexture();
if (extHandler == null && GLSMConfig.extendedAttribsExpected && format.hasTexture()) {
GLStateManager.warnOnce("dl-ext-no-handler",
"Textured display list compiled before the immediate ext-attribute handler was installed! POM data will be missing for this geometry.");
}
int start = 0;
final IVertexArrayObject vao = VAOManager.createStorageVAO(format, -1, 0); // drawMode will be ignored
final IVertexBuffer vbo = vao.getVBO();
// SubVBO slots that need the ext attributes attached on their first draw.
final IntArrayList extDrawIndexes = wantExt ? new IntArrayList() : null;
final IntArrayList extStarts = wantExt ? new IntArrayList() : null;
final IntArrayList extCounts = wantExt ? new IntArrayList() : null;
final IntArrayList extPrims = wantExt ? new IntArrayList() : null;
for (FormatData data : formatData) {
int vertexCount;
final List<ByteBuffer> drawBuffers = data.buffers;
@@ -76,6 +98,23 @@ public final class DisplayListVBOBuilder {
if (data.drawMode == GL11.GL_QUADS) {
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6, flags);
if (wantExt) {
extDrawIndexes.add(data.drawIndex);
extStarts.add(start);
extCounts.add(vertexCount);
extPrims.add(4);
}
} else if (data.drawMode == GL11.GL_TRIANGLES) {
if (wantExt) {
final IVertexArrayObject triVao = new BaseVAO(vbo);
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(triVao, GL11.GL_TRIANGLES, start, vertexCount, flags);
extDrawIndexes.add(data.drawIndex);
extStarts.add(start);
extCounts.add(vertexCount);
extPrims.add(3);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLES, start, vertexCount, flags);
}
} else if (data.drawMode == GL11.GL_QUAD_STRIP) {
// GL_QUAD_STRIP is removed in core profile; an even-length GL_TRIANGLE_STRIP produces the same quads
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_STRIP, start, vertexCount & ~1, flags);
@@ -91,11 +130,55 @@ public final class DisplayListVBOBuilder {
}
ByteBuffer bigBuffer = mergeAndDelete(allBuffers);
vbo.allocate(bigBuffer, start);
if (wantExt && !extDrawIndexes.isEmpty()) {
final int extVbo = buildExt(extHandler, format, bigBuffer, extStarts, extCounts, extPrims);
if (extVbo != 0) {
extVbos.add(extVbo);
for (int e = 0; e < extDrawIndexes.size(); e++) {
vbos[extDrawIndexes.getInt(e)].setPendingExtVbo(extVbo);
}
}
}
memFree(bigBuffer);
allBuffers.clear();
}
return new DisplayListVBO(vbos);
return new DisplayListVBO(vbos, extVbos.toIntArray());
}
private static int buildExt(ImmediateExtendedAttribHandler handler, VertexFormat format, ByteBuffer bigBuffer,
IntArrayList extStarts, IntArrayList extCounts, IntArrayList extPrims) {
final int extStride = ImmediateExtendedAttribHandler.EXT_STRIDE;
final int stride = format.getVertexSize();
final int posOffset = 0;
final int texOffset = ImmediateExtendedAttribHandler.texOffset(format);
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
int extVerts = 0;
for (int q = 0; q < extStarts.size(); q++) {
extVerts = Math.max(extVerts, extStarts.getInt(q) + extCounts.getInt(q));
}
final ByteBuffer ext = memCalloc(extVerts, extStride);
final long extAddr = memAddress0(ext);
final long srcBase = memAddress0(bigBuffer);
for (int q = 0; q < extStarts.size(); q++) {
final int rStart = extStarts.getInt(q);
final int rCount = extCounts.getInt(q);
final int rPrim = extPrims.getInt(q);
handler.buildPacked(srcBase + (long) rStart * stride, stride, posOffset, texOffset, normalOffset, rCount, rPrim,
extAddr + (long) rStart * extStride, extStride);
}
final int extVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, extVbo);
ext.position(0).limit(extVerts * extStride);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, ext, GL15.GL_STATIC_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
memFree(ext);
return extVbo;
}
private static ByteBuffer mergeAndDelete(List<ByteBuffer> buffers) {
@@ -24,9 +24,9 @@ public final class GLCommand {
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4]
public static final int PUSH_MATRIX = 15; // [cmd:4]
public static final int POP_MATRIX = 16; // [cmd:4]
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
@@ -61,6 +61,8 @@ public final class GLCommand {
public static final int COLOR = 54; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int SECONDARY_COLOR = 57; // [cmd:4][r:4f][g:4f][b:4f]
public static final int VERTEX_ATTRIB = 58; // [cmd:4][index:4][x:4f][y:4f][z:4f][w:4f]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
@@ -72,14 +74,13 @@ public final class GLCommand {
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int TRANSLATE = 70; // [cmd:4][x:4f][y:4f][z:4f] = 16 bytes
public static final int SCALE = 72; // [cmd:4][x:4f][y:4f][z:4f] = 16 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
// === Matrix commands (68 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][m00-m33:64f] = 68 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][m00-m33:64f] = 68 bytes
// === Buffer commands (inline 4 floats max) ===
public static final int FOG = 90; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
@@ -155,6 +156,8 @@ public final class GLCommand {
case COLOR -> "COLOR";
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case SECONDARY_COLOR -> "SECONDARY_COLOR";
case VERTEX_ATTRIB -> "VERTEX_ATTRIB";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
@@ -211,7 +214,7 @@ public final class GLCommand {
// Three int commands (16 bytes)
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
GLCommand.LIGHTF, GLCommand.LIGHTI,
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF, GLCommand.SECONDARY_COLOR -> 16;
// Four int commands (20 bytes)
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
@@ -219,6 +222,8 @@ public final class GLCommand {
GLCommand.SCISSOR,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
case GLCommand.VERTEX_ATTRIB -> 24;
// DRAW_RANGE_RESTORE: 28 bytes
case GLCommand.DRAW_RANGE_RESTORE -> 28;
Loaded 100 of 950 files, more files were not shown because too many files have changed in this diff. Show more