Compare commits

..
41 Commits
Author SHA1 Message Date
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
470 changed files with 24582 additions and 5369 deletions

No files matched your search

+6
View File
@@ -20,3 +20,9 @@ indent_size = 2
[*.lang]
trim_trailing_whitespace = false
[*.java]
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
+5 -3
View File
@@ -2,7 +2,8 @@
# Builds the libTracyClient shared library for the tracy-client module.
set -euo pipefail
TRACY_VERSION="${TRACY_VERSION:-v0.13.1}"
TRACY_VERSION="${TRACY_VERSION:-v0.14.0}"
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
@@ -33,7 +34,8 @@ fi
BUILD_DIR="$WORK_DIR/build"
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
cmake -S "$SRC_DIR" -B "$BUILD_DIR" \
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$BUILD_DIR" \
-DTRACY_SRC_DIR="$SRC_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DTRACY_STATIC=OFF \
+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[@]}"
+13 -24
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
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
+4
View File
@@ -1,5 +1,6 @@
.gradle
.kotlin/
.lwjgl/
.settings
/.idea/
/.vs/
@@ -40,5 +41,8 @@ addon.late.local.gradle
addon.late.local.gradle.kts
local/
tracy-client/src/main/resources/natives/
tracy-client/angelica/
dist/tracy-natives/
*.tracy
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
+2
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
+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")
}
+7 -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"))
@@ -104,7 +104,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 +160,8 @@ 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: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 +176,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")
@@ -202,8 +203,10 @@ afterEvaluate {
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); втім, його можна використовувати для модифікованих блоків, яким потрібні кілька різних значень відтінку в одному блоці.
+25 -1
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"
@@ -114,6 +135,7 @@ dependencies {
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
// @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 +162,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 +245,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 {
@@ -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,6 +35,8 @@ 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");
// Tracy
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
@@ -44,10 +47,12 @@ public final class SystemProperties {
// 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);
// Debug
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
@@ -55,18 +60,24 @@ 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");
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 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 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 {
@@ -115,7 +126,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"));
}
@@ -307,6 +307,11 @@ public class DisplayListManager {
}
}
public static void recordSecondaryColor(float r, float g, float b) {
drawBarrier();
currentRecorder.writeSecondaryColor(r, g, b);
}
public static void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
drawBarrier();
currentRecorder.writeColorMask(r, g, b, a);
@@ -841,7 +846,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()) {
@@ -99,6 +99,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 +123,7 @@ public class Feature {
, GLStateManager.getBlendMode()
, GLStateManager.getColorLogicOpState()
, GLStateManager.getColorMaterial()
, GLStateManager.getColorSumState()
, GLStateManager.getCullState()
, GLStateManager.getDepthTest()
, GLStateManager.getDitherState()
@@ -197,6 +199,7 @@ public class Feature {
));
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
GLStateManager.getFogMode()
, GLStateManager.getColorSumState()
, GLStateManager.getFogState()
// ^^ Fog density
// ^^ Linear fog start
@@ -1,6 +1,10 @@
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 it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
@@ -18,6 +22,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
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;
@@ -30,6 +35,7 @@ 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 java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
@@ -103,6 +109,9 @@ class GLContextState {
public final FogStateStack fogState = new FogStateStack();
public final BooleanStateStack fogMode = track(new BooleanStateStack(GL11.GL_FOG, false, true));
public final Color4Stack color = new Color4Stack();
// GL_CURRENT_SECONDARY_COLOR - alpha is always 1 and unused, the color sum only adds RGB
public final Color4Stack secondaryColor = new Color4Stack(new Color4(0.0F, 0.0F, 0.0F, 1.0F));
public final BooleanStateStack colorSumState = track(new BooleanStateStack(GL14.GL_COLOR_SUM, false, true));
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);
@@ -175,12 +184,19 @@ class GLContextState {
public int boundVAO;
public int boundPixelUnpackBuffer;
public int boundPixelPackBuffer;
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
public final IntSet writeMappedBuffers = new IntOpenHashSet();
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 boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged, restoreSecondaryColorChanged;
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;
@@ -564,7 +564,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);
}
@@ -10,9 +10,12 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredAlphaHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
@@ -43,6 +46,8 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
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;
@@ -52,6 +57,7 @@ import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntStack;
@@ -487,7 +493,9 @@ public class GLStateManager {
public static void initialize(GLSMInitConfig config) {
initConfig = config;
preInit(config.getDisplayWidth(), config.getDisplayHeight());
final int width = config.getDisplayWidth() > 0 ? config.getDisplayWidth() : Display.getWidth();
final int height = config.getDisplayHeight() > 0 ? config.getDisplayHeight() : Display.getHeight();
preInit(width, height);
init(config.getPostInitCallback());
}
@@ -500,6 +508,7 @@ public class GLStateManager {
.addFeature(GL11.GL_COLOR_WRITEMASK)
.addFeature(GL11.GL_CURRENT_COLOR)
.addFeature(GL11.GL_CURRENT_NORMAL)
.addFeature(GL14.GL_CURRENT_SECONDARY_COLOR)
.addFeature(GL11.GL_CURRENT_RASTER_COLOR)
.addFeature(GL11.GL_CURRENT_RASTER_POSITION)
.addFeature(GL11.GL_CURRENT_RASTER_TEXTURE_COORDS)
@@ -552,6 +561,9 @@ public class GLStateManager {
// GL_STENCIL_BITS was removed in core profile; query via default FBO attachment
final int stencilBits = RENDER_BACKEND.getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
glCtx.stencilBitMask = stencilBits >= 32 ? 0xFFFFFFFF : (1 << stencilBits) - 1;
if (glCtx.stencilBitMask == 0) {
LOGGER.warn("Default framebuffer reports 0 stencil bits on backend {}; all stencil masks clamp to 0", RENDER_BACKEND.getClass().getSimpleName());
}
// Initialize stencil masks from computed bit mask
glCtx.stencilState.setValueMaskFront(glCtx.stencilBitMask);
@@ -652,6 +664,7 @@ public class GLStateManager {
case GL11.GL_BLEND -> enableBlend();
case GL11.GL_COLOR_MATERIAL -> enableColorMaterial();
case GL11.GL_COLOR_LOGIC_OP -> glCtx.colorLogicOpState.enable();
case GL14.GL_COLOR_SUM -> glCtx.colorSumState.enable();
case GL11.GL_CULL_FACE -> enableCull();
case GL11.GL_DEPTH_TEST -> enableDepthTest();
case GL11.GL_DITHER -> glCtx.ditherState.enable();
@@ -733,6 +746,7 @@ public class GLStateManager {
case GL11.GL_BLEND -> disableBlend();
case GL11.GL_COLOR_MATERIAL -> disableColorMaterial();
case GL11.GL_COLOR_LOGIC_OP -> glCtx.colorLogicOpState.disable();
case GL14.GL_COLOR_SUM -> glCtx.colorSumState.disable();
case GL11.GL_CULL_FACE -> disableCull();
case GL11.GL_DEPTH_TEST -> disableDepthTest();
case GL11.GL_DITHER -> glCtx.ditherState.disable();
@@ -808,6 +822,7 @@ public class GLStateManager {
case GL11.GL_BLEND -> glCtx.blendMode.isEnabled();
case GL11.GL_COLOR_MATERIAL -> glCtx.colorMaterial.isEnabled();
case GL11.GL_COLOR_LOGIC_OP -> glCtx.colorLogicOpState.isEnabled();
case GL14.GL_COLOR_SUM -> glCtx.colorSumState.isEnabled();
case GL11.GL_CULL_FACE -> glCtx.cullState.isEnabled();
case GL11.GL_DEPTH_TEST -> glCtx.depthTest.isEnabled();
case GL11.GL_DITHER -> glCtx.ditherState.isEnabled();
@@ -883,6 +898,7 @@ public class GLStateManager {
case GL11.GL_BLEND -> glCtx.blendMode.isEnabled();
case GL11.GL_COLOR_MATERIAL -> glCtx.colorMaterial.isEnabled();
case GL11.GL_COLOR_LOGIC_OP -> glCtx.colorLogicOpState.isEnabled();
case GL14.GL_COLOR_SUM -> glCtx.colorSumState.isEnabled();
case GL11.GL_CULL_FACE -> glCtx.cullState.isEnabled();
case GL11.GL_DEPTH_TEST -> glCtx.depthTest.isEnabled();
case GL11.GL_DEPTH_WRITEMASK -> glCtx.depthState.isEnabled();
@@ -1126,6 +1142,7 @@ public class GLStateManager {
case GL11.GL_TEXTURE_MATRIX -> glCtx.textures.getTextureUnitMatrix(getActiveTextureUnit()).get(0, params);
case GL11.GL_COLOR_CLEAR_VALUE -> glCtx.clearColor.get(params);
case GL11.GL_CURRENT_COLOR -> glCtx.color.get(params);
case GL14.GL_CURRENT_SECONDARY_COLOR -> glCtx.secondaryColor.get(params);
case GL11.GL_DEPTH_RANGE -> {
final int pos = params.position();
params.put(pos, (float) glCtx.viewportState.depthRangeNear);
@@ -1224,7 +1241,7 @@ public class GLStateManager {
}
case GL11.GL_MODELVIEW_MATRIX, GL11.GL_PROJECTION_MATRIX, GL11.GL_TEXTURE_MATRIX -> widenFromFloat(pname, params, 16);
case GL11.GL_COLOR_CLEAR_VALUE, GL11.GL_CURRENT_COLOR, GL11.GL_CURRENT_TEXTURE_COORDS, GL11.GL_FOG_COLOR,
GL11.GL_LIGHT_MODEL_AMBIENT, GL14.GL_BLEND_COLOR -> widenFromFloat(pname, params, 4);
GL11.GL_LIGHT_MODEL_AMBIENT, GL14.GL_BLEND_COLOR, GL14.GL_CURRENT_SECONDARY_COLOR -> widenFromFloat(pname, params, 4);
case GL11.GL_CURRENT_NORMAL -> widenFromFloat(pname, params, 3);
default -> {
if (!HAS_MULTIPLE_SET.contains(pname)) {
@@ -1297,12 +1314,16 @@ public class GLStateManager {
return;
}
}
final BooleanStateStack blend = ctx().blendMode;
final boolean wasEnabled = blend.isEffectivelyEnabled();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
blend.beforeModify();
bh.deferBlendModeToggle(true);
return;
} else {
blend.enable();
}
ctx().blendMode.enable();
if (!wasEnabled && blend.isEffectivelyEnabled()) postVanillaBlendChange();
}
public static void disableBlend() {
@@ -1313,12 +1334,80 @@ public class GLStateManager {
return;
}
}
final BooleanStateStack blend = ctx().blendMode;
final boolean wasEnabled = blend.isEffectivelyEnabled();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
blend.beforeModify();
bh.deferBlendModeToggle(false);
return;
} else {
blend.disable();
}
ctx().blendMode.disable();
if (wasEnabled && !blend.isEffectivelyEnabled()) postVanillaBlendChange();
}
private static void postVanillaBlendChange() {
if (GLSMHooks.VANILLA_BLEND_CHANGE.hasListeners()) {
GLSMHooks.VANILLA_BLEND_CHANGE.post(GLSMHooks.vanillaBlendChangeEvent);
}
}
private static boolean snapshotVanillaBlendFunc() {
if (!GLSMHooks.VANILLA_BLEND_CHANGE.hasListeners()) return false;
final GLContextState glCtx = ctx();
glCtx.blendState.readEffective(glCtx.vanillaBlendBefore);
glCtx.vanillaBlendEnabledBefore = glCtx.blendMode.isEffectivelyEnabled();
return true;
}
private static void postVanillaBlendChangeIfMoved(boolean snapshotted) {
if (!snapshotted) return;
final GLContextState glCtx = ctx();
final BlendState before = glCtx.vanillaBlendBefore;
final BlendState after = glCtx.blendState.readEffective(glCtx.vanillaBlendAfter);
if (glCtx.blendMode.isEffectivelyEnabled() != glCtx.vanillaBlendEnabledBefore
|| after.getSrcRgb() != before.getSrcRgb()
|| after.getDstRgb() != before.getDstRgb()
|| after.getSrcAlpha() != before.getSrcAlpha()
|| after.getDstAlpha() != before.getDstAlpha()) {
postVanillaBlendChange();
}
}
public static boolean isEffectiveBlendEnabled() {
return ctx().blendMode.isEffectivelyEnabled();
}
public static BlendState getEffectiveBlendState(BlendState out) {
return ctx().blendState.readEffective(out);
}
public static boolean isEffectiveDepthMaskEnabled() {
return ctx().depthState.isEffectiveMaskEnabled();
}
public static boolean isEffectiveAlphaTestEnabled() {
return ctx().alphaTest.isEffectivelyEnabled();
}
public static AlphaState getEffectiveAlphaState(AlphaState out) {
return ctx().alphaState.readEffective(out);
}
private static int foreignDrawDepth;
public static void beginForeignDraw() {
foreignDrawDepth++;
}
public static void endForeignDraw() {
if (foreignDrawDepth > 0 && --foreignDrawDepth == 0 && GLSMHooks.FOREIGN_DRAW_END.hasListeners()) {
GLSMHooks.FOREIGN_DRAW_END.post(GLSMHooks.foreignDrawEndEvent);
}
}
public static boolean isForeignDraw() {
return foreignDrawDepth > 0;
}
public static void enableScissorTest() {
@@ -1352,9 +1441,11 @@ public class GLStateManager {
return;
}
}
final boolean snapshotted = snapshotVanillaBlendFunc();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
bh.deferBlendFunc(srcFactor, dstFactor, srcFactor, dstFactor);
postVanillaBlendChangeIfMoved(snapshotted);
return;
}
final boolean caching = isCachingEnabled();
@@ -1368,6 +1459,7 @@ public class GLStateManager {
GLSMHooks.blendFuncChangeEvent.dstAlpha = GL11.GL_ONE_MINUS_SRC_ALPHA;
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
postVanillaBlendChangeIfMoved(snapshotted);
return;
}
final boolean bypass = !caching;
@@ -1386,6 +1478,7 @@ public class GLStateManager {
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
}
postVanillaBlendChangeIfMoved(snapshotted);
}
public static void glBlendEquation(int mode) {
@@ -1425,9 +1518,11 @@ public class GLStateManager {
return;
}
}
final boolean snapshotted = snapshotVanillaBlendFunc();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
bh.deferBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
postVanillaBlendChangeIfMoved(snapshotted);
return;
}
if (GLSMConfig.hudCacheOverride && dstAlpha != GL11.GL_ONE_MINUS_SRC_ALPHA) {
@@ -1447,6 +1542,7 @@ public class GLStateManager {
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
}
postVanillaBlendChangeIfMoved(snapshotted);
}
public static void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
@@ -1668,6 +1764,40 @@ public class GLStateManager {
return false;
}
public static void glSecondaryColor3f(float red, float green, float blue) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordSecondaryColor(red, green, blue);
if (mode == RecordMode.COMPILE) {
return;
}
}
changeSecondaryColor(red, green, blue);
}
public static void glSecondaryColor3d(double red, double green, double blue) {
glSecondaryColor3f((float) red, (float) green, (float) blue);
}
public static void glSecondaryColor3b(byte red, byte green, byte blue) {
glSecondaryColor3f(b2f(red), b2f(green), b2f(blue));
}
public static void glSecondaryColor3ub(byte red, byte green, byte blue) {
glSecondaryColor3f(ub2f(red), ub2f(green), ub2f(blue));
}
/** Helper for glSecondaryColor* - the color sum is emulated by the FFP shaders, nothing is sent to the driver. */
private static void changeSecondaryColor(float red, float green, float blue) {
final GLContextState glCtx = ctx();
if (!isCachingEnabled() || red != glCtx.secondaryColor.getRed() || green != glCtx.secondaryColor.getGreen() || blue != glCtx.secondaryColor.getBlue()) {
glCtx.secondaryColor.setRed(red);
glCtx.secondaryColor.setGreen(green);
glCtx.secondaryColor.setBlue(blue);
glCtx.fragmentGeneration++;
}
}
private static final Color4 DirtyColor = new Color4(-1.0F, -1.0F, -1.0F, -1.0F);
public static void clearCurrentColor() {
@@ -1741,6 +1871,7 @@ public class GLStateManager {
}
final DeferredAlphaHandler ah = GLSMHooks.alphaHandler;
if (ah != null && ah.isAlphaTestLocked()) {
glCtx.alphaTest.beforeModify();
ah.deferAlphaTestToggle(true);
return;
}
@@ -1762,6 +1893,7 @@ public class GLStateManager {
}
final DeferredAlphaHandler ah = GLSMHooks.alphaHandler;
if (ah != null && ah.isAlphaTestLocked()) {
glCtx.alphaTest.beforeModify();
ah.deferAlphaTestToggle(false);
return;
}
@@ -1873,6 +2005,7 @@ public class GLStateManager {
}
public static void glBindTexture(int target, int texture) {
if (TextureInfoCache.isProxyTarget(target)) return;
final GLContextState glCtx = ctx();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -1965,6 +2098,7 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
if (mode == RecordMode.NONE && RenderSystem.isLTW()) LTWWorkaround.onTexImage2D(target, level, format, pixels != null);
maybeGenerateMipmap(target, level);
}
@@ -1983,6 +2117,7 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
if (mode == RecordMode.NONE && RenderSystem.isLTW()) LTWWorkaround.onTexImage2D(target, level, format, pixels != null);
maybeGenerateMipmap(target, level);
}
@@ -2001,6 +2136,7 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
if (mode == RecordMode.NONE && RenderSystem.isLTW()) LTWWorkaround.onTexImage2D(target, level, format, pixels != null);
maybeGenerateMipmap(target, level);
}
@@ -2019,6 +2155,7 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
restorePixelUnpackBuffer();
if (mode == RecordMode.NONE && RenderSystem.isLTW()) LTWWorkaround.onTexImage2D(target, level, format, pixels != null);
maybeGenerateMipmap(target, level);
}
@@ -2445,12 +2582,17 @@ public class GLStateManager {
return;
}
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
return;
final boolean ffpExt = FfpExtendedAttribs.maybeBindIndexed(mode, indices_count, type, indices_buffer_offset);
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices_count, type, indices_buffer_offset);
} finally {
if (ffpExt) FfpExtendedAttribs.unbind();
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices_count, type, indices_buffer_offset);
} finally {
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
@@ -2560,19 +2702,24 @@ public class GLStateManager {
FeedbackManager.processDrawArrays(mode, first, count);
return;
}
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(first, count);
} else if (mode == GL11.GL_QUAD_STRIP) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_STRIP, first, count & ~1);
} else if (mode == GL11.GL_POLYGON) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_FAN, first, count);
} else {
preDraw(mode);
RENDER_BACKEND.drawArrays(mode, first, count);
final boolean ffpExt = FfpExtendedAttribs.maybeBind(mode, first, count);
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(first, count);
} else if (mode == GL11.GL_QUAD_STRIP) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_STRIP, first, count & ~1);
} else if (mode == GL11.GL_POLYGON) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_FAN, first, count);
} else {
preDraw(mode);
RENDER_BACKEND.drawArrays(mode, first, count);
}
} finally {
if (ffpExt) FfpExtendedAttribs.unbind();
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
private static void ffpClientArrayPointer(int index, int size, int type, boolean normalized, int stride, long offset) {
@@ -3049,9 +3196,15 @@ public class GLStateManager {
return;
}
}
if (light >= VertexKey.FFP_LIGHT_COUNT && (warnedFfpLightMask & (1 << light)) == 0 && ShaderManager.getInstance().isEnabled()) {
warnedFfpLightMask |= (1 << light);
LOGGER.warn("GL_LIGHT{} enabled, but FFP shader emulation implements only GL_LIGHT0..{}", light, VertexKey.FFP_LIGHT_COUNT - 1, new Throwable("call site"));
}
ctx().lightStates[light].enable();
}
private static int warnedFfpLightMask = 0;
public static void enableColorMaterial() {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -3374,19 +3527,19 @@ public class GLStateManager {
}
public static boolean isCurrent() {
if (BackendManager.RENDER_BACKEND == null) {
return Thread.currentThread() == MainThread;
}
return BackendManager.RENDER_BACKEND.isCurrent();
return RENDER_BACKEND.isCurrent();
}
public static void swapBuffers() throws LWJGLException {
if (BackendManager.RENDER_BACKEND != null && BackendManager.RENDER_BACKEND.handleSwapBuffers()) {
if (RENDER_BACKEND.handleSwapBuffers()) {
return;
}
Display.swapBuffers();
}
private static boolean firstClearPending;
private static long firstClearBlockNanos;
public static void updateDisplay() throws LWJGLException {
updateDisplay(true);
}
@@ -3394,10 +3547,19 @@ public class GLStateManager {
public static void updateDisplay(boolean processMessages) throws LWJGLException {
if (Thread.currentThread() != MainThread) {
swapBuffers();
if (processMessages) Display.processMessages();
if (processMessages) pumpDisplayMessages();
return;
}
Display.update(processMessages);
if (processMessages) pumpDisplayMessages();
if (!RENDER_BACKEND.wantsDisplayUpdateGateTiming()) {
Display.update(false);
return;
}
final long start = System.nanoTime();
Display.update(false);
final long end = System.nanoTime();
RENDER_BACKEND.recordGate(end - start, end);
firstClearPending = true;
}
/**
@@ -3513,6 +3675,9 @@ public class GLStateManager {
final int mask = glCtx.attribs.popInt();
glCtx.attribDepth--;
// Snapshot before the stacks pop, so applyRestoredState can tell whether vanilla blend actually moved.
final boolean blendSnapshotted = (mask & (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT)) != 0 && snapshotVanillaBlendFunc();
// First: restore BooleanStateStack states that were actually modified (fast path)
// These use lazy copy-on-write with global depth tracking
final List<IStateStack<?>> modified = glCtx.modifiedAtDepth[glCtx.attribDepth];
@@ -3531,7 +3696,12 @@ public class GLStateManager {
// Third: apply restored state to the GL driver. BooleanStateStacks already issue GL calls via setEnabled(); non-boolean stacks are pure data
// containers, so we must explicitly drive GL here.
applyRestoredState(mask);
applyRestoredState(mask, blendSnapshotted);
}
private static boolean isDepthColorOverridden() {
final DeferredDepthColorHandler dch = GLSMHooks.depthColorHandler;
return dch != null && dch.isOverrideHeld();
}
private static void captureRestoreChanges(int mask) {
@@ -3546,6 +3716,9 @@ public class GLStateManager {
glCtx.restoreDrawBufferChanged = glCtx.drawBuffer.topChanged();
glCtx.restoreLogicOpChanged = glCtx.logicOpMode.topChanged();
}
if ((mask & GL11.GL_CURRENT_BIT) != 0) {
glCtx.restoreSecondaryColorChanged = glCtx.secondaryColor.topChanged();
}
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
glCtx.restoreStencilChanged = glCtx.stencilState.topChanged();
}
@@ -3620,20 +3793,25 @@ public class GLStateManager {
/**
* After popping GLSM stacks, apply restored state to the GL driver.
*/
private static void applyRestoredState(int mask) {
private static void applyRestoredState(int mask, boolean blendSnapshotted) {
final GLContextState glCtx = ctx();
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0 && glCtx.restoreDepthChanged) {
RENDER_BACKEND.depthFunc(glCtx.depthState.getFunc());
RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled());
if (!isDepthColorOverridden()) {
RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled());
}
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
}
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
if (glCtx.restoreBlendChanged) {
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
final DeferredBlendHandler restoreBlendHandler = GLSMHooks.blendHandler;
if (restoreBlendHandler == null || !restoreBlendHandler.isOverrideHeld()) {
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
}
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
}
if (glCtx.restoreColorMaskChanged) {
if (glCtx.restoreColorMaskChanged && !isDepthColorOverridden()) {
RENDER_BACKEND.colorMask(glCtx.colorMask.red, glCtx.colorMask.green, glCtx.colorMask.blue, glCtx.colorMask.alpha);
}
if (glCtx.restoreClearColorChanged) {
@@ -3717,7 +3895,11 @@ public class GLStateManager {
ShaderManager.bumpTexCoordGeneration();
glCtx.dirtyTexCoordAttrib = true;
}
// The secondary color is a fragment stage uniform, not a vertex attribute
if (glCtx.restoreSecondaryColorChanged) glCtx.fragmentGeneration++;
}
postVanillaBlendChangeIfMoved(blendSnapshotted);
}
public static void glClear(int mask) {
@@ -3728,9 +3910,26 @@ public class GLStateManager {
return;
}
}
if (firstClearPending && Thread.currentThread() == MainThread && getDrawFramebuffer() == 0) {
firstClearPending = false;
final long start = System.nanoTime();
RENDER_BACKEND.clear(mask);
final long end = System.nanoTime();
if (end - start > 0) {
firstClearBlockNanos += end - start;
RENDER_BACKEND.recordGate(end - start, end);
}
return;
}
RENDER_BACKEND.clear(mask);
}
public static long takeFirstClearBlockNanos() {
final long value = firstClearBlockNanos;
firstClearBlockNanos = 0L;
return value;
}
public static void glPushAttrib(int mask) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -4380,6 +4579,11 @@ public class GLStateManager {
};
}
private static int mipLevelSize(int base, int level) {
if (base <= 0 || level < 0 || level >= Integer.SIZE - 1) return 1;
return Math.max(base >> level, 1);
}
public static int glGetTexLevelParameteri(int target, int level, int pname) {
if (target != GL11.GL_TEXTURE_2D || !isCachingEnabled()) {
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
@@ -4394,8 +4598,8 @@ public class GLStateManager {
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
}
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> Math.max(info.getWidth() >> level, 1);
case GL11.GL_TEXTURE_HEIGHT -> Math.max(info.getHeight() >> level, 1);
case GL11.GL_TEXTURE_WIDTH -> mipLevelSize(info.getWidth(), level);
case GL11.GL_TEXTURE_HEIGHT -> mipLevelSize(info.getHeight(), level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
if (info.needsInternalFormatResolve() && isRecordingDisplayList()) {
throw new IllegalStateException(String.format("glGetTexLevelParameteri(GL_TEXTURE_INTERNAL_FORMAT) needs to resolve a generic compressed format for texture %d during display list recording", getBoundTextureForServerState()));
@@ -4510,7 +4714,10 @@ public class GLStateManager {
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
restorePixelPackBuffer();
}
public static void glTexStorage2D(int target, int levels, int internalFormat, int width, int height) { RENDER_BACKEND.texStorage2D(target, levels, internalFormat, width, height); }
public static void glTexStorage2D(int target, int levels, int internalFormat, int width, int height) {
RENDER_BACKEND.texStorage2D(target, levels, internalFormat, width, height);
TextureInfoCache.INSTANCE.onTexStorage2D(target, internalFormat, width, height);
}
public static void glClearTexImage(int texture, int level, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearTexImage(texture, level, format, type); }
public static int glGenSamplers() { return RENDER_BACKEND.genSamplers(); }
@@ -4903,16 +5110,22 @@ public class GLStateManager {
final ShaderManager ffp = ShaderManager.getInstance();
if (program == 0 && ffp.isEnabled()) {
final int prev = glCtx.activeProgram;
final boolean caching = isCachingEnabled();
if (prev != 0) glCtx.programGeneration++;
final boolean changed = prev != 0;
if (changed) glCtx.programGeneration++;
glCtx.activeProgram = 0; // Track that FFP was requested
if (GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
if (changed && GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
GLSMHooks.programChangeEvent.previousProgram = prev;
GLSMHooks.programChangeEvent.newProgram = 0;
GLSMHooks.programChangeEvent.postBind = false;
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
}
ffp.activate();
if (changed && GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
GLSMHooks.programChangeEvent.previousProgram = prev;
GLSMHooks.programChangeEvent.newProgram = 0;
GLSMHooks.programChangeEvent.postBind = true;
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
}
return;
}
@@ -5039,6 +5252,42 @@ public class GLStateManager {
RENDER_BACKEND.setVSyncEnabled(enabled);
}
public static void setVSyncMode(VSyncMode mode) {
RENDER_BACKEND.setVSyncMode(mode);
}
public static void setVSyncPreference(VSyncMode mode) {
RENDER_BACKEND.setVSyncPreference(mode);
}
public static boolean supportsVSyncMode(VSyncMode mode) {
return RENDER_BACKEND.supportsVSyncMode(mode);
}
public static VSyncMode preferredTearFreeMode() {
return RENDER_BACKEND.preferredTearFreeMode();
}
public static String getRenderBackendName() {
return RENDER_BACKEND.getName();
}
public static VSyncMode getEffectiveVSyncMode() {
return RENDER_BACKEND.getEffectiveVSyncMode();
}
public static void setPresentSuppressed(boolean suppressed) {
RENDER_BACKEND.setPresentSuppressed(suppressed);
}
public static boolean hasSwapchainBackpressure() {
return RENDER_BACKEND.hasSwapchainBackpressure();
}
public static void pumpDisplayMessages() {
RENDER_BACKEND.pumpDisplayMessages();
}
public static int glGetError() {
if (!RENDER_BACKEND.hasContext()) return 0;
if (initConfig.isNoErrorChecks()) return 0;
@@ -5119,6 +5368,7 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
restorePixelUnpackBuffer();
TextureInfoCache.INSTANCE.onTexSubImage2D(target, level);
maybeGenerateMipmap(target, level);
}
@@ -5134,6 +5384,7 @@ public class GLStateManager {
suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
restorePixelUnpackBuffer();
TextureInfoCache.INSTANCE.onTexSubImage2D(target, level);
maybeGenerateMipmap(target, level);
}
@@ -5143,6 +5394,7 @@ public class GLStateManager {
throw DisplayListManager.unsupportedInList("glTexSubImage2D with buffer offset");
}
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
TextureInfoCache.INSTANCE.onTexSubImage2D(target, level);
maybeGenerateMipmap(target, level);
}
@@ -5183,6 +5435,7 @@ public class GLStateManager {
throw DisplayListManager.unsupportedInList("glCopyTexSubImage2D");
}
RENDER_BACKEND.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
TextureInfoCache.INSTANCE.onTexSubImage2D(target, level);
maybeGenerateMipmap(target, level);
}
@@ -5663,10 +5916,19 @@ public class GLStateManager {
if (buffer == 0) return;
final boolean locked = acquireDrawLock();
try {
FfpExtendedAttribs.onDeleteBuffer(buffer);
if (glCtx.boundVBO == buffer) glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (VAOManager.boundEBO == buffer) glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
if (glCtx.boundPixelUnpackBuffer == buffer) glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
if (glCtx.boundPixelPackBuffer == buffer) glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
if (glCtx.boundCopyReadBuffer == buffer) glBindBuffer(GL31.GL_COPY_READ_BUFFER, 0);
if (glCtx.boundCopyWriteBuffer == buffer) glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, 0);
for (Int2IntMap.Entry e : glCtx.boundOtherBuffers.int2IntEntrySet()) {
if (e.getIntValue() != buffer) continue;
e.setValue(0);
RENDER_BACKEND.bindBuffer(e.getIntKey(), 0);
}
glCtx.writeMappedBuffers.remove(buffer);
VAOManager.onDeleteBuffer(buffer);
} finally {
@@ -5674,20 +5936,30 @@ public class GLStateManager {
}
}
/** glBindBufferBase/Range also set the generic binding point, so they share this. */
private static void trackBufferBinding(GLContextState glCtx, int target, int buffer) {
if (target == GL15.GL_ARRAY_BUFFER) {
// if (boundVBO == buffer) return; TODO figure out why this breaks switching async occlusion mode
glCtx.boundVBO = buffer;
} else if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) {
VAOManager.onBindEBO(buffer);
} else if (target == GL21.GL_PIXEL_UNPACK_BUFFER) {
glCtx.boundPixelUnpackBuffer = buffer;
} else if (target == GL21.GL_PIXEL_PACK_BUFFER) {
glCtx.boundPixelPackBuffer = buffer;
} else if (target == GL31.GL_COPY_READ_BUFFER) {
glCtx.boundCopyReadBuffer = buffer;
} else if (target == GL31.GL_COPY_WRITE_BUFFER) {
glCtx.boundCopyWriteBuffer = buffer;
} else {
glCtx.boundOtherBuffers.put(target, buffer);
}
}
public static void glBindBuffer(int target, int buffer) {
final GLContextState glCtx = ctx();
final boolean locked = acquireDrawLock();
try {
if (target == GL15.GL_ARRAY_BUFFER) {
// if (boundVBO == buffer) return; TODO figure out why this breaks switching async occlusion mode
glCtx.boundVBO = buffer;
} else if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) {
VAOManager.onBindEBO(buffer);
} else if (target == GL21.GL_PIXEL_UNPACK_BUFFER) {
glCtx.boundPixelUnpackBuffer = buffer;
} else if (target == GL21.GL_PIXEL_PACK_BUFFER) {
glCtx.boundPixelPackBuffer = buffer;
}
trackBufferBinding(ctx(), target, buffer);
RENDER_BACKEND.bindBuffer(target, buffer);
} finally {
if (locked) releaseDrawLock();
@@ -5766,15 +6038,78 @@ public class GLStateManager {
for (int i = 0; i < buffers.length; i++) buffers[i] = RENDER_BACKEND.createBuffers();
}
public static void glBufferData(int target, long size, int usage) { RENDER_BACKEND.bufferData(target, size, usage); }
public static void glBufferData(int target, ByteBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, ShortBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, IntBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, FloatBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, DoubleBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
private static final int MAP_PERSISTENT_BIT = 0x40;
private static int bufferForTarget(int target) {
final GLContextState glCtx = ctx();
if (target == GL15.GL_ARRAY_BUFFER) return glCtx.boundVBO;
if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) return VAOManager.boundEBO;
if (target == GL31.GL_COPY_READ_BUFFER) return glCtx.boundCopyReadBuffer;
if (target == GL31.GL_COPY_WRITE_BUFFER) return glCtx.boundCopyWriteBuffer;
if (target == GL21.GL_PIXEL_UNPACK_BUFFER) return glCtx.boundPixelUnpackBuffer;
if (target == GL21.GL_PIXEL_PACK_BUFFER) return glCtx.boundPixelPackBuffer;
return glCtx.boundOtherBuffers.get(target);
}
private static void onBufferRespec(int target) {
if (FfpExtendedAttribs.isEmpty()) return;
final int buffer = bufferForTarget(target);
if (buffer != 0) FfpExtendedAttribs.onBufferRespecified(buffer);
}
private static void onBufferMap(int target, int access) {
if (access == GL15.GL_READ_ONLY) return;
markWriteMapped(target);
onBufferRespec(target);
}
private static void onBufferMapRange(int target, int access) {
if ((access & GL30.GL_MAP_WRITE_BIT) == 0) return;
if ((access & MAP_PERSISTENT_BIT) != 0) {
markPersistentlyMapped(target);
return;
}
markWriteMapped(target);
onBufferRespec(target);
}
private static void markWriteMapped(int target) {
final int buffer = bufferForTarget(target);
if (buffer != 0) ctx().writeMappedBuffers.add(buffer);
}
private static void onBufferUnmap(int target) {
final int buffer = bufferForTarget(target);
if (buffer == 0 || !ctx().writeMappedBuffers.remove(buffer)) return;
onBufferRespec(target);
}
private static void onBufferStorage(int target, int flags) {
if ((flags & MAP_PERSISTENT_BIT) != 0) {
markPersistentlyMapped(target);
return;
}
onBufferRespec(target);
}
private static void markPersistentlyMapped(int target) {
FfpExtendedAttribs.markPersistent(bufferForTarget(target));
}
private static void onNamedBufferRespec(int buffer) {
if (FfpExtendedAttribs.isEmpty()) return;
FfpExtendedAttribs.onBufferRespecified(buffer);
}
public static void glBufferData(int target, long size, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, size, usage); }
public static void glBufferData(int target, ByteBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, ShortBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, IntBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, FloatBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, DoubleBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, LongBuffer data, int usage) { glBufferData(target, MemoryUtilities.memByteBuffer(data), usage); }
public static void glBufferData(int target, int[] data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, float[] data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, int[] data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, float[] data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, short[] data, int usage) {
final ByteBuffer copy = copyOf(data);
glBufferData(target, copy, usage);
@@ -5799,11 +6134,11 @@ public class GLStateManager {
glBufferData(target, MemoryUtilities.memByteBuffer(data, checkedSize(size)), usage);
}
public static void glBufferSubData(int target, long offset, ByteBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, ShortBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, IntBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, FloatBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, DoubleBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, ByteBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, ShortBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, IntBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, FloatBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, DoubleBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, LongBuffer data) { glBufferSubData(target, offset, MemoryUtilities.memByteBuffer(data)); }
public static void glBufferSubData(int target, long offset, short[] data) {
final ByteBuffer copy = copyOf(data);
@@ -5833,13 +6168,13 @@ public class GLStateManager {
public static void nglBufferSubData(int target, long offset, long size, long data) { glBufferSubData(target, offset, MemoryUtilities.memByteBuffer(data, checkedSize(size))); }
public static ByteBuffer glMapBuffer(int target, int access) { return RENDER_BACKEND.mapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access, ByteBuffer old_buffer) { return glMapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) { return RENDER_BACKEND.mapBuffer(target, access, length, old_buffer); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) { return RENDER_BACKEND.mapBufferRange(target, offset, length, access); }
public static ByteBuffer glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access, length, old_buffer); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) { onBufferMapRange(target, access); return RENDER_BACKEND.mapBufferRange(target, offset, length, access); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access, ByteBuffer old_buffer) { return glMapBufferRange(target, offset, length, access); }
public static void glFlushMappedBufferRange(int target, long offset, long length) { RENDER_BACKEND.flushMappedBufferRange(target, offset, length); }
public static boolean glUnmapBuffer(int target) { return RENDER_BACKEND.unmapBuffer(target); }
public static boolean glUnmapBuffer(int target) { onBufferUnmap(target); return RENDER_BACKEND.unmapBuffer(target); }
public static void glGetBufferSubData(int target, long offset, ByteBuffer data) { RENDER_BACKEND.getBufferSubData(target, offset, data); }
public static void glGetBufferSubData(int target, long offset, ShortBuffer data) { RENDER_BACKEND.getBufferSubData(target, offset, data); }
@@ -5886,8 +6221,8 @@ public class GLStateManager {
public static void glGetBufferParameteriv(int target, int pname, IntBuffer params) { params.put(params.position(), glGetBufferParameteri(target, pname)); }
public static void glGetBufferParameteriv(int target, int pname, int[] params) { params[0] = glGetBufferParameteri(target, pname); }
public static void glBufferStorage(int target, long size, int flags) { RENDER_BACKEND.bufferStorage(target, size, flags); }
public static void glBufferStorage(int target, ByteBuffer data, int flags) { RENDER_BACKEND.bufferStorage(target, data, flags); }
public static void glBufferStorage(int target, long size, int flags) { onBufferStorage(target, flags); RENDER_BACKEND.bufferStorage(target, size, flags); }
public static void glBufferStorage(int target, ByteBuffer data, int flags) { onBufferStorage(target, flags); RENDER_BACKEND.bufferStorage(target, data, flags); }
public static void glBufferStorage(int target, ShortBuffer data, int flags) { glBufferStorage(target, MemoryUtilities.memByteBuffer(data), flags); }
public static void glBufferStorage(int target, IntBuffer data, int flags) { glBufferStorage(target, MemoryUtilities.memByteBuffer(data), flags); }
public static void glBufferStorage(int target, FloatBuffer data, int flags) { glBufferStorage(target, MemoryUtilities.memByteBuffer(data), flags); }
@@ -5913,9 +6248,9 @@ public class GLStateManager {
MemoryUtilities.memFree(copy);
}
public static void glNamedBufferData(int buffer, long size, int usage) { RENDER_BACKEND.namedBufferData(buffer, size, usage); }
public static void glNamedBufferData(int buffer, ByteBuffer data, int usage) { RENDER_BACKEND.namedBufferData(buffer, data, usage); }
public static void glNamedBufferData(int buffer, FloatBuffer data, int usage) { RENDER_BACKEND.namedBufferData(buffer, data, usage); }
public static void glNamedBufferData(int buffer, long size, int usage) { onNamedBufferRespec(buffer); RENDER_BACKEND.namedBufferData(buffer, size, usage); }
public static void glNamedBufferData(int buffer, ByteBuffer data, int usage) { onNamedBufferRespec(buffer); RENDER_BACKEND.namedBufferData(buffer, data, usage); }
public static void glNamedBufferData(int buffer, FloatBuffer data, int usage) { onNamedBufferRespec(buffer); RENDER_BACKEND.namedBufferData(buffer, data, usage); }
public static void glNamedBufferData(int buffer, ShortBuffer data, int usage) { glNamedBufferData(buffer, MemoryUtilities.memByteBuffer(data), usage); }
public static void glNamedBufferData(int buffer, IntBuffer data, int usage) { glNamedBufferData(buffer, MemoryUtilities.memByteBuffer(data), usage); }
public static void glNamedBufferData(int buffer, LongBuffer data, int usage) { glNamedBufferData(buffer, MemoryUtilities.memByteBuffer(data), usage); }
@@ -5946,7 +6281,7 @@ public class GLStateManager {
MemoryUtilities.memFree(copy);
}
public static void glNamedBufferSubData(int buffer, long offset, ByteBuffer data) { RENDER_BACKEND.namedBufferSubData(buffer, offset, data); }
public static void glNamedBufferSubData(int buffer, long offset, ByteBuffer data) { onNamedBufferRespec(buffer); RENDER_BACKEND.namedBufferSubData(buffer, offset, data); }
public static void glNamedBufferSubData(int buffer, long offset, ShortBuffer data) { glNamedBufferSubData(buffer, offset, MemoryUtilities.memByteBuffer(data)); }
public static void glNamedBufferSubData(int buffer, long offset, IntBuffer data) { glNamedBufferSubData(buffer, offset, MemoryUtilities.memByteBuffer(data)); }
public static void glNamedBufferSubData(int buffer, long offset, LongBuffer data) { glNamedBufferSubData(buffer, offset, MemoryUtilities.memByteBuffer(data)); }
@@ -5978,15 +6313,15 @@ public class GLStateManager {
MemoryUtilities.memFree(copy);
}
public static void glClearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, data); }
public static void glClearBufferData(int target, int internalformat, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearBufferData(target, internalformat, format, type, data); }
public static void glClearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) { onBufferRespec(target); RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, data); }
public static void glClearBufferData(int target, int internalformat, int format, int type, ByteBuffer data) { onBufferRespec(target); RENDER_BACKEND.clearBufferData(target, internalformat, format, type, data); }
public static boolean glIsBuffer(int buffer) { return RENDER_BACKEND.isBuffer(buffer); }
public static boolean glIsTexture(int texture) { return RENDER_BACKEND.isTexture(texture); }
public static boolean glIsFramebuffer(int framebuffer) { return RENDER_BACKEND.isFramebuffer(framebuffer); }
public static boolean glIsRenderbuffer(int renderbuffer) { return RENDER_BACKEND.isRenderbuffer(renderbuffer); }
public static boolean glIsSampler(int sampler) { return RENDER_BACKEND.isSampler(sampler); }
public static boolean glIsQuery(int query) { return RENDER_BACKEND.isQuery(query); }
public static void glCopyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size) { RENDER_BACKEND.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); }
public static void glCopyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size) { onBufferRespec(writeTarget); RENDER_BACKEND.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); }
private static int checkedSize(long size) {
if (size < 0 || size > Integer.MAX_VALUE) {
@@ -6150,7 +6485,26 @@ public class GLStateManager {
public static void glGetQueryObjectui(int id, int pname, IntBuffer params) { RENDER_BACKEND.getQueryObjectui(id, pname, params); }
public static int glGetQueryObjecti(int id, int pname) { return RENDER_BACKEND.getQueryObjecti(id, pname); }
public static void glBindBufferBase(int target, int index, int buffer) { RENDER_BACKEND.bindBufferBase(target, index, buffer); }
public static void glBindBufferBase(int target, int index, int buffer) {
final boolean locked = acquireDrawLock();
try {
trackBufferBinding(ctx(), target, buffer);
RENDER_BACKEND.bindBufferBase(target, index, buffer);
} finally {
if (locked) releaseDrawLock();
}
}
public static void glBindBufferRange(int target, int index, int buffer, long offset, long size) {
final boolean locked = acquireDrawLock();
try {
trackBufferBinding(ctx(), target, buffer);
RENDER_BACKEND.bindBufferRange(target, index, buffer, offset, size);
} finally {
if (locked) releaseDrawLock();
}
}
public static int glGetUniformBlockIndex(int program, CharSequence name) { return RENDER_BACKEND.getUniformBlockIndex(program, name); }
public static void glUniformBlockBinding(int program, int blockIndex, int binding) { RENDER_BACKEND.uniformBlockBinding(program, blockIndex, binding); }
public static void glBindFragDataLocation(int program, int colorNumber, CharSequence name) { RENDER_BACKEND.bindFragDataLocation(program, colorNumber, name); }
@@ -6188,6 +6542,7 @@ public class GLStateManager {
}
}
public static long nglMapBuffer(int target, int access) {
onBufferMap(target, access);
final ByteBuffer buf = RENDER_BACKEND.mapBuffer(target, access);
return buf == null ? 0L : MemoryUtilities.memAddress0(buf);
}
@@ -7233,6 +7588,14 @@ public class GLStateManager {
return ctx().color;
}
public static Color4Stack getSecondaryColor() {
return ctx().secondaryColor;
}
public static BooleanStateStack getColorSumState() {
return ctx().colorSumState;
}
public static Color4Stack getClearColor() {
return ctx().clearColor;
}
@@ -7497,6 +7860,14 @@ public class GLStateManager {
return ctx().boundVBO;
}
public static int getBoundCopyReadBuffer() {
return ctx().boundCopyReadBuffer;
}
public static int getBoundCopyWriteBuffer() {
return ctx().boundCopyWriteBuffer;
}
public static int getBoundVAO() {
return ctx().boundVAO;
}
@@ -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);
}
}
@@ -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;
@@ -59,6 +61,9 @@ public class RenderSystem {
private static volatile boolean glesDetected;
private static volatile boolean hasClipCullDistance;
private static volatile boolean isLTW;
private static volatile boolean ltwDetected;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
@@ -107,6 +112,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;
@@ -403,6 +415,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;
}
@@ -527,7 +559,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) {
@@ -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,7 +115,19 @@ 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() {
@@ -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);
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
@@ -12,6 +13,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 +65,175 @@ public abstract class RenderBackend {
public abstract void flush();
public abstract void finish();
public abstract void setVSyncEnabled(boolean enabled);
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()) {
@@ -73,7 +244,7 @@ public abstract class RenderBackend {
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; }
@@ -0,0 +1,11 @@
package com.gtnewhorizons.angelica.glsm.backend;
public enum VSyncMode {
OFF,
ON,
MAILBOX;
public boolean tearFree() {
return this != OFF;
}
}
@@ -103,6 +103,7 @@ 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");
@@ -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,583 @@
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.GLStateManager;
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 int internalGlDepth = 0;
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();
}
private static int internalDrawDepth = 0;
public static void beginInternalDraw() { internalDrawDepth++; }
public static void endInternalDraw() { if (internalDrawDepth > 0) internalDrawDepth--; }
private static void beginInternalGl() { internalGlDepth++; }
private static void endInternalGl() { if (internalGlDepth > 0) 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(int mode, int first, int count) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (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 = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId)) return false;
VAOManager.Attrib normal = extPrim == 3 ? VAOManager.get(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(int mode, int indexCount, int indexType, long indicesOffset) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (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 = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(VAOManager.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(VAOManager.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() {
GLStateManager.glVertexAttrib2f(ImmediateExtendedAttribHandler.LOC_MID_TEX, 0.5f, 0.5f);
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
}
public static void onDeleteBuffer(int vboId) {
if (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(int vboId) {
if (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);
}
public static void markStreaming(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
respecCounts.remove(vboId);
respecRunStart.remove(vboId);
respecFrames.put(vboId, frame);
if (streamingSources.add(vboId)) dropDerivedFrom(vboId);
}
public static void markPersistent(int vboId) {
if (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;
}
}
}
@@ -12,7 +12,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 (13) + unit 0 (47 bits at offset 13)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,7 +63,7 @@ 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 = 13;
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 +71,7 @@ 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 U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -130,10 +131,12 @@ public final class FragmentKey {
global |= (1L << BIT_LINE_STIPPLE);
}
// Separate specular
// Separate specular / color sum
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
global |= (1L << BIT_SEPARATE_SPECULAR);
} else if (GLStateManager.getColorSumState().isEnabled()) {
global |= (1L << BIT_COLOR_SUM);
}
// Per-unit state
@@ -238,6 +241,7 @@ public final class FragmentKey {
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; }
@@ -374,10 +378,10 @@ 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",
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(), overlayEnabled(), nrEnabledUnits()));
separateSpecular(), colorSum(), overlayEnabled(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -276,6 +276,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) {
@@ -2,12 +2,12 @@ package com.gtnewhorizons.angelica.glsm.ffp;
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_MATRIX_COL0 = 5;
public static final int LOC_MATRIX_COL1 = 6;
public static final int LOC_MATRIX_COL2 = 7;
public static final int LOC_MATRIX_COL3 = 8;
public static final int LOC_COLOR = 9;
public static final int LOC_LIGHTMAP = 10;
public static final int OFFSET_MATRIX = 0;
public static final int OFFSET_COLOR = 64;
@@ -80,6 +80,10 @@ public final class ShaderManager {
warmUp();
enabled = true;
if (GLStateManager.getActiveProgram() == 0) {
activate();
}
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
@@ -179,6 +183,7 @@ public final class ShaderManager {
sm.lastFramePreDrawCalls = sm.preDrawCalls;
sm.preDrawCalls = 0;
sm.uniforms.endFrame();
FfpExtendedAttribs.endFrame();
}
public static void setCurrentNormal(float x, float y, float z) {
@@ -27,7 +27,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.
@@ -209,7 +208,7 @@ 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++;
@@ -232,8 +231,12 @@ public class Uniforms {
private void stageNormalMatrix() {
GLStateManager.getModelViewMatrix().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));
}
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 stageTextureMatrices() {
@@ -327,6 +330,9 @@ public class Uniforms {
GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
final var secondary = GLStateManager.getSecondaryColor();
putVec3(FFPUniformBlock.SECONDARY_COLOR, secondary.getRed(), secondary.getGreen(), secondary.getBlue());
// Mesa STATE_FOG_PARAMS_OPTIMIZED
final FogState fog = GLStateManager.getFogState();
final float start = fog.getStart();
@@ -20,38 +20,36 @@ 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_INSTANCED = BIT_LINE_STIPPLE + 1;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -70,10 +68,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); }
@@ -136,17 +132,12 @@ public final class VertexKey {
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 (!GLStateManager.getLightStates()[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 (GLStateManager.getLightDataStates()[i].position.w == 0.0f) {
bits |= (1L << (BIT_LIGHT_DIR_BASE + i));
}
}
@@ -265,7 +256,7 @@ 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(),
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,
@@ -182,11 +182,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 +197,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');
@@ -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,65 @@
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 bindSplashVao() {
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
}
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++;
}
}
}
@@ -6,8 +6,17 @@ public final class GLSMConfig {
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() {
return packBrightness(lastBrightnessX, lastBrightnessY);
}
public static boolean hudCacheOverride;
public static boolean extendedAttribsExpected;
public static volatile boolean expandVertexFormats;
private GLSMConfig() {}
@@ -9,6 +9,7 @@ public final class GLSMHooks {
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ImmediateExtendedAttribHandler immediateExtendedHandler;
public static ShaderWorkSubmitter shaderWorkSubmitter;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
@@ -25,6 +26,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 +41,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;
@@ -14,7 +16,7 @@ public final class GLSMInitConfig {
@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) {
@@ -26,7 +28,11 @@ public final class GLSMInitConfig {
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 +40,14 @@ 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 Runnable streamingDrawerDestroy = TessellatorStreamingDrawer::destroy;
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() {}
@@ -83,7 +89,7 @@ public final class GLSMInitConfig {
}
public Builder enableDSA(boolean enableDSA) {
this.enableDSA = enableDSA;
this.dsaEnabled = enableDSA;
return this;
}
@@ -0,0 +1,64 @@
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();
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);
}
}
@@ -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);
}
}
@@ -0,0 +1,47 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
public final class TextureUtilHooks {
private TextureUtilHooks() {}
private static int savedFilterTextureId;
public static void setMaxLevel(int mipmapLevels) {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
}
public static void captureBoundFilterTexture() {
savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
}
public static void clearSavedFilterTexture() {
savedFilterTextureId = 0;
}
public static void restoreFilter(int min, int mag) {
final int id = 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 = 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);
}
}
}
@@ -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,26 @@
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;
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,6 +20,10 @@ 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
@@ -28,5 +37,39 @@ public final class DependencyVerifier {
}
}
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,78 @@
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 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,76 @@
package com.gtnewhorizons.angelica.glsm.loading;
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;
protected RfbModRedirector(@Pattern("[a-z0-9-]+") String id, ModRedirector inner) {
this.id = id;
this.inner = inner;
}
@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;
}
return inner.shouldTransform(classNode.getOriginalBytes());
}
@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);
}
}
}
@@ -44,6 +44,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() {
@@ -62,6 +74,10 @@ public final class Tracy {
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
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");
}
}
@@ -95,6 +111,7 @@ public final class Tracy {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but no TracyBackend service present");
return null;
}
if (!implementsCurrentAbi(found)) return null;
final TracyBackend backend = found;
final CountDownLatch done = new CountDownLatch(1);
final AtomicReference<Boolean> result = new AtomicReference<>();
@@ -120,10 +137,28 @@ public final class Tracy {
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 +203,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 +309,7 @@ public final class Tracy {
threadNameRefreshed = true;
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
}
SECTIONS.poll();
BACKEND.frameMark();
}
@@ -330,8 +366,22 @@ 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() {
@@ -5,6 +5,13 @@ 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;
boolean init();
long internSrcLoc(String name, int color);
@@ -24,7 +31,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();
@@ -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];
@@ -274,6 +274,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);
@@ -356,6 +356,14 @@ 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 writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
@@ -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) {
@@ -61,6 +61,7 @@ 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]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
@@ -155,6 +156,7 @@ public final class GLCommand {
case COLOR -> "COLOR";
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case SECONDARY_COLOR -> "SECONDARY_COLOR";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
@@ -211,7 +213,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,
@@ -142,6 +142,7 @@ public class GLSMRedirector {
private static final String GLU = "org/lwjgl/util/glu/GLU";
private static final String OpenGlHelper = "net/minecraft/client/renderer/OpenGlHelper";
private static final String EXTBlendFunc = "org/lwjgl/opengl/EXTBlendFuncSeparate";
private static final String EXTSecondaryColor = "org/lwjgl/opengl/EXTSecondaryColor";
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
private static final String ARBInstancedArrays = "org/lwjgl/opengl/ARBInstancedArrays";
@@ -162,6 +163,7 @@ public class GLSMRedirector {
private static final String EXTGpuShader4 = "org/lwjgl/opengl/EXTGpuShader4";
private static final String EXTGPUShader4 = "org/lwjgl/opengl/EXTGPUShader4";
private static final String ARBTimerQuery = "org/lwjgl/opengl/ARBTimerQuery";
private static final String EXTTimerQuery = "org/lwjgl/opengl/EXTTimerQuery";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
@@ -402,7 +404,12 @@ public class GLSMRedirector {
.add("glBlendEquation")
.add("glMultiDrawArrays")
.add("glPointParameterf")
.add("glPointParameteri");
.add("glPointParameteri")
// Removed in core profile - emulated by the FFP shaders, calling through would abort the JVM
.add("glSecondaryColor3f")
.add("glSecondaryColor3d")
.add("glSecondaryColor3b")
.add("glSecondaryColor3ub");
final var gl15 = RedirectMap.newMap()
.add("glGenBuffers")
.add("glBindBuffer")
@@ -510,7 +517,8 @@ public class GLSMRedirector {
.add("glFramebufferRenderbuffer")
.add("glIsFramebuffer")
.add("glIsRenderbuffer")
.add("glBindBufferBase");
.add("glBindBufferBase")
.add("glBindBufferRange");
final var gl31 = RedirectMap.newMap()
.add("glDrawElementsInstanced")
.add("glDrawArraysInstanced")
@@ -588,6 +596,11 @@ public class GLSMRedirector {
.add("isFramebufferEnabled")
);
methodRedirects.put(EXTBlendFunc, RedirectMap.newMap().add("glBlendFuncSeparateEXT", "tryBlendFuncSeparate"));
methodRedirects.put(EXTSecondaryColor, RedirectMap.newMap()
.add("glSecondaryColor3bEXT", "glSecondaryColor3b")
.add("glSecondaryColor3fEXT", "glSecondaryColor3f")
.add("glSecondaryColor3dEXT", "glSecondaryColor3d")
.add("glSecondaryColor3ubEXT", "glSecondaryColor3ub"));
// ARB
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
@@ -632,6 +645,10 @@ public class GLSMRedirector {
.add("glQueryCounter")
.add("glGetQueryObjectui64")
);
methodRedirects.put(EXTTimerQuery, RedirectMap.newMap()
.add("glGetQueryObjectuEXT", "glGetQueryObjectui64")
.add("glGetQueryObjectui64EXT", "glGetQueryObjectui64")
);
glDescRedirects.put("org/lwjgl/opengl/GL32C glFenceSync(II)J", "glFenceSync");
glDescRedirects.put("org/lwjgl/opengl/GL32C glClientWaitSync(JIJ)I", "glClientWaitSync");
@@ -706,6 +723,7 @@ public class GLSMRedirector {
);
final var uniformBufferObject = RedirectMap.newMap()
.add("glBindBufferBase")
.add("glBindBufferRange")
.add("glGetUniformBlockIndex")
.add("glUniformBlockBinding");
methodRedirects.put(ARBUniformBufferObject, uniformBufferObject);
@@ -100,7 +100,7 @@ public final class GlslVulkanPreprocess {
return out;
}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs) {}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless) {}
public static Metadata collectEdits(String source, GLSLParser.Translation_unitContext root, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
@@ -216,7 +216,7 @@ public final class GlslVulkanPreprocess {
}
}, root);
if (needsSamplerless[0]) {
if (needsSamplerless[0] && source != null) {
final int at = versionDirectiveEnd(source);
if (at >= 0) edits.add(new Edit(at, at - 1, "\n" + SAMPLERLESS_EXTENSION));
}
@@ -234,7 +234,7 @@ public final class GlslVulkanPreprocess {
}
}
return new Metadata(bools, explicitInputs);
return new Metadata(bools, explicitInputs, needsSamplerless[0]);
}
public static void clearCache() {
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
import lombok.Setter;
public class AlphaStateStack extends AlphaState implements IStateStack<AlphaState> {
@@ -10,6 +12,8 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
protected int pointer;
@Setter private VanillaStateLayer<AlphaState> vanillaLayer;
public AlphaStateStack() {
stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -22,7 +26,7 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +35,19 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final AlphaState saved = stack[--pointer];
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
set(saved);
}
return this;
}
public AlphaState readEffective(AlphaState out) {
out.set(this);
VanillaStateLayer.capture(vanillaLayer, out);
return out;
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import lombok.Setter;
public class BlendStateStack extends BlendState implements IStateStack<BlendStateStack> {
@@ -10,6 +12,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
protected int pointer;
@Setter private VanillaStateLayer<BlendState> vanillaLayer;
public BlendStateStack() {
stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -22,7 +26,7 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +35,21 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final BlendState saved = stack[--pointer];
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
setExceptFunc(saved);
} else {
set(saved);
}
return this;
}
public BlendState readEffective(BlendState out) {
out.set(this);
VanillaStateLayer.capture(vanillaLayer, out);
return out;
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
import lombok.Setter;
/**
* A stack for boolean GL state with lazy copy-on-write optimization.
@@ -14,6 +16,7 @@ import com.gtnewhorizons.angelica.glsm.states.BooleanState;
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
protected final boolean[] stack;
@Setter private VanillaBooleanLayer vanillaLayer;
/**
* The depth at which state has been saved. Compared against GLStateManager.getAttribDepth()
@@ -56,7 +59,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
if (savedDepth >= stack.length) {
throw new IllegalStateException("Stack overflow size " + (savedDepth + 1) + " reached");
}
stack[savedDepth++] = enabled;
stack[savedDepth++] = vanillaValue();
return this;
}
@@ -65,12 +68,26 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
if (savedDepth == 0) {
throw new IllegalStateException("Stack underflow");
}
final boolean oldValue = stack[--savedDepth];
// Call setEnabledDirect to avoid triggering beforeModify during restore
setEnabledDirect(oldValue);
restore(stack[--savedDepth]);
return this;
}
public boolean isEffectivelyEnabled() {
return (vanillaLayer != null && vanillaLayer.isOverrideHeld()) ? vanillaLayer.getVanilla() : enabled;
}
private boolean vanillaValue() {
return isEffectivelyEnabled();
}
private void restore(boolean value) {
if (vanillaLayer != null && vanillaLayer.isOverrideHeld()) {
vanillaLayer.setVanilla(value);
} else {
setEnabledDirect(value);
}
}
@Override
public boolean isEmpty() {
return savedDepth == 0;
@@ -92,8 +109,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
public BooleanStateStack popDepth() {
// We're only called if we were modified, so savedDepth should match
if (savedDepth > 0) {
final boolean oldValue = stack[--savedDepth];
setEnabledDirect(oldValue);
restore(stack[--savedDepth]);
}
return this;
}
@@ -106,7 +122,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
public void beforeModify() {
final int globalDepth = GLStateManager.getAttribDepth();
if (savedDepth < globalDepth) {
stack[savedDepth++] = enabled;
stack[savedDepth++] = vanillaValue();
GLStateManager.registerModifiedState(this);
}
}
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
import lombok.Setter;
public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskStack> {
@@ -10,6 +12,8 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
protected int pointer;
@Setter private VanillaStateLayer<ColorMask> vanillaLayer;
public ColorMaskStack() {
stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -22,7 +26,7 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,7 +35,10 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final ColorMask saved = stack[--pointer];
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
set(saved);
}
return this;
}
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.DepthState;
import lombok.Setter;
public class DepthStateStack extends DepthState implements IStateStack<DepthStateStack> {
@@ -10,6 +12,10 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
protected int pointer;
@Setter private VanillaStateLayer<DepthState> vanillaLayer;
private final DepthState effective = new DepthState();
public DepthStateStack() {
stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -22,7 +28,7 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +37,21 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final DepthState saved = stack[--pointer];
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
setExceptMask(saved);
} else {
set(saved);
}
return this;
}
public boolean isEffectiveMaskEnabled() {
if (!VanillaStateLayer.isHeld(vanillaLayer)) return isEnabled();
vanillaLayer.readVanilla(effective);
return effective.isEnabled();
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -44,6 +44,7 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
if (GLSMHooks.TEXTURE_UNIT_STATE.hasListeners()) {
GLSMHooks.textureUnitStateEvent.unit = unitIndex;
GLSMHooks.textureUnitStateEvent.cap = glCap;
GLSMHooks.textureUnitStateEvent.enabled = enabled;
GLSMHooks.TEXTURE_UNIT_STATE.post(GLSMHooks.textureUnitStateEvent);
}
@@ -55,6 +55,15 @@ public class BlendState implements ISettableState<BlendState> {
return this;
}
public void setExceptFunc(BlendState state) {
final int sRgb = srcRgb, dRgb = dstRgb, sAlpha = srcAlpha, dAlpha = dstAlpha;
set(state);
this.srcRgb = sRgb;
this.dstRgb = dRgb;
this.srcAlpha = sAlpha;
this.dstAlpha = dAlpha;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
@@ -18,6 +18,12 @@ public class DepthState implements ISettableState<DepthState> {
return this;
}
public void setExceptMask(DepthState state) {
final boolean mask = enabled;
set(state);
this.enabled = mask;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
@@ -4,12 +4,16 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
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.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
@@ -22,6 +26,7 @@ import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static org.joml.Math.clamp;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
@@ -36,6 +41,7 @@ public class TessellatorStreamingDrawer {
private static final Tracy.ZoneId Z_SDL_STREAM_DRAW = Tracy.zoneId("sdlStreamDraw", Tracy.COLOR_FFP);
private static final Logger LOGGER = LogManager.getLogger("TessellatorStreamingDrawer");
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE; // 16
private static PersistentStreamingBuffer persistentBuffer;
@@ -44,6 +50,12 @@ public class TessellatorStreamingDrawer {
private static final int[] persistentVAOs = new int[FORMAT_COUNT];
private static final int[] orphanVAOs = new int[FORMAT_COUNT];
private static final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
private static final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
private static ByteBuffer extScratch;
private static long extScratchAddress;
private static int extScratchCapacity;
private static ByteBuffer repackBuffer;
private static long repackAddress;
private static int repackCapacity;
@@ -108,7 +120,7 @@ public class TessellatorStreamingDrawer {
final int defaultBrightness;
if (!tess.hasBrightness) {
defaultBrightness = ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF);
defaultBrightness = GLSMConfig.packedLastBrightness();
} else {
defaultBrightness = 0;
}
@@ -153,7 +165,25 @@ public class TessellatorStreamingDrawer {
repackBuffer.position(0);
repackBuffer.limit((int)(writePtr - repackAddress));
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
final int extPrim = extHandler == null ? 0 : ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount);
final boolean wantsExt = extHandler != null && extHandler.wantsExtended();
final boolean doExt = extPrim != 0 && tess.hasTexture && wantsExt;
if (doExt) {
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, effectiveFlags, format, combinedStride, tess.drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} else {
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
}
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
@@ -184,6 +214,7 @@ public class TessellatorStreamingDrawer {
final ByteBuffer buffer = dt.getWriteBuffer();
final int vertexSize = format.getVertexSize();
if (tryExtendedPacked(buffer, flags, format, vertexSize, drawMode, vertexCount)) return;
uploadAndDraw(buffer, flags, format, vertexSize, drawMode, vertexCount);
}
@@ -197,9 +228,37 @@ public class TessellatorStreamingDrawer {
public static void drawPacked(ByteBuffer packedData, int drawMode, int flags, int vertexCount) {
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
final int vertexSize = format.getVertexSize();
if (tryExtendedPacked(packedData, flags, format, vertexSize, drawMode, vertexCount)) return;
uploadAndDraw(packedData, flags, format, vertexSize, drawMode, vertexCount);
}
private static boolean tryExtendedPacked(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) {
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
if (extHandler == null || !extHandler.wantsExtended()) return false;
final int extPrim = ImmediateExtendedAttribHandler.extPrimVerts(drawMode, vertexCount);
if (extPrim == 0 || !format.hasTexture()) return false;
final long srcBase = memAddress0(packed) + packed.position();
final int texOffset = ImmediateExtendedAttribHandler.texOffset(format);
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(srcBase, extScratchAddress, vertexCount, vertexSize, combinedStride);
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
return true;
}
private static void interleaveBase(long src, long dst, int vertexCount, int vertexSize, int combinedStride) {
for (int i = 0; i < vertexCount; i++) {
memCopy(src + (long) i * vertexSize, dst + (long) i * combinedStride, vertexSize);
}
}
private static String cachedDebugInfo = "Stream: not initialized";
private static long lastDebugUpdateNanos;
private static final long DEBUG_UPDATE_INTERVAL_NS = 500_000_000L; // 500ms
@@ -294,10 +353,89 @@ public class TessellatorStreamingDrawer {
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
FfpExtendedAttribs.beginInternalDraw();
try {
if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
} else {
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
}
} finally {
FfpExtendedAttribs.endInternalDraw();
}
}
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
ensureVAO(flags, format);
ensureExtendedVAOs(flags, format);
int firstVertex = -1;
if (persistentBuffer != null) {
firstVertex = persistentBuffer.upload(combined, combinedStride);
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
} else {
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
orphanBuffers[flags].upload(combined);
firstVertex = 0;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
}
private static void ensureExtendedVAOs(int flags, VertexFormat format) {
if (extendedOrphanVAOs[flags] == 0) {
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
setupExtendedBufferState(format);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
if (persistentBuffer != null && extendedPersistentVAOs[flags] == 0) {
extendedPersistentVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
setupExtendedBufferState(format);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
}
private static void setupExtendedBufferState(VertexFormat format) {
final int combinedStride = format.getVertexSize() + ImmediateExtendedAttribHandler.EXT_STRIDE;
long offset = 0;
for (final VertexFormatElement element : format.elementsArray) {
element.setupBufferState(offset, combinedStride);
offset += element.getByteSize();
}
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
}
private static void ensureExtScratch(int requiredBytes) {
if (extScratch != null && requiredBytes <= extScratchCapacity) return;
int newCapacity = Math.max(0x4000, extScratchCapacity);
while (newCapacity < requiredBytes) {
newCapacity *= 2;
}
if (extScratch != null) memFree(extScratch);
extScratch = memAlloc(newCapacity);
extScratchAddress = memAddress0(extScratch);
extScratchCapacity = newCapacity;
}
private static void shrinkExtScratchIfOversized(int lastRequiredBytes) {
if (extScratchCapacity > 0x20000 && lastRequiredBytes < (extScratchCapacity >> 3)) {
memFree(extScratch);
extScratchCapacity = 0x4000;
extScratch = memAlloc(extScratchCapacity);
extScratchAddress = memAddress0(extScratch);
}
}
@@ -358,8 +496,16 @@ public class TessellatorStreamingDrawer {
for (int i = 0; i < FORMAT_COUNT; i++) {
if (persistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(persistentVAOs[i]); persistentVAOs[i] = 0; }
if (orphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(orphanVAOs[i]); orphanVAOs[i] = 0; }
if (extendedPersistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedPersistentVAOs[i]); extendedPersistentVAOs[i] = 0; }
if (extendedOrphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedOrphanVAOs[i]); extendedOrphanVAOs[i] = 0; }
if (orphanBuffers[i] != null) { orphanBuffers[i].destroy(); orphanBuffers[i] = null; }
}
if (extScratch != null) {
memFree(extScratch);
extScratch = null;
extScratchAddress = 0;
extScratchCapacity = 0;
}
if (persistentBuffer != null) {
persistentBuffer.destroy();
persistentBuffer = null;
@@ -36,6 +36,12 @@ public class TextureInfo {
@Getter @Setter protected boolean generateMipmap = false;
@Getter protected int uploadGeneration;
void bumpUploadGeneration() {
uploadGeneration++;
}
protected TextureInfo(int id) {
this.id = id;
}
@@ -9,6 +9,9 @@ import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import java.nio.Buffer;
import java.util.concurrent.locks.ReentrantLock;
@@ -53,6 +56,7 @@ public class TextureInfoCache {
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
}
@@ -65,9 +69,29 @@ public class TextureInfoCache {
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
}
public void onTexStorage2D(int target, int internalformat, int width, int height) {
if (!isTrackedTarget(target)) return;
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
public void onTexSubImage2D(int target, int level) {
if (!isTrackedTarget(target) || level != 0) return;
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.bumpUploadGeneration();
}
public static boolean isGenericCompressedInternalFormat(int internalFormat) {
return switch (internalFormat) {
case GL30.GL_COMPRESSED_RED,
@@ -80,6 +104,22 @@ public class TextureInfoCache {
};
}
public static boolean isProxyTarget(int target) {
return switch (target) {
case GL11.GL_PROXY_TEXTURE_1D,
GL11.GL_PROXY_TEXTURE_2D,
GL12.GL_PROXY_TEXTURE_3D,
GL13.GL_PROXY_TEXTURE_CUBE_MAP,
GL30.GL_PROXY_TEXTURE_1D_ARRAY,
GL30.GL_PROXY_TEXTURE_2D_ARRAY,
GL31.GL_PROXY_TEXTURE_RECTANGLE,
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE,
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY,
GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> true;
default -> false;
};
}
private static boolean isTrackedTarget(int target) {
return target == GL11.GL_TEXTURE_2D
|| target == GL11.GL_TEXTURE_1D
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.glsm.texture;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
public final class TextureTargets {
private TextureTargets() {}
public static boolean isProxyTarget(int target) {
return switch (target) {
case GL11.GL_PROXY_TEXTURE_1D,
GL11.GL_PROXY_TEXTURE_2D,
GL12.GL_PROXY_TEXTURE_3D,
GL13.GL_PROXY_TEXTURE_CUBE_MAP,
GL30.GL_PROXY_TEXTURE_1D_ARRAY,
GL30.GL_PROXY_TEXTURE_2D_ARRAY,
GL31.GL_PROXY_TEXTURE_RECTANGLE,
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE,
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY,
GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> true;
default -> false;
};
}
}
@@ -4,8 +4,11 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import java.nio.FloatBuffer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -22,6 +25,7 @@ public class GLSM_CoreRemovedCaps_GLTest {
cap("GL_POLYGON_STIPPLE", GL11.GL_POLYGON_STIPPLE),
cap("GL_INDEX_LOGIC_OP", GL11.GL_INDEX_LOGIC_OP),
cap("GL_AUTO_NORMAL", GL11.GL_AUTO_NORMAL),
cap("GL_COLOR_SUM", GL14.GL_COLOR_SUM),
cap("GL_MAP1_COLOR_4", GL11.GL_MAP1_COLOR_4),
cap("GL_MAP1_INDEX", GL11.GL_MAP1_INDEX),
cap("GL_MAP1_NORMAL", GL11.GL_MAP1_NORMAL),
@@ -46,6 +50,14 @@ public class GLSM_CoreRemovedCaps_GLTest {
return Arguments.of(name, value);
}
private static void assertSecondaryColor(float red, float green, float blue) {
final FloatBuffer params = BufferUtils.createFloatBuffer(4);
GLStateManager.glGetFloat(GL14.GL_CURRENT_SECONDARY_COLOR, params);
assertEquals(red, params.get(0), "red must round-trip through the cache");
assertEquals(green, params.get(1), "green must round-trip through the cache");
assertEquals(blue, params.get(2), "blue must round-trip through the cache");
}
private static void assertNoGlError(String what) {
final int error = GL11.glGetError();
assertEquals(GL11.GL_NO_ERROR, error, () -> what + " raised 0x" + Integer.toHexString(error));
@@ -83,6 +95,71 @@ public class GLSM_CoreRemovedCaps_GLTest {
}
}
@Test
void secondaryColorIsCacheOnly() {
try {
// Calling through to the driver here aborts the JVM in a core profile - the color sum is emulated by the FFP shaders
GLStateManager.glSecondaryColor3f(0.25F, 0.5F, 0.75F);
assertNoGlError("glSecondaryColor3f");
assertSecondaryColor(0.25F, 0.5F, 0.75F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@Test
void secondaryColorIsRestoredByPopAttrib() {
try {
GLStateManager.glSecondaryColor3f(0.125F, 0.25F, 0.375F);
GLStateManager.glPushAttrib(GL11.GL_CURRENT_BIT);
GLStateManager.glSecondaryColor3f(1.0F, 1.0F, 1.0F);
GLStateManager.glPopAttrib();
assertNoGlError("glPushAttrib/glSecondaryColor3f/glPopAttrib");
assertSecondaryColor(0.125F, 0.25F, 0.375F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@Test
void colorSumIsRestoredByFogBitPopAttrib() {
try {
GLStateManager.glPushAttrib(GL11.GL_FOG_BIT);
GLStateManager.glEnable(GL14.GL_COLOR_SUM);
GLStateManager.glPopAttrib();
assertNoGlError("glPushAttrib(GL_FOG_BIT)/glEnable(GL_COLOR_SUM)/glPopAttrib");
assertFalse(GLStateManager.glIsEnabled(GL14.GL_COLOR_SUM));
} finally {
GLStateManager.glDisable(GL14.GL_COLOR_SUM);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@Test
void secondaryColorReplaysFromDisplayList() {
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glSecondaryColor3f(0.5F, 0.625F, 0.75F);
GLStateManager.glEndList();
assertNoGlError("glSecondaryColor3f display list compile");
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
GLStateManager.glCallList(list);
assertNoGlError("glSecondaryColor3f display list replay");
assertSecondaryColor(0.5F, 0.625F, 0.75F);
} finally {
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@Test
void displayListReplayIsCacheOnly() {
final int list = GLStateManager.glGenLists(1);
@@ -42,6 +42,28 @@ public class GLSM_TextureBindingTargetCache_UnitTest {
}
}
@Test
void proxyTargetBindIsNeitherForwardedNorCached() {
drainErrors();
final int t2d = GL11.glGenTextures();
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE5);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
GLStateManager.glBindTexture(GL11.GL_PROXY_TEXTURE_2D, 0);
GLStateManager.glBindTexture(GL12.GL_PROXY_TEXTURE_3D, t2d);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "a proxy target must never reach the driver");
assertEquals(t2d, GLStateManager.getBoundTextureForServerState(5), "a proxy bind must not disturb the cache");
assertEquals(t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D));
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(t2d);
drainErrors();
}
}
@Test
void popAttribRestoresRecordedTarget() {
drainErrors();
@@ -0,0 +1,86 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
@GLCoreTest
public class GLSM_TextureUploadGeneration_GLTest {
private static int allocate(int size) {
final int texId = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(size * size * 4));
return texId;
}
@Test
void aLevelBeyondTheShiftWidthClampsToOne() {
final int texId = allocate(64);
try {
assertEquals(64, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 6, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 31, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 32, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 64, GL11.GL_TEXTURE_HEIGHT));
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(texId);
}
}
@Test
void anUploadLeavesANonZeroGeneration() {
final int texId = allocate(4);
try {
assertNotEquals(0, TextureInfoCache.INSTANCE.getInfo(texId).getUploadGeneration());
} finally {
GLStateManager.glDeleteTextures(texId);
}
}
@Test
void recreatingATextureAtAReusedIdYieldsAFreshInfo() {
final int texId = allocate(4);
try {
final TextureInfo first = TextureInfoCache.INSTANCE.getInfo(texId);
final int firstGeneration = first.getUploadGeneration();
TextureInfoCache.INSTANCE.onDeleteTexture(texId);
final ByteBuffer pixels = BufferUtils.createByteBuffer(4 * 4 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 4, 4, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
final TextureInfo second = TextureInfoCache.INSTANCE.getInfo(texId);
assertNotSame(first, second, "a deleted id drops its entry, so the replacement must be a distinct instance");
assertEquals(firstGeneration, second.getUploadGeneration(), "generations alias across a delete/recreate cycle, which is exactly why identity has to carry the distinction");
} finally {
GLStateManager.glDeleteTextures(texId);
}
}
@Test
void anInPlaceRewriteBumpsTheGeneration() {
final int texId = allocate(4);
try {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
final int afterAlloc = info.getUploadGeneration();
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(2 * 2 * 4));
assertNotEquals(afterAlloc, info.getUploadGeneration(), "content changed, so anything caching the pixels is stale");
final int afterSub = info.getUploadGeneration();
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 1, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(4));
assertEquals(afterSub, info.getUploadGeneration(), "only level 0 is tracked");
} finally {
GLStateManager.glDeleteTextures(texId);
}
}
}
@@ -21,6 +21,18 @@ class GLVersionParseTest {
assertEquals(-1, RenderSystem.parseMajorMinor("OpenGL ES 3.2"));
}
@Test
void detectsLtwWrapper() {
assertTrue(LTWWorkaround.isLtwVersionString("3.3 OpenLTW (Built on: May 21 2026/12:38:14)"));
assertFalse(LTWWorkaround.isLtwVersionString(null));
assertFalse(LTWWorkaround.isLtwVersionString(""));
assertFalse(LTWWorkaround.isLtwVersionString("4.6.0 NVIDIA 551.61"));
assertFalse(LTWWorkaround.isLtwVersionString("4.6 (Core Profile) Mesa 24.0.3"));
assertFalse(LTWWorkaround.isLtwVersionString("4.1 Metal - 88"));
assertFalse(LTWWorkaround.isLtwVersionString("OpenGL ES 3.2 ANGLE (Vulkan)"));
assertFalse(LTWWorkaround.isLtwVersionString("2.1 gl4es wrapper"));
}
@Test
void isContextValid() {
assertTrue(RenderSystem.isContextValid(4, 4, 46, true));
@@ -0,0 +1,67 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.taumc.glsl.grammar.GLSLParser;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ReservedWordRestoreTest {
private static void assertTreeRestoreMatchesStringRestore(String label, String renamedSource) {
final GLSLParser.Translation_unitContext treeA = GlslTransformUtils.parseFullQuiet(renamedSource);
final String legacy = GlslTransformUtils.restoreReservedWords(GlslTransformUtils.getFormattedShader(treeA, ""));
final GLSLParser.Translation_unitContext treeB = GlslTransformUtils.parseFullQuiet(renamedSource);
GlslTransformUtils.restoreReservedWordsInTree(treeB);
final String inTree = GlslTransformUtils.getFormattedShader(treeB, "");
assertEquals(legacy, inTree, "in-tree restore diverged from string restore for " + label);
}
@Test
void allThreeRenamesRestoreIdentically() {
final String src = "float angelica_renamed_sample = 1.0;\n"
+ "uniform sampler2D angelica_renamed_sampler;\n"
+ "vec4 angelica_renamed_texture = vec4(0.0);\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture + vec4(angelica_renamed_sample);\n"
+ "}\n";
assertTreeRestoreMatchesStringRestore("all-three", src);
}
@Test
void renamedNewStaysRenamed() {
final String src = "float angelica_renamed_new = 2.0;\n"
+ "void main() { float x = angelica_renamed_new; }\n";
assertTreeRestoreMatchesStringRestore("new-identifier", src);
final GLSLParser.Translation_unitContext tree = GlslTransformUtils.parseFullQuiet(src);
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String out = GlslTransformUtils.getFormattedShader(tree, "");
assertTrue(out.contains("angelica_renamed_new"), "new is reserved in every GLSL version and must stay renamed");
}
@Test
void renamedTextureFunctionCallRestores() {
final String src = "uniform sampler2D tex;\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture2D(tex, vec2(0.5));\n"
+ "}\n";
assertTreeRestoreMatchesStringRestore("texture2D-call", src);
final GLSLParser.Translation_unitContext tree = GlslTransformUtils.parseFullQuiet(src);
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String out = GlslTransformUtils.getFormattedShader(tree, "");
assertTrue(out.contains("texture2D"), "suffixed rename must strip only the prefix");
assertFalse(out.contains("angelica_renamed_"), "no renamed remnants expected");
}
@Test
void untouchedSourcePrintsIdentically() {
final String src = "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(1.0); }\n";
assertTreeRestoreMatchesStringRestore("no-renames", src);
}
}
@@ -0,0 +1,91 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import java.util.function.Predicate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class BackendPolicyTest {
@Test
void onlyImmediateCanTear() {
assertTrue(VSyncMode.ON.tearFree());
assertTrue(VSyncMode.MAILBOX.tearFree());
assertFalse(VSyncMode.OFF.tearFree());
}
private static final Predicate<VSyncMode> NO_MAILBOX = mode -> mode != VSyncMode.MAILBOX;
private static final Predicate<VSyncMode> EVERYTHING = mode -> true;
@Test
void mailboxIsTakenWhenTheBackendOffersIt() {
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(null, EVERYTHING));
}
@Test
void vsyncIsTheFallbackWhenMailboxIsUnsupported() {
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(null, NO_MAILBOX));
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.MAILBOX, NO_MAILBOX));
}
@Test
void anExplicitSupportedPreferenceWins() {
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.ON, EVERYTHING));
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(VSyncMode.MAILBOX, EVERYTHING));
}
@Test
void offIsNeverATearFreePreference() {
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(VSyncMode.OFF, EVERYTHING));
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.OFF, NO_MAILBOX));
}
@Test
void periodPrefersTheExactRational() {
assertEquals(16_683_333L, RenderBackend.periodFromRational(60000, 1001, 0.0f));
assertEquals(6_944_444L, RenderBackend.periodFromRational(144, 1, 0.0f));
}
@Test
void periodFallsBackToTheFloatRate() {
assertEquals(16_683_350L, RenderBackend.periodFromRational(0, 0, 59.94f));
assertEquals(16_666_667L, RenderBackend.periodFromRational(0, 0, 60.0f));
}
@Test
void periodIsZeroWhenUnknown() {
assertEquals(0L, RenderBackend.periodFromRational(0, 0, 0.0f));
assertEquals(0L, RenderBackend.periodFromRational(-1, -1, -1.0f));
assertEquals(0L, RenderBackend.periodFromRational(144, 0, 0.0f));
}
@Test
void integerNtscRatesAreAdjusted() {
assertEquals(16_683_333L, RenderBackend.periodFromIntegerHz(59));
assertEquals(6_951_389L, RenderBackend.periodFromIntegerHz(143));
assertEquals(16_666_667L, RenderBackend.periodFromIntegerHz(60));
assertEquals(6_944_444L, RenderBackend.periodFromIntegerHz(144));
assertEquals(0L, RenderBackend.periodFromIntegerHz(0));
assertEquals(0L, RenderBackend.periodFromIntegerHz(-60));
assertEquals(60, RenderBackend.hzFromPeriod(RenderBackend.periodFromIntegerHz(59)));
}
@Test
void hzFromPeriodRoundsToNearest() {
assertEquals(60, RenderBackend.hzFromPeriod(16_666_667L));
assertEquals(144, RenderBackend.hzFromPeriod(6_944_444L));
assertEquals(0, RenderBackend.hzFromPeriod(0L));
assertEquals(0, RenderBackend.hzFromPeriod(-1L));
}
@Test
void plausiblePeriodFilterKeeps20To1000Hz() {
assertEquals(0L, RenderBackend.plausiblePeriodNanos(999_000L));
assertEquals(1_000_000L, RenderBackend.plausiblePeriodNanos(1_000_000L));
assertEquals(50_000_000L, RenderBackend.plausiblePeriodNanos(50_000_000L));
assertEquals(0L, RenderBackend.plausiblePeriodNanos(51_000_000L));
}
}
@@ -0,0 +1,72 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class GateRecorderTest {
@Test
void theLongestBlockWithinAFrameWinsRegardlessOfOrder() {
final RenderBackend.GateRecorder longestFirst = new RenderBackend.GateRecorder();
longestFirst.recordGate(5_000_000L, 100L);
longestFirst.recordGate(1_000_000L, 200L);
final RenderBackend.GateSample out = new RenderBackend.GateSample();
longestFirst.readGateSample(out);
assertEquals(5_000_000L, out.durationNanos);
assertEquals(100L, out.endNanos);
final RenderBackend.GateRecorder shortestFirst = new RenderBackend.GateRecorder();
shortestFirst.recordGate(1_000_000L, 10L);
shortestFirst.recordGate(4_000_000L, 20L);
shortestFirst.readGateSample(out);
assertEquals(4_000_000L, out.durationNanos);
assertEquals(20L, out.endNanos);
}
@Test
void aReadConsumesTheSample() {
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
recorder.recordGate(2_000_000L, 50L);
final RenderBackend.GateSample out = new RenderBackend.GateSample();
recorder.readGateSample(out);
recorder.readGateSample(out);
assertEquals(0L, out.endNanos);
assertEquals(0L, out.durationNanos);
}
@Test
void gpuWaitAccumulatesAndResetsOnRead() {
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
recorder.recordGate(1_000_000L, 10L);
recorder.recordGpuWait(300L);
recorder.recordGpuWait(700L);
final RenderBackend.GateSample out = new RenderBackend.GateSample();
recorder.readGateSample(out);
assertEquals(1_000L, out.gpuWaitNanos);
recorder.recordGate(1_000_000L, 11L);
recorder.readGateSample(out);
assertEquals(0L, out.gpuWaitNanos);
}
@Test
void gpuWaitIsDrainedOnASampleFreeRead() {
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
recorder.recordGpuWait(450L);
final RenderBackend.GateSample out = new RenderBackend.GateSample();
recorder.readGateSample(out);
assertEquals(0L, out.endNanos);
assertEquals(450L, out.gpuWaitNanos);
recorder.readGateSample(out);
assertEquals(0L, out.gpuWaitNanos);
}
}
@@ -0,0 +1,153 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Timeout(10)
class MainThreadPumpTest {
private static final class DeferredExecutor implements Executor {
final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();
@Override
public void execute(Runnable command) {
tasks.add(command);
}
void runNext() {
tasks.removeFirst().run();
}
void awaitTask() {
while (tasks.isEmpty()) {
Thread.onSpinWait();
}
}
}
private static final class InlineExecutor implements Executor {
final List<Runnable> submitted = Collections.synchronizedList(new ArrayList<>());
@Override
public void execute(Runnable command) {
submitted.add(command);
command.run();
}
}
private static void awaitWaiting(Thread t) {
while (t.getState() != Thread.State.WAITING && t.getState() != Thread.State.TIMED_WAITING) {
assertTrue(t.isAlive(), "thread died instead of blocking");
Thread.onSpinWait();
}
}
private static MainThreadPump pump(Executor executor, Runnable pumpBody, Runnable pollBody) {
return new MainThreadPump(executor, () -> false, pumpBody, pollBody);
}
@Test
void repeatedPumpsSubmitTheSameRunnable() {
final InlineExecutor executor = new InlineExecutor();
final AtomicInteger pumps = new AtomicInteger();
final AtomicInteger polls = new AtomicInteger();
final MainThreadPump p = pump(executor, pumps::incrementAndGet, polls::incrementAndGet);
for (int i = 0; i < 16; i++) {
p.pumpMessages();
}
assertEquals(16, executor.submitted.size());
assertEquals(16, pumps.get());
assertEquals(16, polls.get());
final Runnable first = executor.submitted.get(0);
for (Runnable r : executor.submitted) {
assertSame(first, r, "pump must not allocate a new task per call");
}
}
@Test
void pumpBlocksUntilTheMainThreadRunsTheTask() throws Exception {
final DeferredExecutor executor = new DeferredExecutor();
final AtomicInteger polls = new AtomicInteger();
final MainThreadPump p = pump(executor, () -> {}, polls::incrementAndGet);
final Thread caller = new Thread(p::pumpMessages, "pump-caller");
caller.start();
awaitWaiting(caller);
executor.awaitTask();
assertEquals(1, executor.tasks.size());
assertEquals(0, polls.get());
executor.runNext();
caller.join();
assertEquals(1, polls.get());
}
@Test
void aThrowingBodyPropagatesToTheCallerAndDoesNotLeaveItParked() {
final InlineExecutor executor = new InlineExecutor();
final AtomicInteger polls = new AtomicInteger();
final AtomicInteger pumps = new AtomicInteger();
final MainThreadPump p = pump(executor, () -> {
if (pumps.incrementAndGet() == 1) throw new IllegalStateException("boom");
}, polls::incrementAndGet);
final IllegalStateException thrown = assertThrows(IllegalStateException.class, p::pumpMessages);
assertEquals("boom", thrown.getMessage());
assertEquals(0, polls.get(), "poll must not run when the pump body failed");
p.pumpMessages();
assertEquals(1, polls.get(), "a failed pump must not poison the next one");
}
@Test
void mainThreadCallersRunInlineWithoutTheExecutor() {
final InlineExecutor executor = new InlineExecutor();
final AtomicInteger pumps = new AtomicInteger();
final AtomicInteger polls = new AtomicInteger();
final MainThreadPump p = new MainThreadPump(executor, () -> true, pumps::incrementAndGet,
polls::incrementAndGet);
p.pumpMessages();
assertTrue(executor.submitted.isEmpty(), "the main thread must not hop through the executor");
assertEquals(1, pumps.get());
assertEquals(1, polls.get());
}
@Test
void anInterruptedCallerCompletesAndKeepsItsInterruptFlag() throws Exception {
final DeferredExecutor executor = new DeferredExecutor();
final MainThreadPump p = pump(executor, () -> {}, () -> {});
final AtomicInteger stillInterrupted = new AtomicInteger(-1);
final Thread caller = new Thread(() -> {
p.pumpMessages();
stillInterrupted.set(Thread.currentThread().isInterrupted() ? 1 : 0);
}, "pump-interrupted-caller");
caller.start();
awaitWaiting(caller);
executor.awaitTask();
caller.interrupt();
assertFalse(executor.tasks.isEmpty());
executor.runNext();
caller.join();
assertEquals(1, stillInterrupted.get());
}
}
@@ -0,0 +1,241 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Vector3f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
class FFPVertexLightingGLTest {
private static final int POSITION_LOC = VertexFormatElement.Usage.POSITION.getAttributeLocation();
private static final int COLOR_LOC = VertexFormatElement.Usage.COLOR.getAttributeLocation();
private static final int STRIDE = 16;
private static final int VERTEX_COLOR = 0xFF808080;
private static final float VERTEX_CHANNEL = 0x80 / 255.0f;
private static final Vector3f IDENTITY_FACTOR = new Vector3f(1.0f, 1.0f, 1.0f);
private final Vector3f factor = new Vector3f();
private int vao;
private int vbo;
@BeforeEach
void setUpState() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
GLStateManager.disableBlend();
GLStateManager.disableTexture();
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
standardItemLighting();
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 0.0f);
}
@AfterEach
void cleanup() {
GLStateManager.glDisable(GL11.GL_NORMALIZE);
GLStateManager.glDisable(GL12.GL_RESCALE_NORMAL);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glDisable(GL11.GL_LIGHTING);
GLStateManager.glDisable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_LIGHT1);
GLStateManager.glDisable(GL11.GL_LIGHT2);
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
GLStateManager.glBindVertexArray(0);
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
private static void standardItemLighting() {
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEnable(GL11.GL_LIGHT0);
GLStateManager.glEnable(GL11.GL_LIGHT1);
GLStateManager.glEnable(GL11.GL_COLOR_MATERIAL);
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.4f, 0.4f, 0.4f, 1.0f));
light(GL11.GL_LIGHT0, 0.2f, 1.0f, -0.7f);
light(GL11.GL_LIGHT1, -0.2f, 1.0f, 0.7f);
}
private static void light(int light, float x, float y, float z) {
GLStateManager.glLight(light, GL11.GL_POSITION, floats(x, y, z, 0.0f));
GLStateManager.glLight(light, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
GLStateManager.glLight(light, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glLight(light, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
}
private static String gateState() {
final var material = GLStateManager.getFrontMaterial();
return "lighting=" + GLStateManager.getLightingState().isEnabled()
+ " colorMaterial=" + GLStateManager.getColorMaterial().isEnabled()
+ " cmParam=0x" + Integer.toHexString(GLStateManager.getColorMaterialParameter().getValue())
+ " emission=" + material.emission
+ " specular=" + material.specular
+ " shininess=" + material.shininess
+ " lmAmbient=" + GLStateManager.getLightModel().ambient
+ " light0=" + GLStateManager.getLightStates()[0].isEnabled()
+ " light1=" + GLStateManager.getLightStates()[1].isEnabled();
}
private static FloatBuffer floats(float... values) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(values.length);
buf.put(values).flip();
return buf;
}
private void buildTriangle() {
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final ByteBuffer data = BufferUtils.createByteBuffer(3 * STRIDE);
putVertex(data, -0.9f, -0.9f);
putVertex(data, 0.9f, -0.9f);
putVertex(data, 0.0f, 0.9f);
data.flip();
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
GLStateManager.glEnableVertexAttribArray(POSITION_LOC);
GLStateManager.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
GLStateManager.glEnableVertexAttribArray(COLOR_LOC);
GLStateManager.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
VAOManager.setCurrentVertexFlags(COLOR_BIT);
}
private static void putVertex(ByteBuffer buf, float x, float y) {
final int base = buf.position();
buf.putFloat(base, x).putFloat(base + 4, y).putFloat(base + 8, 0.0f);
buf.putInt(base + 12, VERTEX_COLOR);
buf.position(base + STRIDE);
}
private float[] drawAndReadCenter() {
buildTriangle();
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
sm.activate();
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "FFP draw must not raise a GL error");
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
GL11.glReadPixels(400, 200, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
return new float[] { (pixel.get(0) & 0xFF) / 255.0f, (pixel.get(1) & 0xFF) / 255.0f, (pixel.get(2) & 0xFF) / 255.0f };
}
private void assertSolverMatchesShader(String context) {
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), context + ": expected a representable factor");
final float[] actual = drawAndReadCenter();
final float[] expected = { Math.min(1.0f, VERTEX_CHANNEL * factor.x), Math.min(1.0f, VERTEX_CHANNEL * factor.y), Math.min(1.0f, VERTEX_CHANNEL * factor.z) };
for (int c = 0; c < 3; c++) {
assertEquals(expected[c], actual[c], 2.0f / 255.0f, context + ": channel " + c + " solver=" + expected[c] + " shader=" + actual[c] + " factor=" + factor);
}
}
@Test
void faceOnNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
assertSolverMatchesShader("face-on normal");
}
@Test
void signNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.0f, 0.0f, -0.010416667f);
assertSolverMatchesShader("sign normal");
assertTrue(factor.x < 0.45f, "a 0.0104-long normal must stay at the 0.4 light model ambient floor rather than pick up 0.6 per light, got " + factor.x);
}
@Test
void tiltedNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.3f, 0.8f, 0.5f);
assertSolverMatchesShader("tilted normal");
}
@Test
void rescaleNormalMatchesGeneratedShader() {
GLStateManager.glEnable(GL12.GL_RESCALE_NORMAL);
GLStateManager.glScalef(1.0f, 2.0f, 1.0f);
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
assertSolverMatchesShader("rescale normal");
}
@Test
void normalizeMatchesGeneratedShader() {
GLStateManager.glEnable(GL11.GL_NORMALIZE);
GLStateManager.glScalef(1.0f, 2.0f, 1.0f);
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
assertSolverMatchesShader("normalize");
assertTrue(factor.x > 1.2f, "normalize restores the unit normal, which must pick up more diffuse than rescale's 0.5-long one, got " + factor.x);
}
@Test
void materialEmissionIsNotRepresentable() {
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION, floats(0.1f, 0.1f, 0.1f, 1.0f));
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "emission is additive, so the lit colour is not vertexColor * factor");
assertEquals(IDENTITY_FACTOR, factor, "a rejected solve must leave an identity factor");
}
@Test
void specularContributionIsNotRepresentable() {
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 16.0f);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "specular is additive, so the lit colour is not vertexColor * factor");
}
@Test
void nonAmbientAndDiffuseColorMaterialIsNotRepresentable() {
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION);
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "only AMBIENT_AND_DIFFUSE makes the lit colour scale with the vertex colour");
}
@Test
void lightsBeyondTheFfpCountAreIgnored() {
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), FFPVertexLightingGLTest::gateState);
final Vector3f twoLights = new Vector3f(factor);
light(GL11.GL_LIGHT2, 0.0f, 0.0f, 1.0f);
GLStateManager.glEnable(GL11.GL_LIGHT2);
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), FFPVertexLightingGLTest::gateState);
assertEquals(twoLights, factor, "VertexShaderGenerator emits only GL_LIGHT0/GL_LIGHT1, so text must not see GL_LIGHT2 either");
}
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class UniformsRescaleTest {
private static float rescaleFactorFor(float sx, float sy, float sz) {
final Matrix3f normalMatrix = new Matrix3f();
new Matrix4f().rotateY(0.7f).scale(sx, sy, sz).normal(normalMatrix);
return Uniforms.rescaleFactor(normalMatrix, new Vector3f());
}
@Test
void rescaleFactorUsesTheInverseModelviewBottomRow() {
assertEquals(8.0f, rescaleFactorFor(4.0f, 2.0f, 8.0f), 1.0e-5f, "wrong normal matrix column");
}
@Test
void uniformScaleIsRowIndependent() {
assertEquals(2.0f, rescaleFactorFor(2.0f, 2.0f, 2.0f), 1.0e-5f);
}
@Test
void singularMatrixFallsBackToUnitScale() {
assertEquals(1.0f, rescaleFactorFor(1.0f, 1.0f, 0.0f), 1.0e-5f, "a degenerate normal matrix must not divide by zero");
}
}
@@ -0,0 +1,15 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class GLSMInitConfigDefaultsTest {
@Test
void emptyBuilderLeavesDsaToTheProbe() {
assertTrue(GLSMInitConfig.builder().build().isDSAEnabled());
assertFalse(GLSMInitConfig.builder().enableDSA(false).build().isDSAEnabled());
}
}
@@ -0,0 +1,127 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ModRedirectorTest {
private static final String EXTRA_EXCLUSION = "com.example.excluded";
private static ClassNode emptyClass() {
final ClassNode cn = new ClassNode();
cn.version = Opcodes.V1_8;
cn.access = Opcodes.ACC_PUBLIC;
cn.name = "com/example/TestClass";
cn.superName = "java/lang/Object";
cn.methods = new ArrayList<>();
final MethodNode mn = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "test", "()V", null, null);
mn.instructions.add(new InsnNode(Opcodes.RETURN));
cn.methods.add(mn);
return cn;
}
private static byte[] bytesWithoutGlCall() {
return toBytes(emptyClass());
}
private static byte[] bytesWithGlCall() {
final ClassNode cn = emptyClass();
final MethodNode mn = cn.methods.get(0);
mn.instructions.clear();
mn.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/lwjgl/opengl/GL11", "glPopMatrix", "()V", false));
mn.instructions.add(new InsnNode(Opcodes.RETURN));
return toBytes(cn);
}
private static byte[] toBytes(ClassNode cn) {
final ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
return cw.toByteArray();
}
private static ModRedirector redirector() {
return new ModRedirector(ClassDump.DISABLED, EXTRA_EXCLUSION);
}
private static void assertStandardExclusions(String[] actual) {
final List<String> exclusions = Arrays.asList(actual);
final String[] coreExclusions = new GLSMRedirector().getCoreExclusions();
for (String coreExclusion : coreExclusions) {
assertTrue(exclusions.contains(coreExclusion), coreExclusion);
}
assertTrue(exclusions.contains("com.gtnewhorizons.angelica.lwjgl3."));
assertTrue(exclusions.contains(EXTRA_EXCLUSION));
assertEquals(coreExclusions.length + 2, exclusions.size(), exclusions.toString());
}
@Test
void exclusionsCombineCoreLwjgl3AndExtra() {
assertStandardExclusions(redirector().getTransformerExclusions());
}
@Test
void earlyRedirectorExclusionsCombineCoreLwjgl3AndExtra() {
assertStandardExclusions(new EarlyRedirectorCore(EXTRA_EXCLUSION).getTransformerExclusions());
}
@Test
void nullInputReturnsNull() {
assertNull(redirector().transform("com.example.TestClass", null, this));
}
@Test
void excludedPrefixReturnsOriginalArray() {
final byte[] original = bytesWithGlCall();
assertSame(original, redirector().transform(EXTRA_EXCLUSION + ".TestClass", original, this));
}
@Test
void coreExcludedPrefixReturnsOriginalArray() {
final byte[] original = bytesWithGlCall();
assertSame(original, redirector().transform("org.lwjgl.opengl.TestClass", original, this));
}
@Test
void unchangedClassReturnsOriginalArray() {
final byte[] original = bytesWithoutGlCall();
assertSame(original, redirector().transform("com.example.TestClass", original, this));
}
@Test
void redirectedClassReturnsNewArray() {
final byte[] original = bytesWithGlCall();
assertNotSame(original, redirector().transform("com.example.TestClass", original, this));
}
@Test
void earlyRedirectorSkipsClassesOutsideItsTargets() {
final byte[] original = bytesWithGlCall();
final EarlyRedirectorCore early = new EarlyRedirectorCore(EXTRA_EXCLUSION);
assertSame(original, early.transform("com.example.TestClass", original, this));
}
@Test
void earlyRedirectorTransformsItsTargets() {
final byte[] original = bytesWithGlCall();
final EarlyRedirectorCore early = new EarlyRedirectorCore(EXTRA_EXCLUSION);
final String target = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS[0] + "TestClass";
assertNotSame(original, early.transform(target, original, this));
}
}
@@ -0,0 +1,92 @@
package com.gtnewhorizons.angelica.glsm.loading;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TransformerNarrowerTest {
private static final String LEGENDS_BROAD = "com.tihyo.legends.management.asm";
private static final String LEGENDS_HOOK = LEGENDS_BROAD + ".ASMClientMethods";
private static Set<String> exceptionsWith(String... entries) {
return new HashSet<>(Set.of(entries));
}
private static boolean isExcluded(Set<String> exceptions, String className) {
return exceptions.stream().anyMatch(className::startsWith);
}
private static int narrow(Set<String> exceptions, Map<String, Object> blackboard) {
return TransformerNarrower.narrow(exceptions, blackboard, "angelica", "Angelica", EcosystemNarrowRules.ALL);
}
@Test
void legendsHooksBecomeTransformableButTransformersDoNot() {
final Set<String> exceptions = exceptionsWith(LEGENDS_BROAD);
assertEquals(1, narrow(exceptions, new HashMap<>()));
assertFalse(isExcluded(exceptions, LEGENDS_HOOK));
assertFalse(isExcluded(exceptions, LEGENDS_BROAD + ".ASMMethods"));
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".LegendsLoadingPlugin"));
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".AbstractTransformer"));
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".ASMHelper"));
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".TransformerRenderPlayer"));
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".TransformerEntityRenderer"));
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".TransformerMethodProcess"));
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".TransformerGuiInventory"));
}
@Test
void disabledRuleLeavesBroadExclusionIntact() {
final Set<String> exceptions = exceptionsWith(LEGENDS_BROAD);
final Map<String, Object> blackboard = new HashMap<>();
blackboard.put("angelica.narrow.LegendsMod", Boolean.FALSE);
assertEquals(0, narrow(exceptions, blackboard));
assertTrue(exceptions.contains(LEGENDS_BROAD));
assertTrue(isExcluded(exceptions, LEGENDS_HOOK));
}
@Test
void sharedXaerosKeyGovernsBothExclusions() {
final Set<String> enabled = exceptionsWith("xaero.common.core", "xaero.map.core");
assertEquals(2, narrow(enabled, new HashMap<>()));
assertFalse(isExcluded(enabled, "xaero.common.gui.GuiMap"));
assertFalse(isExcluded(enabled, "xaero.map.gui.GuiMap"));
assertTrue(isExcluded(enabled, "xaero.common.core.transformer.Foo"));
assertTrue(isExcluded(enabled, "xaero.map.core.transformer.Foo"));
final Set<String> disabled = exceptionsWith("xaero.common.core", "xaero.map.core");
final Map<String, Object> blackboard = new HashMap<>();
blackboard.put("angelica.narrow.Xaeros", Boolean.FALSE);
assertEquals(0, narrow(disabled, blackboard));
assertTrue(disabled.contains("xaero.common.core"));
assertTrue(disabled.contains("xaero.map.core"));
}
@Test
void narrowingIsIdempotent() {
final Set<String> exceptions = exceptionsWith(LEGENDS_BROAD);
assertEquals(1, narrow(exceptions, new HashMap<>()));
final Set<String> afterFirst = new HashSet<>(exceptions);
assertEquals(0, narrow(exceptions, new HashMap<>()));
assertEquals(afterFirst, exceptions);
}
@Test
void absentBroadExclusionIsNoOp() {
final Set<String> exceptions = exceptionsWith("some.unrelated.package");
assertEquals(0, narrow(exceptions, new HashMap<>()));
assertEquals(Set.of("some.unrelated.package"), exceptions);
}
}
@@ -0,0 +1,113 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SectionRegistryTest {
private static final class RecordingEmitter implements SectionRegistry.Emitter {
final List<String> setups = new ArrayList<>();
final List<String> enters = new ArrayList<>();
final List<Long> leaves = new ArrayList<>();
long nextId;
boolean connected;
@Override
public long enter(int category, String text) {
enters.add(category + ":" + text);
return connected ? ++nextId : 0L;
}
@Override
public void leave(long nativeId) {
leaves.add(nativeId);
}
@Override
public void setup(int category, String name) {
setups.add(category + ":" + name);
}
@Override
public boolean isConnected() {
return connected;
}
}
private final RecordingEmitter emitter = new RecordingEmitter();
private final SectionRegistry registry = new SectionRegistry(emitter);
private void reconnect() {
emitter.connected = false;
registry.poll();
emitter.connected = true;
registry.poll();
}
@Test
void tokensOutliveEveryReplayAndLeaveUsesTheNewestId() {
final long openedWhileDisconnected = registry.enter(1, "world");
assertNotEquals(0L, openedWhileDisconnected, "token must be valid while disconnected");
assertEquals(0L, emitter.nextId, "no native id while disconnected");
emitter.connected = true;
registry.poll();
final long openedWhileConnected = registry.enter(2, "shaders");
assertNotEquals(openedWhileDisconnected, openedWhileConnected);
reconnect();
emitter.leaves.clear();
registry.leave(openedWhileDisconnected);
registry.leave(openedWhileConnected);
assertEquals(List.of(3L, 4L), emitter.leaves, "leave must use the post-reconnect ids");
}
@Test
void aSectionEnteredOnTheConnectingFrameIsNotEnteredTwice() {
emitter.connected = true;
registry.enter(2, "shaders pack / Overworld");
registry.poll();
assertEquals(List.of("2:shaders pack / Overworld"), emitter.enters, "must not re-enter a section already live on this connection");
}
@Test
void reconnectReplaysEveryCategoryAndEveryOpenSection() {
registry.registerCategory(1, "World");
registry.registerCategory(4, "UI");
emitter.connected = true;
registry.poll();
registry.enter(1, "world");
registry.enter(4, "pause");
emitter.setups.clear();
emitter.enters.clear();
reconnect();
assertEquals(List.of("1:World", "4:UI"), emitter.setups, "all categories re-registered");
assertEquals(List.of("1:world", "4:pause"), emitter.enters, "all open sections re-entered");
}
@Test
void leftSectionsAreNotResurrectedByAReconnect() {
emitter.connected = true;
registry.poll();
final long token = registry.enter(1, "transient");
registry.leave(token);
emitter.enters.clear();
emitter.leaves.clear();
reconnect();
registry.leave(token);
assertTrue(emitter.enters.isEmpty(), "closed sections must not be re-entered");
assertTrue(emitter.leaves.isEmpty(), "closed tokens must not be left twice");
}
}
@@ -331,6 +331,9 @@ class GLSMRedirectorTest {
void phase2NameKeyedGapsRedirect() {
assertRedirected("org/lwjgl/opengl/GL33C", "glQueryCounter", "(II)V", "glQueryCounter");
assertRedirected("org/lwjgl/opengl/GL33C", "glGetQueryObjectui64", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/ARBTimerQuery", "glGetQueryObjectui64", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/EXTTimerQuery", "glGetQueryObjectuEXT", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/EXTTimerQuery", "glGetQueryObjectui64EXT", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/GL30C", "glBindFragDataLocation", "(IILjava/lang/CharSequence;)V", "glBindFragDataLocation");
assertRedirected("org/lwjgl/opengl/GL11C", "glGetIntegerv", "(I[I)V", "glGetInteger");
assertRedirected("org/lwjgl/opengl/GL20C", "glUniform4fv", "(I[F)V", "glUniform4");
@@ -39,7 +39,12 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
new ContextAttribs(3, 3).withProfileCore(true).withForwardCompatible(true));
GLSMExtension.setMainThread(Thread.currentThread());
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(800, 600).build());
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(800, 600)
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete("test");
Loaded 100 of 470 files, more files were not shown because too many files have changed in this diff. Show more