mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 17:24:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73f5556595 | ||
|
|
19bc7cbaed | ||
|
|
d189a644e2 | ||
|
|
94ba40ae4d | ||
|
|
961b23fd2d | ||
|
|
61b5301f1a | ||
|
|
0e539ae034 | ||
|
|
e581326641 | ||
|
|
c707cc51f1 | ||
|
|
7d890bf3e7 | ||
|
|
ffc324bab6 | ||
|
|
0dfffca88e | ||
|
|
a66db1517a | ||
|
|
269b7245d3 | ||
|
|
67f2484976 | ||
|
|
1c8687e6fd | ||
|
|
e6bbd155da | ||
|
|
2f64fef6a6 | ||
|
|
b0fd1ea46e | ||
|
|
6d8cbf9c2f | ||
|
|
096dd01446 | ||
|
|
22c9479946 | ||
|
|
b2f597a73a | ||
|
|
553dec5b6d | ||
|
|
bc8116006c | ||
|
|
bf9492dffb | ||
|
|
69aed42b89 | ||
|
|
ffe54f61b9 | ||
|
|
c86e1de9f6 | ||
|
|
6af401c834 | ||
|
|
b17bb42c49 | ||
|
|
3659b59db9 | ||
|
|
8c31eec525 |
No files matched your search
@@ -20,3 +20,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
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
@@ -3,7 +3,7 @@ name: Release tagged build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ '*' ]
|
||||
tags: [ '*', '!tracy-v*' ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -40,5 +40,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/
|
||||
@@ -1,5 +1,7 @@
|
||||
# AI Usage Policy
|
||||
|
||||
🌐 **Languages:** [English](AI_POLICY.md) | [Українська](AI_POLICY_uk.md)
|
||||
|
||||
This project has strict rules for AI usage:
|
||||
|
||||
* **The human-in-the-loop must fully understand all code.** If you
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Політика використання ШІ
|
||||
|
||||
🌐 **Мови:** [English](AI_POLICY.md) | [Українська](AI_POLICY_uk.md)
|
||||
|
||||
Цей проєкт має суворі правила щодо використання штучного інтелекту (ШІ):
|
||||
|
||||
* **Людина, яка контролює процес, повинна повністю розуміти весь код.**
|
||||
Якщо ви не можете пояснити, що роблять ваші зміни та як вони взаємодіють із
|
||||
більшою системою без допомоги інструментів ШІ, не пропонуйте ці зміни.
|
||||
|
||||
* **Обговорення та задачі можуть створюватися за допомогою ШІ, але повинні перебувати
|
||||
під повним контролем людини.** Це означає, що будь-який контент, згенерований за допомогою ШІ,
|
||||
перед надсиланням має бути перевірений *та відредагований* людиною.
|
||||
Автор повинен сам розібратися в питанні та відсіяти все зайве.
|
||||
|
||||
* **Медіаконтент, згенерований ШІ, заборонений (текстури, відео, аудіо тощо).**
|
||||
Текст і код є єдиним контентом, згенерованим ШІ, який дозволено використовувати
|
||||
згідно з іншими правилами цієї політики.
|
||||
|
||||
* **Повідомлення до комітів та описи PR повинні бути написані у вашому власному стилі.**
|
||||
Коміти, повністю або частково створені ШІ, заборонені, включно з тегами `Co-authored-by` з вказівкою ШІ.
|
||||
|
||||
* **Подавайте виправлення лише для тих багів, із якими ви особисто зіткнулися в грі.**
|
||||
Якщо ви особисто не стикалися з багом, не відтворили його власноруч і ще не знаєте
|
||||
добре відповідний код, не натравлюйте на нього ШІ, щоб потім перенаправити результат нам.
|
||||
Патчі, написані для проблем, із якими автор особисто ніколи не стикався, зазвичай
|
||||
виправляють не те, що потрібно, та/або створюють нові баги.
|
||||
|
||||
* **Стиль та суть письмового контенту повинні належати вам.**
|
||||
Для документації та особливо для квестів тон і думка, яку ви намагаєтеся донести, мають
|
||||
виходити від вас, а не від ШІ. Ви можете використовувати ШІ для покращення формулювань
|
||||
або вичитки, якщо бажаєте, але якщо суть і стиль не ваші, не пропонуйте такий контент.
|
||||
|
||||
* **За зловживання ШІ можна отримати блокування.** Користувачі, які створюють
|
||||
низькоякісний контент, очевидно згенерований ШІ (слоп), можуть бути заблоковані для подальших внесків.
|
||||
Ми залюбки допомагаємо розробникам-початківцям навчатися та рости, але якщо ви зацікавлені
|
||||
в цьому, не використовуйте ШІ, і ми вам допоможемо. Нам прикро, що недобросовісні
|
||||
користувачі ШІ зіпсували це для вас.
|
||||
|
||||
|
||||
## Тут працюють люди
|
||||
|
||||
Будь ласка, пам’ятайте, що цей проєкт підтримують люди.
|
||||
|
||||
Кожне обговорення, задача та запит на злиття читаються й перевіряються
|
||||
людьми (а іноді й машинами). Це точка перетину, де люди взаємодіють
|
||||
один з одним та виконують роботу. Неґречно та неповажно приходити сюди
|
||||
з неякісною, некваліфікованою та зробленою абияк роботою, оскільки це
|
||||
перекладає тягар перевірки на мейнтейнерів.
|
||||
|
||||
В ідеальному світі ШІ щоразу створював би високоякісну та точну роботу.
|
||||
Але сьогодні ця реальність залежить від користувача ШІ. І сьогодні чимало
|
||||
користувачів використовують ШІ неналежним чином. Тому, поки це не зміниться,
|
||||
ми змушені мати суворі правила, щоб захистити мейнтейнерів.
|
||||
|
||||
Створено на основі https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
|
||||
@@ -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
@@ -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)
|
||||
+9
-2
@@ -1,3 +1,4 @@
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
|
||||
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
|
||||
@@ -76,7 +77,7 @@ 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 (name.startsWith("runClient") && name != "runClient") {
|
||||
if (isMacOs) {
|
||||
// SDL3 / Cocoa / Metal must initialize on the JVM main thread.
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
@@ -186,6 +187,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(
|
||||
@@ -228,7 +232,6 @@ val glCoreTest by tasks.registering(Test::class) {
|
||||
}
|
||||
|
||||
tasks.test { finalizedBy(glCoreTest) }
|
||||
tasks.check { dependsOn(glCoreTest) }
|
||||
|
||||
tasks.shadowJar {
|
||||
dependsOn(embedOnly)
|
||||
@@ -257,3 +260,7 @@ tasks.shadowJar {
|
||||
tasks.withType<TaskPublishCurseForge>().configureEach {
|
||||
uploadArtifacts.forEach { it.addGameVersion("Client") }
|
||||
}
|
||||
|
||||
tasks.withType<TaskModrinthUpload>().configureEach {
|
||||
setDependsOn(dependsOn.filterNot { it == "build" } + "assemble")
|
||||
}
|
||||
@@ -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.187:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
@@ -161,6 +161,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")
|
||||
@@ -202,6 +204,7 @@ 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)
|
||||
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
# A specification for models
|
||||
|
||||
🌐 **Languages:** [English](model-spec.md) | [Українська](model-spec_uk.md)
|
||||
|
||||
This is borrowed from [the unofficial Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) under the CC BY-NC-SA 3.0.
|
||||
|
||||
The folder `assets/<namespace>/models/block` holds the model files for all the specified variants. The names of the files can be changed, but must always correspond with the names used in the variant files.
|
||||
@@ -8,7 +11,7 @@ The folder `assets/<namespace>/models/block` holds the model files for all the s
|
||||
* Can be set to `"builtin/generated"` to use a model that is created out of the specified icon. Note that only the first layer is supported, and rotation can be achieved only by using block states files.
|
||||
* `[TAG_byte]` **ambientocclusion**: Whether to use [ambient occlusion](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` - default), or not (`false`). Note:only works on Parent file
|
||||
* `[TAG_object]` **display**: Holds the different places where item models are displayed.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. '''fixed''' refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. **'''fixed'''** refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_list]` **rotation**: Specifies the rotation of the model according to the scheme `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Specifies the position of the model according to the scheme `[x, y, z]`. The values are clamped between -80 and 80.
|
||||
* `[TAG_list]` **scale**: Specifies the scale of the model according to the scheme `[x, y, z]`. If the value is greater than 4, it is displayed as 4.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Специфікація моделей
|
||||
|
||||
🌐 **Мови:** [English](model-spec.md) | [Українська](model-spec_uk.md)
|
||||
|
||||
Запозичено з [неофіційної Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) на умовах ліцензії CC BY-NC-SA 3.0.
|
||||
|
||||
Папка `assets/<namespace>/models/block` містить файли моделей для усіх вказаних варіантів. Назви файлів можна змінювати, але вони завжди повинні відповідати назвам, що використовуються у файлах варіантів.
|
||||
|
||||
* `[TAG_compound]` Кореневий тег
|
||||
* `[TAG_string]` **parent**: Завантажує іншу модель із вказаного шляху у формі [локації ресурсу](https://minecraft.wiki/w/Tutorials/Models#File_path). Якщо задано і `"parent"`, і `"elements"`, тег `"elements"` перевизначає тег `"elements"` із попередньої моделі.
|
||||
* Може мати значення `"builtin/generated"`, щоб використовувати модель, створену із вказаної піктограми. Зверніть увагу: підтримується лише перший шар, а обертання можна досягти лише за допомогою файлів станів блоку.
|
||||
* `[TAG_byte]` **ambientocclusion**: Визначає, чи використовувати [розсіяне затінення](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` — за замовчуванням), чи ні (`false`). Примітка: працює лише у батьківському файлі.
|
||||
* `[TAG_object]` **display**: Містить різні місця відображення моделей предметів.
|
||||
* `[TAG_compound]` ***Position***: Має назву `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground` або `fixed`. Місце, де відображається модель предмета. Містить її обертання, зміщення та масштаб для вказаної ситуації. **'''fixed'''** стосується рамок для предметів, а решта відповідають своїм назвам. Зверніть увагу: зміщення застосовується до моделі перед обертанням.
|
||||
* `[TAG_list]` **rotation**: Задає обертання моделі за схемою `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Задає зміщення моделі за схемою `[x, y, z]`. Значення обмежені в діапазоні від -80 до 80.
|
||||
* `[TAG_list]` **scale**: Задає масштаб моделі за схемою `[x, y, z]`. Якщо значення більше за 4, воно відображається як 4.
|
||||
* `[TAG_compound]` **textures**: Містить текстури моделі у формі [локації ресурсу](https://minecraft.wiki/w/Tutorials/Models#File_path) або у вигляді іншої текстурної змінної.
|
||||
* `[TAG_string]` **particle**: Текстура, з якої завантажуються частинки. Ця текстура також використовується як оверлей, якщо ви перебуваєте в [порталі Незеру](https://minecraft.wiki/w/Nether_portal), а також для нерухомих текстур [води](https://minecraft.wiki/w/water) та [лави](https://minecraft.wiki/w/Lava).<sup>[[1]](https://bugs.mojang.com/browse/MC-240042)</sup> Ця текстура також вважається текстурною змінною, на яку можна посилатися як `"#particle"`. Примітка: Усі частинки руйнування для блоків без моделей вшиті в код, наприклад, для [бар’єрів](https://minecraft.wiki/w/Barrier).
|
||||
* `[TAG_string]` ***Texture variable***: Визначає текстурну змінну з ім’ям `name` та призначає її текстурі або іншій змінній.
|
||||
* `[TAG_list]` **elements**: Містить усі елементи моделі. Вони можуть мати лише кубічну форму. Якщо задано і `"parent"`, і `"elements"`, тег `"elements"` перевизначає тег `"elements"` із попередньої моделі.
|
||||
* `[TAG_compound]` Елемент.
|
||||
* `[TAG_list]` **from**: Початкова точка кубоїда за схемою `[x, y, z]`. Значення мають бути від -16 до 32.
|
||||
* `[TAG_list]` **to**: Кінцева точка кубоїда за схемою `[x, y, z]`. Значення мають бути від -16 до 32.
|
||||
* `[TAG_compound]` **rotation**: Визначає обертання елемента.
|
||||
* `[TAG_list]` **origin**: Встановлює центр обертання за схемою `[x, y, z]`.
|
||||
* `[TAG_string]` **axis**: Задає вісь обертання: може бути `"x"`, `"y"` або `"z"`.
|
||||
* `[TAG_float]` **angle**: Задає кут обертання. Може бути від 45 до -45 градусів із кроком у 22,5 градуса.
|
||||
* `[TAG_byte]` **rescale**: Визначає, чи масштабувати грані по всьому блоку. Може бути `true` або `false`. За замовчуванням — `false`.
|
||||
* `[TAG_byte]` **shade**: Визначає, чи рендерити тіні (`true` — за замовчуванням), чи ні (`false`).
|
||||
* `[TAG_compound]` **faces**: Містить усі грані кубоїда. Якщо грань пропущено, вона не рендериться.
|
||||
* `[TAG_compound]` ***Face***: Має назву `down`, `up`, `north`, `south`, `west` або `east`. Містить властивості вказаної грані.
|
||||
* `[TAG_list]` **uv**: Визначає ділянку текстури для використання за схемою `[x1, y1, x2, y2]`. Поведінка текстури стає непередбачуваною, якщо значення UV виходять за межі 0 або 16. Якщо значення `x1` та `x2` поміняти місцями (наприклад, з `0, 0, 16, 16` на `16, 0, 0, 16`), текстура віддзеркалюється. Параметр UV є необов’язковим; якщо його не вказано, він автоматично генерується на основі позиції елемента.
|
||||
* `[TAG_string]` **texture**: Вказує текстуру у формі текстурної змінної зі знаком `#` на початку.
|
||||
* `[TAG_string]` **cullface**: Визначає, чи не потрібно рендерити грань, якщо до неї в указаній позиції торкається інший блок. Позиція може бути: `down`, `up`, `north`, `south`, `west` або `east`. Цей параметр також визначає сторону блоку, з якої береться рівень освітлення для цієї грані; якщо параметр не задано, за замовчуванням використовується відповідна сторона.
|
||||
* `[TAG_int]` **rotation**: Обертає текстуру за годинниковою стрілкою на вказаний кут у градусах. Може бути 0, 90, 180 або 270. За замовчуванням — 0. Обертання не впливає на те, яка частина текстури використовується. Натомість воно зводиться до перестановки вершин вибраної текстури (вибраних неявно або явно через `uv`).
|
||||
* `[TAG_int]` **tintindex**: Визначає, чи потрібно тонувати текстуру за допомогою вшитого індексу відтінку. Значення за замовчуванням, -1, вказує на те, що відтінок не використовується. Будь-яке інше число передається в `BlockColors` для отримання значення відтінку, відповідного цьому індексу. Однак більшість блоків не мають визначеного значення відтінку (у такому разі використовується білий колір). Крім того, жоден ванільний блок наразі не використовує кілька значень відтінку, тому значення індексу відтінку ігнорується (якщо воно відмінне від -1); втім, його можна використовувати для модифікованих блоків, яким потрібні кілька різних значень відтінку в одному блоці.
|
||||
@@ -224,6 +224,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,6 +47,7 @@ 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
|
||||
@@ -55,7 +59,7 @@ 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 LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
|
||||
@@ -65,8 +69,12 @@ public final class SystemProperties {
|
||||
// Set by us, read by celeritas
|
||||
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
|
||||
|
||||
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 +123,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;
|
||||
|
||||
@@ -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;
|
||||
@@ -500,6 +506,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 +559,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 +662,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 +744,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 +820,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 +896,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 +1140,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 +1239,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 +1312,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 +1332,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 +1439,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 +1457,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 +1476,7 @@ public class GLStateManager {
|
||||
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
|
||||
}
|
||||
}
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
}
|
||||
|
||||
public static void glBlendEquation(int mode) {
|
||||
@@ -1425,9 +1516,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 +1540,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 +1762,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 +1869,7 @@ public class GLStateManager {
|
||||
}
|
||||
final DeferredAlphaHandler ah = GLSMHooks.alphaHandler;
|
||||
if (ah != null && ah.isAlphaTestLocked()) {
|
||||
glCtx.alphaTest.beforeModify();
|
||||
ah.deferAlphaTestToggle(true);
|
||||
return;
|
||||
}
|
||||
@@ -1762,6 +1891,7 @@ public class GLStateManager {
|
||||
}
|
||||
final DeferredAlphaHandler ah = GLSMHooks.alphaHandler;
|
||||
if (ah != null && ah.isAlphaTestLocked()) {
|
||||
glCtx.alphaTest.beforeModify();
|
||||
ah.deferAlphaTestToggle(false);
|
||||
return;
|
||||
}
|
||||
@@ -1965,6 +2095,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 +2114,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 +2133,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 +2152,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 +2579,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 +2699,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 +3193,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,14 +3524,11 @@ 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();
|
||||
@@ -3397,7 +3544,14 @@ public class GLStateManager {
|
||||
if (processMessages) Display.processMessages();
|
||||
return;
|
||||
}
|
||||
if (!RENDER_BACKEND.wantsDisplayUpdateGateTiming()) {
|
||||
Display.update(processMessages);
|
||||
return;
|
||||
}
|
||||
final long start = System.nanoTime();
|
||||
Display.update(processMessages);
|
||||
final long end = System.nanoTime();
|
||||
RENDER_BACKEND.recordFrameGate(end - start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3513,6 +3667,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 +3688,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 +3708,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 +3785,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 +3887,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) {
|
||||
@@ -4510,7 +4684,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 +5080,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 +5222,54 @@ 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 int getDisplayRefreshRateHz() {
|
||||
return RENDER_BACKEND.getDisplayRefreshRateHz();
|
||||
}
|
||||
|
||||
public static boolean gateAnchorsNextFrameStart() {
|
||||
return RENDER_BACKEND.gateAnchorsNextFrameStart();
|
||||
}
|
||||
|
||||
public static long lastFrameGateNanos() {
|
||||
return RENDER_BACKEND.lastFrameGateNanos();
|
||||
}
|
||||
|
||||
public static long lastFrameGateEndNanos() {
|
||||
return RENDER_BACKEND.lastFrameGateEndNanos();
|
||||
}
|
||||
|
||||
public static boolean hasSwapchainBackpressure() {
|
||||
return RENDER_BACKEND.hasSwapchainBackpressure();
|
||||
}
|
||||
|
||||
public static void pumpDisplayMessages() {
|
||||
Display.processMessages();
|
||||
}
|
||||
|
||||
public static int glGetError() {
|
||||
if (!RENDER_BACKEND.hasContext()) return 0;
|
||||
if (initConfig.isNoErrorChecks()) return 0;
|
||||
@@ -5119,6 +5350,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 +5366,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 +5376,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 +5417,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 +5898,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 +5918,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 +6020,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 +6116,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 +6150,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 +6203,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 +6230,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 +6263,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 +6295,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 +6467,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 +6524,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 +7570,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 +7842,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 com.mitchej123.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) {
|
||||
|
||||
+14
-1
@@ -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;
|
||||
@@ -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 int queryDisplayRefreshRateHz() {
|
||||
final DisplayMode mode = Display.getDisplayMode();
|
||||
if (mode != null && mode.getFrequency() > 0) return mode.getFrequency();
|
||||
final DisplayMode desktop = Display.getDesktopDisplayMode();
|
||||
return desktop == null ? 0 : desktop.getFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnisotropicSupported() {
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Abstract rendering backend.
|
||||
@@ -62,7 +63,77 @@ 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);
|
||||
publishVSyncMode(applyVSyncMode(preferred));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
public boolean supportsVSyncMode(VSyncMode mode) { return mode != VSyncMode.MAILBOX; }
|
||||
|
||||
protected final void publishVSyncMode(VSyncMode mode) { effectiveVSyncMode = mode; }
|
||||
|
||||
public final VSyncMode getEffectiveVSyncMode() { return effectiveVSyncMode; }
|
||||
|
||||
private static final long REFRESH_POLL_NANOS = 1_000_000_000L;
|
||||
|
||||
private long refreshPollNanos;
|
||||
private int cachedRefreshHz;
|
||||
|
||||
public final int getDisplayRefreshRateHz() {
|
||||
final long now = System.nanoTime();
|
||||
if (refreshPollNanos == 0L || now - refreshPollNanos >= REFRESH_POLL_NANOS) {
|
||||
refreshPollNanos = now;
|
||||
cachedRefreshHz = queryDisplayRefreshRateHz();
|
||||
}
|
||||
return cachedRefreshHz;
|
||||
}
|
||||
|
||||
protected int queryDisplayRefreshRateHz() { return 0; }
|
||||
|
||||
public static int refreshHzFrom(int numerator, int denominator, float fallbackHz) {
|
||||
if (numerator > 0 && denominator > 0) return (numerator + denominator - 1) / denominator;
|
||||
return fallbackHz > 0.0f ? (int) Math.ceil(fallbackHz) : 0;
|
||||
}
|
||||
|
||||
public boolean hasSwapchainBackpressure() { return false; }
|
||||
|
||||
private long frameGateNanos;
|
||||
private long frameGateEndNanos;
|
||||
|
||||
public boolean gateAnchorsNextFrameStart() { return false; }
|
||||
|
||||
public boolean wantsDisplayUpdateGateTiming() { return true; }
|
||||
|
||||
public void recordFrameGate(long durationNanos, long endNanos) {
|
||||
frameGateNanos = durationNanos;
|
||||
frameGateEndNanos = endNanos;
|
||||
}
|
||||
|
||||
public long lastFrameGateNanos() { return frameGateNanos; }
|
||||
|
||||
public long lastFrameGateEndNanos() { return frameGateEndNanos; }
|
||||
|
||||
public void onFrameBegin() {
|
||||
if (GLStateManager.takeStateSeedPending()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
+1
@@ -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);
|
||||
}
|
||||
@@ -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() {}
|
||||
|
||||
+64
@@ -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);
|
||||
}
|
||||
}
|
||||
+5
@@ -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,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;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class ForeignDrawEndEvent extends MutableEvent {
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class VanillaBlendChangeEvent extends MutableEvent {
|
||||
}
|
||||
+9
-2
@@ -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,6 +78,13 @@ 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"
|
||||
))
|
||||
);
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
+7
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+84
-1
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+151
-5
@@ -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;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ public class TextureInfoCache {
|
||||
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.bumpUploadGeneration();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,9 +66,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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
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 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,65 @@
|
||||
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 refreshPrefersTheExactRational() {
|
||||
assertEquals(144, RenderBackend.refreshHzFrom(144, 1, 0.0f));
|
||||
assertEquals(60, RenderBackend.refreshHzFrom(60000, 1001, 0.0f));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshRoundsUp() {
|
||||
assertEquals(144, RenderBackend.refreshHzFrom(0, 0, 143.98f));
|
||||
assertEquals(60, RenderBackend.refreshHzFrom(0, 0, 59.94f));
|
||||
assertEquals(60, RenderBackend.refreshHzFrom(0, 0, 60.0f));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshIsZeroWhenUnknown() {
|
||||
assertEquals(0, RenderBackend.refreshHzFrom(0, 0, 0.0f));
|
||||
assertEquals(0, RenderBackend.refreshHzFrom(-1, -1, -1.0f));
|
||||
assertEquals(0, RenderBackend.refreshHzFrom(144, 0, 0.0f));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
+92
@@ -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);
|
||||
}
|
||||
}
|
||||
+113
@@ -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");
|
||||
|
||||
@@ -9,13 +9,13 @@ asmCompile = "5.0.3"
|
||||
lwjgl3 = "3.4.2-20260602.093430-9"
|
||||
lwjgl3ify = "3.0.31"
|
||||
|
||||
celeritas = "2.5.9-GTNH"
|
||||
celeritas = "2.5.10-GTNH"
|
||||
gtnhlib = "0.11.36"
|
||||
eventbus = "7.0-beta.15"
|
||||
glslTransformLib = "0.2.0-33.g3ec381e"
|
||||
antlr4Runtime = "4.13.2"
|
||||
jcpp = "1.4.14"
|
||||
tracy = "0.13.1"
|
||||
tracy = "0.14.0"
|
||||
retroFuturaBootstrap = "1.1.0"
|
||||
|
||||
lombok = "1.18.46"
|
||||
|
||||
+18
-1
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.GLESCaps;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.DebugMessageHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -32,6 +33,7 @@ import org.lwjgl.opengl.EXTPolygonOffsetClamp;
|
||||
import org.lwjgl.opengl.EXTShaderImageLoadStore;
|
||||
import org.lwjgl.opengl.GLCapabilities;
|
||||
import org.lwjgl.opengl.GLDebugMessageCallback;
|
||||
import org.lwjgl.sdl.SDL_DisplayMode;
|
||||
import org.lwjgl.sdl.SDL_DropEvent;
|
||||
import org.lwjgl.sdl.SDL_EventFilter;
|
||||
import org.lwjgl.sdl.SDLEvents;
|
||||
@@ -39,6 +41,7 @@ import org.lwjgl.sdl.SDLVideo;
|
||||
import org.lwjgl.system.JNI;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjglx.opengl.Display;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -169,7 +172,21 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
public int getMinGLSLVersion() {return 330;}
|
||||
|
||||
@Override
|
||||
public void setVSyncEnabled(boolean enabled) {SDLVideo.SDL_GL_SetSwapInterval(enabled ? 1 : 0);}
|
||||
protected VSyncMode applyVSyncMode(VSyncMode preferred) {
|
||||
final boolean enabled = preferred.tearFree();
|
||||
SDLVideo.SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
||||
return enabled ? VSyncMode.ON : VSyncMode.OFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int queryDisplayRefreshRateHz() {
|
||||
final long window = Display.getWindow();
|
||||
final int displayId = window == 0 ? SDLVideo.SDL_GetPrimaryDisplay() : SDLVideo.SDL_GetDisplayForWindow(window);
|
||||
if (displayId == 0) return 0;
|
||||
final SDL_DisplayMode mode = SDLVideo.SDL_GetCurrentDisplayMode(displayId);
|
||||
if (mode == null) return 0;
|
||||
return refreshHzFrom(mode.refresh_rate_numerator(), mode.refresh_rate_denominator(), mode.refresh_rate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnisotropicSupported() {
|
||||
|
||||
@@ -59,6 +59,7 @@ dependencies {
|
||||
api(project(":glsm"))
|
||||
|
||||
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
|
||||
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
compileOnly(libs.celeritas.common) { isTransitive = false }
|
||||
|
||||
// GL enum constants (compileOnly -- no runtime GL dependency)
|
||||
@@ -76,6 +77,7 @@ dependencies {
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(testFixtures(project(":glsm")))
|
||||
testImplementation(libs.celeritas.common) { isTransitive = false }
|
||||
testImplementation(libs.lwjgl3.opengl)
|
||||
// GL.<clinit> loads liblwjgl_opengl.so. Test scope only: a root-level runtimeOnly collides with lwjgl3Bindings.
|
||||
testRuntimeOnly(libs.lwjgl3.opengl) { artifact { classifier = lwjglNatives } }
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.sdl.SDLLog;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.lwjgl.sdl.SDLProperties;
|
||||
import org.lwjgl.system.Platform;
|
||||
import org.lwjglx.Sys;
|
||||
@@ -100,6 +101,11 @@ public final class SDLGPUGate {
|
||||
ShaderManager.prewarmSpirv(transformedSource, glShaderType);
|
||||
}
|
||||
|
||||
public static void prewarmSpirv(String transformedSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, int glShaderType) {
|
||||
if (!isActive()) return;
|
||||
ShaderManager.prewarmSpirv(transformedSource, bodyTree, headerLen, glShaderType);
|
||||
}
|
||||
|
||||
public static void clearShaderPrewarmCache() {
|
||||
if (!isActive()) return;
|
||||
ShaderManager.clearPrewarmCache();
|
||||
@@ -152,6 +158,9 @@ public final class SDLGPUGate {
|
||||
}
|
||||
|
||||
public static void fallBackToGL() {
|
||||
if (SDLGPULWJGLService.isOfferedAsAvailable()) {
|
||||
throw new IllegalStateException("Celeritas already selected the SDL GPU LWJGL service; falling back to OpenGL would leave it bound to a dead backend");
|
||||
}
|
||||
disarmed = true;
|
||||
engaged = false;
|
||||
deviceReady = false;
|
||||
|
||||
@@ -19,11 +19,17 @@ import java.util.Arrays;
|
||||
|
||||
/** SDL GPU implementation of {@link LWJGLService} for Celeritas */
|
||||
public final class SDLGPULWJGLService extends LWJGLService {
|
||||
|
||||
private static volatile boolean offeredAsAvailable;
|
||||
|
||||
public static boolean isOfferedAsAvailable() { return offeredAsAvailable; }
|
||||
|
||||
@Override public int getPriority() {
|
||||
if (SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable() && SDLGPUGate.isEngaged()) {
|
||||
return 200;
|
||||
if (!SystemProperties.USE_SDL_GPU || !SDLGPUGate.isSDLGPUAvailable() || !SDLGPUGate.isEngaged()) {
|
||||
return PRIORITY_UNAVAILABLE;
|
||||
}
|
||||
return Integer.MIN_VALUE;
|
||||
offeredAsAvailable = true;
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override public boolean isOpenGLVersionSupported(int major, int minor) {
|
||||
@@ -148,22 +154,30 @@ public final class SDLGPULWJGLService extends LWJGLService {
|
||||
}
|
||||
@Override public void glDeleteSync(long sync) { BackendManager.RENDER_BACKEND.deleteSync(sync); }
|
||||
|
||||
private long[] queryTimestamps = new long[64];
|
||||
private long[] queryElapsed = new long[64];
|
||||
private int activeQueryId = -1;
|
||||
private long activeQueryStart;
|
||||
|
||||
private void ensureQueryCapacity(int id) {
|
||||
if (id >= queryTimestamps.length) {
|
||||
queryTimestamps = Arrays.copyOf(queryTimestamps, Math.max(id + 1, queryTimestamps.length * 2));
|
||||
if (id >= queryElapsed.length) {
|
||||
queryElapsed = Arrays.copyOf(queryElapsed, Math.max(id + 1, queryElapsed.length * 2));
|
||||
}
|
||||
}
|
||||
|
||||
@Override public int glGenQueries() { return BackendManager.RENDER_BACKEND.genQueries(); }
|
||||
@Override public void glDeleteQueries(int query) { }
|
||||
@Override public void glQueryCounter(int id, int target) {
|
||||
@Override public void glBeginQuery(int target, int id) {
|
||||
ensureQueryCapacity(id);
|
||||
queryTimestamps[id] = System.nanoTime();
|
||||
activeQueryId = id;
|
||||
activeQueryStart = System.nanoTime();
|
||||
}
|
||||
@Override public void glEndQuery(int target) {
|
||||
if (activeQueryId < 0) return;
|
||||
queryElapsed[activeQueryId] = System.nanoTime() - activeQueryStart;
|
||||
activeQueryId = -1;
|
||||
}
|
||||
@Override public long glGetQueryObjectui64(int id, int pname) {
|
||||
if (id >= 0 && id < queryTimestamps.length) return queryTimestamps[id];
|
||||
if (id >= 0 && id < queryElapsed.length) return queryElapsed[id];
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
@@ -17,6 +18,8 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FenceTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.Presenter;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DrawDispatch;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
@@ -40,9 +43,10 @@ import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageBufferBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageTextureBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.splash.SplashDispatcher;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.splash.SplashOffscreenTarget;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.OffscreenTarget;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.DebugLabels;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.DebugMessageRelay;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
@@ -79,12 +83,11 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.lwjgl.sdl.SDLGPU;
|
||||
import org.lwjgl.sdl.SDLSurface;
|
||||
import org.lwjgl.sdl.SDL_GPUBlitInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUBufferLocation;
|
||||
import org.lwjgl.sdl.SDL_GPUTextureLocation;
|
||||
@@ -107,6 +110,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
private final Device device = SDLGPUGate.device();
|
||||
private final FrameManager frameManager = new FrameManager(device);
|
||||
private volatile Presenter presenter;
|
||||
private final ResourceManager resourceManager = new ResourceManager(device, frameManager);
|
||||
private final Image3DClear image3DClear = new Image3DClear();
|
||||
private final ShaderManager shaderManager = new ShaderManager(device);
|
||||
@@ -150,33 +154,31 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
private volatile boolean shutdown;
|
||||
|
||||
private SplashOffscreenTarget splashTarget;
|
||||
private volatile OffscreenTarget splashTarget;
|
||||
private volatile boolean splashTeardownPending;
|
||||
|
||||
private static final CopyOnWriteArrayList<ContextState> registeredStates = new CopyOnWriteArrayList<>();
|
||||
private static final ThreadLocal<ContextState> tlState = ThreadLocal.withInitial(() -> {
|
||||
private static final ThreadRegistry<ContextState> registeredStates = new ThreadRegistry<>(new ContextState[0]);
|
||||
private static final ThreadLocal<ContextState> tlState = new ThreadLocal<>();
|
||||
|
||||
private static ContextState s() {
|
||||
final ContextState sole = registeredStates.sole();
|
||||
if (sole != null && sole.owner == Thread.currentThread()) return sole;
|
||||
final ContextState st = tlState.get();
|
||||
return st != null ? st : registerState();
|
||||
}
|
||||
|
||||
private static ContextState registerState() {
|
||||
final ContextState st = new ContextState();
|
||||
for (int i = 0; i < ContextState.MAX_VERTEX_ATTRIBS; i++) st.attribDefaults[i * 4 + 3] = 1.0f;
|
||||
st.attribDefaults[4] = 1.0f; st.attribDefaults[5] = 1.0f;
|
||||
st.attribDefaults[6] = 1.0f;
|
||||
tlState.set(st);
|
||||
registeredStates.add(st);
|
||||
refreshSoleState();
|
||||
return st;
|
||||
});
|
||||
|
||||
private static volatile ContextState soleState;
|
||||
|
||||
private static void refreshSoleState() {
|
||||
soleState = registeredStates.size() == 1 ? registeredStates.get(0) : null;
|
||||
}
|
||||
|
||||
private static ContextState s() {
|
||||
final ContextState st = soleState;
|
||||
if (st != null && st.owner == Thread.currentThread()) return st;
|
||||
return tlState.get();
|
||||
}
|
||||
|
||||
private void markAllPipelineInputDirty() {
|
||||
for (ContextState st : registeredStates) st.pipeline.markInputDirty();
|
||||
for (ContextState st : registeredStates.snapshot()) st.pipeline.markInputDirty();
|
||||
}
|
||||
|
||||
public static final int CTR_SLOT_WRITES = 0;
|
||||
@@ -188,7 +190,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
public static void takeContextCounters(int[] out) {
|
||||
Arrays.fill(out, 0);
|
||||
for (ContextState st : registeredStates) {
|
||||
for (ContextState st : registeredStates.snapshot()) {
|
||||
out[CTR_SLOT_WRITES] += st.slotWrites;
|
||||
out[CTR_SLOT_WRITES_ELIDED] += st.slotWritesElided;
|
||||
out[CTR_SSBO_BINDS] += st.ssboBinds;
|
||||
@@ -203,7 +205,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
private void releaseUniformStagingAllThreads(ShaderManager.ProgramObject prog) {
|
||||
for (ContextState st : registeredStates) st.releaseUniformStaging(prog);
|
||||
for (ContextState st : registeredStates.snapshot()) st.releaseUniformStaging(prog);
|
||||
}
|
||||
|
||||
|
||||
@@ -239,16 +241,12 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
});
|
||||
GLSMHooks.LOADING_CHECKPOINT
|
||||
.addListener(event -> {
|
||||
if (Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
if (!GLStateManager.isSplashComplete()) {
|
||||
SplashDispatcher.tryDispatch(this, splashTarget);
|
||||
} else if (splashTarget != null) {
|
||||
SplashDispatcher.signalFrameReady();
|
||||
SplashDispatcher.tryDispatch(this, splashTarget);
|
||||
splashTarget.destroy(resourceManager);
|
||||
splashTarget = null;
|
||||
SplashDispatcher.reset();
|
||||
if (!GLStateManager.isSplashComplete()) {
|
||||
if (Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
SplashDispatcher.tryDispatch(this, splashTarget, false);
|
||||
}
|
||||
} else if (splashTarget != null) {
|
||||
splashTeardownPending = true;
|
||||
}
|
||||
if (event.requiresSync) {
|
||||
frameManager.requestSyncFlushOnAllRegisteredFrames();
|
||||
@@ -289,13 +287,19 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
resourceManager.setBufferLivenessListener(this::markAllPipelineInputDirty);
|
||||
shaderManager.setUniformReleaseListener(this::releaseUniformStagingAllThreads);
|
||||
fenceTracker.setUnresolvedFenceFlush(this::midFrameFenceFlush);
|
||||
frameManager.setMainThread(GLStateManager.getMainThread());
|
||||
if (!SystemProperties.DISABLE_SDL_PRESENTER_THREAD) {
|
||||
presenter = new Presenter(frameManager, MainStartOnFirstThread.instance());
|
||||
frameManager.setPresenter(presenter);
|
||||
LOG.info("SDL presenter thread enabled: presents run on '{}'", device.getWindowThread() != null ? device.getWindowThread().getName() : "window thread");
|
||||
}
|
||||
|
||||
final int splashW = Math.max(1, Display.getWidth());
|
||||
final int splashH = Math.max(1, Display.getHeight());
|
||||
final int[] maxDesktop = device.getMaxDesktopSizePixels();
|
||||
final int splashTexW = Math.max(splashW, maxDesktop[0]);
|
||||
final int splashTexH = Math.max(splashH, maxDesktop[1]);
|
||||
splashTarget = new SplashOffscreenTarget();
|
||||
splashTarget = new OffscreenTarget();
|
||||
splashTarget.create(device, resourceManager, splashTexW, splashTexH, device.getSwapchainTextureFormat());
|
||||
SplashDispatcher.seedDrawnSize(splashW, splashH);
|
||||
LOG.info("Splash offscreen target {}x{} (window {}x{}, max desktop {}x{})", splashTexW, splashTexH, splashW, splashH, maxDesktop[0], maxDesktop[1]);
|
||||
@@ -375,13 +379,50 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
LOG.info("Populated GL capabilities for SDL GPU backend");
|
||||
}
|
||||
|
||||
@Override public void setVSyncEnabled(boolean enabled) {
|
||||
device.setVSyncEnabled(enabled);
|
||||
private final AtomicReference<VSyncMode> pendingVSync = new AtomicReference<>();
|
||||
|
||||
@Override protected VSyncMode applyVSyncMode(VSyncMode preferred) {
|
||||
pendingVSync.set(preferred);
|
||||
return device.chooseVSyncMode(preferred);
|
||||
}
|
||||
|
||||
@Override public boolean supportsVSyncMode(VSyncMode mode) {
|
||||
return device.supportsVSyncMode(mode);
|
||||
}
|
||||
|
||||
private void applyPendingVSync() {
|
||||
final VSyncMode preferred = pendingVSync.getAndSet(null);
|
||||
if (preferred != null) {
|
||||
if (presenter != null) presenter.drain();
|
||||
publishVSyncMode(device.applyVSync(preferred));
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected int queryDisplayRefreshRateHz() {
|
||||
return device.getDisplayRefreshRateHz();
|
||||
}
|
||||
|
||||
@Override public boolean gateAnchorsNextFrameStart() { return true; }
|
||||
|
||||
@Override public boolean wantsDisplayUpdateGateTiming() { return false; }
|
||||
|
||||
@Override public long lastFrameGateNanos() {
|
||||
if (presenter != null) return frameManager.windowFrame().lastFrameGateNanos;
|
||||
return frameManager.lastFrameGateNanos();
|
||||
}
|
||||
|
||||
@Override public long lastFrameGateEndNanos() {
|
||||
if (presenter != null) return frameManager.windowFrame().lastFrameGateEndNanos;
|
||||
return frameManager.lastFrameGateEndNanos();
|
||||
}
|
||||
|
||||
@Override public boolean hasSwapchainBackpressure() { return true; }
|
||||
|
||||
@Override public void shutdown() {
|
||||
if (shutdown) return;
|
||||
shutdown = true;
|
||||
if (presenter != null) presenter.drain();
|
||||
frameManager.destroyFinalTarget();
|
||||
if (splashTarget != null) {
|
||||
splashTarget.destroy(resourceManager);
|
||||
splashTarget = null;
|
||||
@@ -404,7 +445,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
fenceTracker.dispose();
|
||||
frameManager.releaseAllRegisteredFrames();
|
||||
resourceManager.shutdownTexSamplerStates();
|
||||
for (ContextState st : registeredStates) {
|
||||
for (ContextState st : registeredStates.snapshot()) {
|
||||
if (st.fanIndexBuffer != 0) {
|
||||
resourceManager.releaseBufferHandle(st.fanIndexBuffer);
|
||||
st.fanIndexBuffer = 0;
|
||||
@@ -422,7 +463,6 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
clearMappedState(st);
|
||||
}
|
||||
registeredStates.clear();
|
||||
soleState = null;
|
||||
|
||||
drawDispatch.clearWarnedState();
|
||||
pipelineStore.shutdown();
|
||||
@@ -461,32 +501,35 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public void onRenderThreadReleased(Thread t) {
|
||||
if (shutdown || t == GLStateManager.getMainThread()) return;
|
||||
if (frameManager.isFrameActive()) {
|
||||
frameManager.endFrame();
|
||||
}
|
||||
frameManager.releaseThreadState();
|
||||
registeredStates.remove(tlState.get());
|
||||
tlState.remove();
|
||||
refreshSoleState();
|
||||
final ContextState st = tlState.get();
|
||||
if (st != null) {
|
||||
tlState.remove();
|
||||
registeredStates.remove(st);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean handleMakeCurrent(Object drawable) {
|
||||
if (!isSDLManagedDrawable(drawable)) return false;
|
||||
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
|
||||
if (splashTarget != null && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
s().boundFboId = splashTarget.fboId();
|
||||
final OffscreenTarget splash = splashTarget;
|
||||
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
s().boundFboId = splash.fboId();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean handleReleaseContext(Object drawable) {
|
||||
if (isSDLManagedDrawable(drawable)) {
|
||||
Lwjgl3GLCapabilitiesShim.clearOnCurrentThread();
|
||||
onRenderThreadReleased(Thread.currentThread());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (!isSDLManagedDrawable(drawable)) return false;
|
||||
if (Thread.currentThread() == GLStateManager.getMainThread()) return true;
|
||||
Lwjgl3GLCapabilitiesShim.clearOnCurrentThread();
|
||||
onRenderThreadReleased(Thread.currentThread());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isSDLManagedDrawable(Object drawable) {
|
||||
@@ -496,7 +539,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public boolean handleSwapBuffers() {
|
||||
if (splashTarget != null && splashTarget.isFor(s()) && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
final OffscreenTarget splash = splashTarget;
|
||||
if (splash != null && splash.isFor(s()) && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
endFrameUploadFlush();
|
||||
frameManager.endFrame();
|
||||
SplashDispatcher.signalFrameReady((int) s().viewportW, (int) s().viewportH);
|
||||
@@ -509,33 +553,30 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void dispatchSplashBlit(SplashOffscreenTarget target) {
|
||||
public void dispatchSplashBlit(OffscreenTarget target) {
|
||||
if (shutdown || target == null) return;
|
||||
onFrameBegin();
|
||||
final FrameManager.FrameState f = frameManager.frame();
|
||||
if (!frameManager.ensureSwapchainAcquired(f)) {
|
||||
onFrameEnd();
|
||||
return;
|
||||
}
|
||||
frameManager.endCopyPassIfActive();
|
||||
frameManager.endRenderPassIfActive();
|
||||
final long drawnSize = SplashDispatcher.getDrawnSize();
|
||||
final int srcW = Math.max(1, Math.min((int) (drawnSize >> 32), target.width()));
|
||||
final int srcH = Math.max(1, Math.min((int) drawnSize, target.height()));
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final var info = SDL_GPUBlitInfo.calloc(stack);
|
||||
info.source(s -> s.texture(target.colorTexture()).x(0).y(0).w(srcW).h(srcH));
|
||||
info.destination(d -> d.texture(f.swapchainTexture).x(0).y(0).w(f.swapchainWidth).h(f.swapchainHeight));
|
||||
info.filter(SDL_GPU_FILTER_LINEAR);
|
||||
info.flip_mode(SDLSurface.SDL_FLIP_VERTICAL);
|
||||
SDL_BlitGPUTexture(f.commandBuffer, info);
|
||||
}
|
||||
f.swapchainUsedThisFrame = true;
|
||||
onFrameEnd();
|
||||
frameManager.presentSplash(target.colorTexture(), srcW, srcH);
|
||||
}
|
||||
|
||||
private void finishSplash() {
|
||||
splashTeardownPending = false;
|
||||
final OffscreenTarget target = splashTarget;
|
||||
if (target == null) return;
|
||||
SplashDispatcher.signalFrameReady();
|
||||
SplashDispatcher.tryDispatch(this, target, true);
|
||||
if (presenter != null) presenter.drain();
|
||||
splashTarget = null;
|
||||
target.destroy(resourceManager);
|
||||
SplashDispatcher.reset();
|
||||
}
|
||||
|
||||
@Override public void onFrameBegin() {
|
||||
if (shutdown) return;
|
||||
if (splashTeardownPending) finishSplash();
|
||||
if (GLStateManager.isSplashComplete()) applyPendingVSync();
|
||||
frameManager.beginFrame();
|
||||
beginFrameInit();
|
||||
}
|
||||
@@ -549,9 +590,13 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
ResourceManager.setSingleThreadedReads(GLStateManager.isSplashComplete());
|
||||
st.clearedTexturesThisFrame.clear();
|
||||
st.clearedStencilTexturesThisFrame.clear();
|
||||
st.pendingColorTextures.clear();
|
||||
st.pendingDepthTextures.clear();
|
||||
st.pendingStencilTextures.clear();
|
||||
st.pendingSwapchainClear = false;
|
||||
st.pendingSwapchainDepthClear = false;
|
||||
st.pendingSwapchainStencilClear = false;
|
||||
st.uniformBlocks[0].flushedThisFrame = false;
|
||||
st.uniformBlocks[1].flushedThisFrame = false;
|
||||
st.fanIndexBufferOffset = 0;
|
||||
@@ -613,22 +658,24 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
endFrameUploadFlushNoWait();
|
||||
|
||||
final FrameState f = frameManager.frame();
|
||||
if (f.frameActive && !f.swapchainUsedThisFrame) {
|
||||
if (f.frameActive && !f.fbo0UsedThisFrame) {
|
||||
if (!emptyFrameWarned) {
|
||||
emptyFrameWarned = true;
|
||||
LOG.info("Frame {} ended without drawing to the swapchain; skipping present (further occurrences counted in sdl.emptyFrames)", f.frameNumber);
|
||||
LOG.info("Frame {} ended without drawing to FBO 0; presenting the previous contents (further occurrences counted in sdl.emptyFrames)", f.frameNumber);
|
||||
}
|
||||
frameManager.markFrameEmpty(f);
|
||||
}
|
||||
|
||||
awaitUploadFlush();
|
||||
frameManager.endFrame();
|
||||
frameManager.presentFinalTarget();
|
||||
fenceTracker.resolvePendingFences();
|
||||
resourceManager.flushDeferredReleases();
|
||||
resourceManager.recycleGpuBufferPool(frameManager.getFrameNumber());
|
||||
}
|
||||
|
||||
@Override public void onPreSwapchainInvalidatingChange(Object change) {
|
||||
if (presenter != null) presenter.drain();
|
||||
endFrameUploadFlush();
|
||||
if (frameManager.isFrameActive()) {
|
||||
frameManager.endFrame();
|
||||
@@ -869,18 +916,19 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public void clearDepth(double depth) { s().depthClearValue = (float) depth; }
|
||||
@Override public void clearStencil(int s) {}
|
||||
@Override public void clearStencil(int s) { s().stencilClearValue = s & 0xFF; }
|
||||
|
||||
@Override public void clear(int mask) {
|
||||
final ContextState cs = s();
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
final boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
|
||||
final boolean wantDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
|
||||
if (!wantColor && !wantDepth) return;
|
||||
final boolean wantStencil = (mask & GL11.GL_STENCIL_BUFFER_BIT) != 0;
|
||||
if (!wantColor && !wantDepth && !wantStencil) return;
|
||||
|
||||
final ContextState st = cs;
|
||||
|
||||
// Swapchain path: just record pending state; ensureSwapchainRenderPass consumes it lazily.
|
||||
// FBO0 path: just record pending state; ensureFbo0RenderPass consumes it lazily.
|
||||
if (st.boundFboId == 0) {
|
||||
if (wantColor) {
|
||||
st.pendingSwapchainClear = true;
|
||||
@@ -889,7 +937,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
st.pendingSwapchainB = st.clearB;
|
||||
st.pendingSwapchainA = st.clearA;
|
||||
}
|
||||
// swapchain has no depth attachment in this backend; ignore wantDepth
|
||||
final int swapDepthFormat = resourceManager.getSwapchainDepthStencilFormat();
|
||||
if (wantDepth && swapDepthFormat != 0) {
|
||||
st.pendingSwapchainDepthClear = true;
|
||||
st.pendingSwapchainDepth = st.depthClearValue;
|
||||
}
|
||||
if (wantStencil && PixelOps.isDepthStencilFormat(swapDepthFormat)) {
|
||||
st.pendingSwapchainStencilClear = true;
|
||||
st.pendingSwapchainStencil = st.stencilClearValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -914,6 +970,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, cs.depthClearValue);
|
||||
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
|
||||
}
|
||||
if (wantStencil && fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat)) {
|
||||
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, cs.stencilClearValue);
|
||||
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
|
||||
}
|
||||
|
||||
if (affectsActivePass) {
|
||||
frameManager.endRenderPassIfActive();
|
||||
@@ -1459,14 +1519,12 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (framebuffer == 0) {
|
||||
cs.pipeline.setColorTargetFormats(cachedSwapchainFormatArray);
|
||||
cs.pipeline.setDrawBuffers(null);
|
||||
cs.pipeline.hasDepthTarget = false;
|
||||
cs.pipeline.depthTargetFormat = 0;
|
||||
cs.pipeline.setDepthTargetFormat(resourceManager.getSwapchainDepthStencilFormat());
|
||||
} else {
|
||||
final FboState fbo = resourceManager.getFbo(framebuffer);
|
||||
if (fbo != null && fbo.getColorTexture() != 0) {
|
||||
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
|
||||
cs.pipeline.hasDepthTarget = fbo.depthTexture != 0;
|
||||
cs.pipeline.depthTargetFormat = fbo.depthFormat;
|
||||
cs.pipeline.setDepthTargetFormat(fbo.depthTexture != 0 ? fbo.depthFormat : 0);
|
||||
}
|
||||
}
|
||||
cs.pipeline.markOutputDirty();
|
||||
@@ -1503,14 +1561,23 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
fbo.cachedFormatsDirty = true;
|
||||
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
|
||||
cs.pipeline.markOutputDirty();
|
||||
} else if (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT) {
|
||||
} else if (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT || attachment == GL30.GL_STENCIL_ATTACHMENT) {
|
||||
final boolean stencilOnly = attachment == GL30.GL_STENCIL_ATTACHMENT;
|
||||
if (texture == 0) {
|
||||
if (stencilOnly && fbo.depthGlId != 0) return;
|
||||
fbo.detachDepth();
|
||||
cs.pipeline.hasDepthTarget = false;
|
||||
cs.pipeline.depthTargetFormat = 0;
|
||||
cs.pipeline.markOutputDirty();
|
||||
return;
|
||||
}
|
||||
if (stencilOnly && fbo.depthGlId != 0 && fbo.depthGlId != texture) {
|
||||
if (!warnedSplitStencilAttachment) {
|
||||
warnedSplitStencilAttachment = true;
|
||||
LOG.warn("Separate stencil attachment (tex {}) on an FBO whose depth attachment is tex {}; SDL has no separate stencil aspect, keeping depth", texture, fbo.depthGlId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
final long handle = resourceManager.ensureTextureUsage(texture, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
fbo.depthTexture = handle;
|
||||
@@ -1619,7 +1686,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long texHandle;
|
||||
final int srcSdlFormat;
|
||||
if (cs.boundReadFboId == 0) {
|
||||
texHandle = frameManager.getSwapchainTexture();
|
||||
texHandle = frameManager.getFbo0Texture();
|
||||
srcSdlFormat = frameManager.getSwapchainFormat();
|
||||
} else {
|
||||
final FboState fbo = resourceManager.getFbo(cs.boundReadFboId);
|
||||
@@ -1674,22 +1741,67 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public int getFramebufferAttachmentParameteri(int target, int attachment, int pname) {
|
||||
final FboState fbo = resourceManager.getFbo(s().boundFboId);
|
||||
final ContextState cs = s();
|
||||
final int fboId = target == GL30.GL_READ_FRAMEBUFFER ? cs.boundReadFboId : cs.boundFboId;
|
||||
if (fboId == 0) {
|
||||
final int colorFormat = cachedSwapchainFormatArray != null ? cachedSwapchainFormatArray[0] : 0;
|
||||
return defaultFramebufferAttachmentParameteri(attachment, pname, colorFormat, resourceManager.getSwapchainDepthStencilFormat());
|
||||
}
|
||||
return fboAttachmentParameteri(resourceManager.getFbo(fboId), attachment, pname);
|
||||
}
|
||||
|
||||
static int fboAttachmentParameteri(FboState fbo, int attachment, int pname) {
|
||||
if (fbo == null) return 0;
|
||||
final int colorIdx = attachment - GL30.GL_COLOR_ATTACHMENT0;
|
||||
final boolean isColor = (colorIdx >= 0 && colorIdx < ContextState.MAX_COLOR_ATTACHMENTS);
|
||||
final boolean isDepth = (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT);
|
||||
final boolean isDepth = (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT || attachment == GL30.GL_STENCIL_ATTACHMENT || attachment == GL11.GL_DEPTH || attachment == GL11.GL_STENCIL);
|
||||
final boolean colorPresent = isColor && fbo.colorGlIds[colorIdx] != 0;
|
||||
final boolean depthPresent = isDepth && fbo.depthGlId != 0;
|
||||
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) {
|
||||
return isColor ? fbo.colorGlIds[colorIdx] : isDepth ? fbo.depthGlId : 0;
|
||||
return colorPresent ? fbo.colorGlIds[colorIdx] : depthPresent ? fbo.depthGlId : 0;
|
||||
}
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) {
|
||||
if (isColor && fbo.colorGlIds[colorIdx] != 0) return GL11.GL_TEXTURE;
|
||||
if (isDepth && fbo.depthGlId != 0) return GL11.GL_TEXTURE;
|
||||
return GL11.GL_NONE;
|
||||
return (colorPresent || depthPresent) ? GL11.GL_TEXTURE : GL11.GL_NONE;
|
||||
}
|
||||
if (colorPresent) return colorAttachmentSize(fbo.colorFormats[colorIdx], pname);
|
||||
if (depthPresent) return depthAttachmentSize(fbo.depthFormat, pname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int defaultFramebufferAttachmentParameteri(int attachment, int pname, int colorFormat, int depthFormat) {
|
||||
final boolean isDepth = attachment == GL11.GL_DEPTH || attachment == GL11.GL_STENCIL || attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_STENCIL_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
if (isDepth) {
|
||||
if (depthFormat == 0) {
|
||||
return pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE ? GL11.GL_NONE : 0;
|
||||
}
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) return GL30.GL_FRAMEBUFFER_DEFAULT;
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) return 0;
|
||||
return depthAttachmentSize(depthFormat, pname);
|
||||
}
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) return GL30.GL_FRAMEBUFFER_DEFAULT;
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) return 0;
|
||||
return colorAttachmentSize(colorFormat, pname);
|
||||
}
|
||||
|
||||
private static int colorAttachmentSize(int sdlFormat, int pname) {
|
||||
return switch (pname) {
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE -> PixelOps.colorChannelBits(sdlFormat, 0);
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE -> PixelOps.colorChannelBits(sdlFormat, 1);
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE -> PixelOps.colorChannelBits(sdlFormat, 2);
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE -> PixelOps.colorChannelBits(sdlFormat, 3);
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int depthAttachmentSize(int sdlFormat, int pname) {
|
||||
return switch (pname) {
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE -> PixelOps.depthBits(sdlFormat);
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE -> PixelOps.stencilBits(sdlFormat);
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Override public int createShader(int type) { return shaderManager.createShader(type); }
|
||||
@Override public void deleteShader(int shader) { shaderManager.deleteShader(shader); }
|
||||
@Override public void shaderSource(int shader, CharSequence source) { shaderManager.shaderSource(shader, source); }
|
||||
@@ -3199,6 +3311,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId);
|
||||
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId);
|
||||
case GL11.GL_TEXTURE_BINDING_2D -> boundTextureOf(cs.activeTextureUnit, cs.boundTextures);
|
||||
case GL11.GL_DEPTH_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
|
||||
case GL11.GL_STENCIL_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
|
||||
default -> {
|
||||
final int limit = deviceLimit(pname);
|
||||
yield limit >= 0 ? limit : unknownGetInteger(pname);
|
||||
@@ -3646,6 +3760,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
private boolean warnedMSAARenderbuffer = false;
|
||||
private boolean warnedSplitStencilAttachment = false;
|
||||
|
||||
@Override
|
||||
public void renderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.DebugMessageRelay;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -18,6 +20,7 @@ import org.lwjgl.system.Platform;
|
||||
import org.lwjglx.opengl.Display;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
@@ -29,7 +32,11 @@ import static org.lwjgl.system.MemoryUtil.memUTF8;
|
||||
public final class Device {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
public static final int FRAMES_IN_FLIGHT = 3;
|
||||
public static final int MAX_FRAMES_IN_FLIGHT = 3;
|
||||
|
||||
private static final int FRAMES_IN_FLIGHT = Math.min(Math.max(SystemProperties.SDL_FRAMES_IN_FLIGHT, 1), MAX_FRAMES_IN_FLIGHT);
|
||||
|
||||
public static int framesInFlight() { return FRAMES_IN_FLIGHT; }
|
||||
|
||||
private static final int REQUESTED_SHADER_FORMATS = SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_DXBC | SDL_GPU_SHADERFORMAT_DXIL;
|
||||
|
||||
@@ -40,7 +47,8 @@ public final class Device {
|
||||
private String deviceName;
|
||||
private String driverVersion;
|
||||
private String driverInfo;
|
||||
private int presentMode = SDL_GPU_PRESENTMODE_VSYNC;
|
||||
private VSyncMode vsyncMode = VSyncMode.ON;
|
||||
private volatile Thread windowThread;
|
||||
private SDL_LogOutputFunction logCallback; // prevent GC
|
||||
|
||||
private volatile boolean lost;
|
||||
@@ -146,12 +154,12 @@ public final class Device {
|
||||
throw new RuntimeException("Failed to claim window for SDL GPU device: " + SDLError.SDL_GetError());
|
||||
}
|
||||
claimed = true;
|
||||
windowThread = Thread.currentThread();
|
||||
|
||||
final int n = FRAMES_IN_FLIGHT;
|
||||
if (!SDL_SetGPUAllowedFramesInFlight(device, n)) {
|
||||
LOG.warn("SDL_SetGPUAllowedFramesInFlight({}) failed: {}", n, SDLError.SDL_GetError());
|
||||
if (!SDL_SetGPUAllowedFramesInFlight(device, FRAMES_IN_FLIGHT)) {
|
||||
LOG.warn("SDL_SetGPUAllowedFramesInFlight({}) failed: {}", FRAMES_IN_FLIGHT, SDLError.SDL_GetError());
|
||||
} else {
|
||||
LOG.info("SDL frames-in-flight set to {}", n);
|
||||
LOG.info("SDL frames-in-flight set to {}", FRAMES_IN_FLIGHT);
|
||||
}
|
||||
|
||||
logWindowDiagnostics(window);
|
||||
@@ -260,6 +268,10 @@ public final class Device {
|
||||
return driverName;
|
||||
}
|
||||
|
||||
public Thread getWindowThread() {
|
||||
return windowThread;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
@@ -288,22 +300,89 @@ public final class Device {
|
||||
return (supportedShaderFormats & SDL_GPU_SHADERFORMAT_DXBC) != 0;
|
||||
}
|
||||
|
||||
public void setVSyncEnabled(boolean enabled) {
|
||||
private static final List<VSyncMode> ON_ORDER = List.of(VSyncMode.ON);
|
||||
private static final List<VSyncMode> OFF_ORDER = List.of(VSyncMode.OFF, VSyncMode.ON);
|
||||
private static final List<VSyncMode> MAILBOX_ORDER = List.of(VSyncMode.MAILBOX, VSyncMode.ON);
|
||||
|
||||
private static List<VSyncMode> presentModeOrder(VSyncMode preferred) {
|
||||
return switch (preferred) {
|
||||
case MAILBOX -> MAILBOX_ORDER;
|
||||
case OFF -> OFF_ORDER;
|
||||
default -> ON_ORDER;
|
||||
};
|
||||
}
|
||||
|
||||
public VSyncMode chooseVSyncMode(VSyncMode preferred) {
|
||||
for (VSyncMode candidate : presentModeOrder(preferred)) {
|
||||
if (supportsVSyncMode(candidate)) return candidate;
|
||||
}
|
||||
return vsyncMode;
|
||||
}
|
||||
|
||||
public VSyncMode applyVSync(VSyncMode preferred) {
|
||||
if (device == 0 || !claimed) return vsyncMode;
|
||||
final long window = Display.getWindow();
|
||||
final int mode;
|
||||
if (enabled) {
|
||||
mode = SDL_GPU_PRESENTMODE_VSYNC;
|
||||
} else if (SDL_WindowSupportsGPUPresentMode(device, window, SDL_GPU_PRESENTMODE_MAILBOX)) {
|
||||
mode = SDL_GPU_PRESENTMODE_MAILBOX;
|
||||
} else {
|
||||
mode = SDL_GPU_PRESENTMODE_IMMEDIATE;
|
||||
final List<VSyncMode> order = presentModeOrder(preferred);
|
||||
for (VSyncMode candidate : order) {
|
||||
final int sdlMode = toSdl(candidate);
|
||||
if (!SDL_WindowSupportsGPUPresentMode(device, window, sdlMode)) continue;
|
||||
if (!SDL_SetGPUSwapchainParameters(device, window, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, sdlMode)) {
|
||||
LOG.warn("Failed to set present mode {}: {}", candidate, SDLError.SDL_GetError());
|
||||
continue;
|
||||
}
|
||||
if (candidate != order.get(0)) {
|
||||
LOG.warn("Present mode {} is not supported by this window; using {} instead", order.get(0), candidate);
|
||||
}
|
||||
vsyncMode = candidate;
|
||||
LOG.info("SDL present mode {} (preferred={}), refresh {}Hz, {}, video={}, framesInFlight={}, supported: vsync={} immediate={} mailbox={}",
|
||||
candidate, preferred, getDisplayRefreshRateHz(),
|
||||
(SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0 ? "fullscreen" : "windowed",
|
||||
SDL_GetCurrentVideoDriver(), FRAMES_IN_FLIGHT,
|
||||
supportsVSyncMode(VSyncMode.ON), supportsVSyncMode(VSyncMode.OFF), supportsVSyncMode(VSyncMode.MAILBOX));
|
||||
return candidate;
|
||||
}
|
||||
if (!SDL_SetGPUSwapchainParameters(device, window, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, mode)) {
|
||||
LOG.warn("Failed to set present mode {}: {} (keeping prior mode {})", mode, SDLError.SDL_GetError(), presentMode);
|
||||
return;
|
||||
LOG.warn("No usable present mode for preference {}; keeping {}", preferred, vsyncMode);
|
||||
return vsyncMode;
|
||||
}
|
||||
|
||||
public boolean supportsVSyncMode(VSyncMode mode) {
|
||||
return device != 0 && claimed && SDL_WindowSupportsGPUPresentMode(device, Display.getWindow(), toSdl(mode));
|
||||
}
|
||||
|
||||
public long getClaimedWindow() {
|
||||
return device != 0 && claimed ? Display.getWindow() : 0L;
|
||||
}
|
||||
|
||||
public boolean wasWindowResized() {
|
||||
return getClaimedWindow() != 0L && Display.wasResized();
|
||||
}
|
||||
|
||||
public long getWindowSizeInPixels() {
|
||||
final long window = getClaimedWindow();
|
||||
if (window == 0) return 0L;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
final IntBuffer w = stack.mallocInt(1);
|
||||
final IntBuffer h = stack.mallocInt(1);
|
||||
SDL_GetWindowSizeInPixels(window, w, h);
|
||||
return ((long) w.get(0) << 32) | (h.get(0) & 0xFFFFFFFFL);
|
||||
}
|
||||
presentMode = mode;
|
||||
LOG.info("SDL present mode set to {}", mode);
|
||||
}
|
||||
|
||||
private static int toSdl(VSyncMode mode) {
|
||||
return switch (mode) {
|
||||
case OFF -> SDL_GPU_PRESENTMODE_IMMEDIATE;
|
||||
case MAILBOX -> SDL_GPU_PRESENTMODE_MAILBOX;
|
||||
default -> SDL_GPU_PRESENTMODE_VSYNC;
|
||||
};
|
||||
}
|
||||
|
||||
public int getDisplayRefreshRateHz() {
|
||||
final long window = Display.getWindow();
|
||||
final int displayId = window == 0 ? SDL_GetPrimaryDisplay() : SDL_GetDisplayForWindow(window);
|
||||
if (displayId == 0) return 0;
|
||||
final SDL_DisplayMode mode = SDL_GetCurrentDisplayMode(displayId);
|
||||
if (mode == null) return 0;
|
||||
return RenderBackend.refreshHzFrom(mode.refresh_rate_numerator(), mode.refresh_rate_denominator(), mode.refresh_rate());
|
||||
}
|
||||
|
||||
public int getSwapchainTextureFormat() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
@@ -92,6 +93,7 @@ public final class ContextState {
|
||||
public float blendColorR, blendColorG, blendColorB, blendColorA;
|
||||
public int stencilRef;
|
||||
public float depthClearValue = 1.0f;
|
||||
public int stencilClearValue;
|
||||
public boolean primitiveRestartEnabled;
|
||||
public int primitiveRestartSentinel;
|
||||
|
||||
@@ -292,13 +294,20 @@ public final class ContextState {
|
||||
public int boundReadFboId;
|
||||
|
||||
public final LongOpenHashSet clearedTexturesThisFrame = new LongOpenHashSet();
|
||||
public final LongOpenHashSet clearedStencilTexturesThisFrame = new LongOpenHashSet();
|
||||
|
||||
public final LongOpenHashSet pendingColorTextures = new LongOpenHashSet();
|
||||
public final Long2ObjectOpenHashMap<float[]> pendingColorValues = new Long2ObjectOpenHashMap<>();
|
||||
public final LongOpenHashSet pendingDepthTextures = new LongOpenHashSet();
|
||||
public final Long2FloatOpenHashMap pendingDepthValues = new Long2FloatOpenHashMap();
|
||||
public final LongOpenHashSet pendingStencilTextures = new LongOpenHashSet();
|
||||
public final Long2IntOpenHashMap pendingStencilValues = new Long2IntOpenHashMap();
|
||||
public boolean pendingSwapchainClear;
|
||||
public float pendingSwapchainR, pendingSwapchainG, pendingSwapchainB, pendingSwapchainA;
|
||||
public boolean pendingSwapchainDepthClear;
|
||||
public boolean pendingSwapchainStencilClear;
|
||||
public float pendingSwapchainDepth;
|
||||
public int pendingSwapchainStencil;
|
||||
|
||||
public final LongArrayList samplerFlushColorHandles = new LongArrayList();
|
||||
public final IntArrayList samplerFlushColorGlIds = new IntArrayList();
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.UploadArena;
|
||||
@@ -16,16 +17,20 @@ import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDL_FColor;
|
||||
import org.lwjgl.sdl.SDL_GPUBlitInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUBlitRegion;
|
||||
import org.lwjgl.sdl.SDL_GPUColorTargetInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUDepthStencilTargetInfo;
|
||||
import org.lwjglx.opengl.Display;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
|
||||
import static org.lwjgl.system.MemoryStack.*;
|
||||
|
||||
/**
|
||||
@@ -37,7 +42,6 @@ public final class FrameManager {
|
||||
private static final Tracy.ZoneId Z_SDL_FENCE_WAIT = Tracy.zoneId("sdlFenceWait", Tracy.COLOR_SWAP);
|
||||
private static final Tracy.ZoneId Z_SDL_SUBMIT = Tracy.zoneId("sdlSubmit", Tracy.COLOR_SWAP);
|
||||
private static final Tracy.ZoneId Z_SDL_ACQUIRE_WAIT = Tracy.zoneId("sdlAcquireWait", Tracy.COLOR_SWAP);
|
||||
private static final Tracy.ZoneId Z_SDL_SWAPCHAIN_WAIT = Tracy.zoneId("sdlSwapchainWait", Tracy.COLOR_SWAP);
|
||||
|
||||
private final Device device;
|
||||
private ResourceManager resourceManager;
|
||||
@@ -48,19 +52,16 @@ public final class FrameManager {
|
||||
public long renderPass;
|
||||
public long copyPass;
|
||||
public boolean computePassOpen;
|
||||
public long swapchainTexture;
|
||||
public int swapchainWidth;
|
||||
public int swapchainHeight;
|
||||
public boolean frameActive;
|
||||
public long currentColorTarget;
|
||||
public long currentDepthTarget;
|
||||
public long activeLayoutHash;
|
||||
public long frameNumber;
|
||||
public boolean clearedThisFrame;
|
||||
public boolean swapchainUsedThisFrame;
|
||||
public boolean swapchainAcquiredThisFrame;
|
||||
public boolean depthClearedThisFrame;
|
||||
public boolean fbo0UsedThisFrame;
|
||||
public boolean presentedThisFrame;
|
||||
public boolean swapchainUnavailable;
|
||||
public boolean skipPresent;
|
||||
public long renderPassGeneration;
|
||||
|
||||
public boolean wantFenceOnNextSubmit;
|
||||
@@ -82,13 +83,17 @@ public final class FrameManager {
|
||||
public int batchBreakSamplerThisFrame;
|
||||
public int stateAppliesThisFrame;
|
||||
public long acquireWaitNanosThisFrame;
|
||||
public volatile long lastFrameGateNanos;
|
||||
public volatile long lastFrameGateEndNanos;
|
||||
public int midFrameSubmitsThisFrame;
|
||||
public int mipGensThisFrame;
|
||||
public int mipGenSubmitsThisFrame;
|
||||
public int uploadFlushSubmitsThisFrame;
|
||||
public int blitsThisFrame;
|
||||
public int presentSkipsThisFrame;
|
||||
public int emptyFramesThisFrame;
|
||||
public int droppedDrawsThisFrame;
|
||||
public int computeBatchJoinsThisFrame;
|
||||
public boolean drawableStarved;
|
||||
public int clearPassesThisFrame;
|
||||
public int swapchainPassesThisFrame;
|
||||
public int fboPassesThisFrame;
|
||||
@@ -144,42 +149,53 @@ public final class FrameManager {
|
||||
|
||||
private volatile boolean globalSyncAtNextBeginFrame;
|
||||
|
||||
private final CopyOnWriteArrayList<FrameState> registeredFrames = new CopyOnWriteArrayList<>();
|
||||
private final ThreadRegistry<FrameState> registeredFrames = new ThreadRegistry<>(new FrameState[0]);
|
||||
|
||||
private volatile FrameState soleFrame;
|
||||
|
||||
private final ThreadLocal<FrameState> tlFrame = ThreadLocal.withInitial(() -> {
|
||||
final FrameState f = new FrameState();
|
||||
registeredFrames.add(f);
|
||||
refreshSoleFrame();
|
||||
return f;
|
||||
});
|
||||
|
||||
private void refreshSoleFrame() {
|
||||
soleFrame = registeredFrames.size() == 1 ? registeredFrames.get(0) : null;
|
||||
}
|
||||
private final ThreadLocal<FrameState> tlFrame = new ThreadLocal<>();
|
||||
|
||||
public FrameState frame() {
|
||||
final FrameState f = soleFrame;
|
||||
if (f != null && f.owner == Thread.currentThread()) return f;
|
||||
return tlFrame.get();
|
||||
final FrameState sole = registeredFrames.sole();
|
||||
if (sole != null && sole.owner == Thread.currentThread()) return sole;
|
||||
final FrameState f = tlFrame.get();
|
||||
return f != null ? f : registerFrame();
|
||||
}
|
||||
|
||||
private FrameState registerFrame() {
|
||||
final FrameState f = new FrameState();
|
||||
tlFrame.set(f);
|
||||
registeredFrames.add(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
public int registeredFrameCount() {
|
||||
return registeredFrames.size();
|
||||
}
|
||||
|
||||
public FrameState busiestFrame() {
|
||||
final FrameState[] frames = registeredFrames.snapshot();
|
||||
FrameState best = null;
|
||||
for (final FrameState f : registeredFrames) {
|
||||
for (final FrameState f : frames) {
|
||||
if (best == null || f.frameNumber > best.frameNumber) best = f;
|
||||
}
|
||||
return best != null ? best : frame();
|
||||
}
|
||||
|
||||
public static final long SWAPCHAIN_LAYOUT_HASH = 0x5CA1AB1E5CA1AB1EL;
|
||||
public static final long FBO0_LAYOUT_HASH = 0x5CA1AB1E5CA1AB1EL;
|
||||
|
||||
private final OffscreenTarget finalTarget = new OffscreenTarget();
|
||||
|
||||
public OffscreenTarget finalTarget() { return finalTarget; }
|
||||
|
||||
public void destroyFinalTarget() {
|
||||
if (resourceManager != null) finalTarget.destroy(resourceManager);
|
||||
}
|
||||
|
||||
public static final long MAX_PENDING_UPLOAD_BYTES = 16L * 1024L * 1024L;
|
||||
public static final int MAX_PENDING_UPLOAD_COMMANDS = 256;
|
||||
|
||||
private Runnable beforeEndCopyPass;
|
||||
|
||||
|
||||
public FrameManager(Device device) {
|
||||
this.device = device;
|
||||
}
|
||||
@@ -188,18 +204,12 @@ public final class FrameManager {
|
||||
this.resourceManager = resourceManager;
|
||||
}
|
||||
|
||||
private static volatile boolean threadLogged;
|
||||
|
||||
public void beginFrame() {
|
||||
final FrameState f = frame();
|
||||
if (f.frameActive) {
|
||||
LOG.warn("beginFrame() called while frame already active on thread {}", Thread.currentThread().getName());
|
||||
return;
|
||||
}
|
||||
if (!threadLogged) {
|
||||
threadLogged = true;
|
||||
LOG.info("First beginFrame() on thread={}; must match the SDL window-creating thread", Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
flushPendingUploadCommandBuffer(f);
|
||||
if (globalSyncAtNextBeginFrame) {
|
||||
@@ -209,23 +219,15 @@ public final class FrameManager {
|
||||
finally { Tracy.endZone(); }
|
||||
}
|
||||
|
||||
if (f.drawableStarved) {
|
||||
f.drawableStarved = false;
|
||||
Tracy.beginZone(Z_SDL_SWAPCHAIN_WAIT);
|
||||
try { SDL_WaitForGPUSwapchain(device.getDevice(), Display.getWindow()); }
|
||||
finally { Tracy.endZone(); }
|
||||
}
|
||||
|
||||
f.commandBuffer = SDL_AcquireGPUCommandBuffer(device.getDevice());
|
||||
if (f.commandBuffer == 0) {
|
||||
throw new RuntimeException("Failed to acquire GPU command buffer: " + SDLError.SDL_GetError());
|
||||
}
|
||||
|
||||
f.swapchainTexture = 0;
|
||||
f.swapchainWidth = 0;
|
||||
f.swapchainHeight = 0;
|
||||
f.swapchainAcquiredThisFrame = false;
|
||||
f.swapchainUnavailable = false;
|
||||
f.presentedThisFrame = false;
|
||||
|
||||
maintainFinalTarget();
|
||||
|
||||
f.frameActive = true;
|
||||
f.renderPass = 0;
|
||||
@@ -233,23 +235,72 @@ public final class FrameManager {
|
||||
f.currentDepthTarget = 0;
|
||||
f.frameNumber++;
|
||||
f.clearedThisFrame = false;
|
||||
f.swapchainUsedThisFrame = false;
|
||||
f.skipPresent = false;
|
||||
f.depthClearedThisFrame = false;
|
||||
f.fbo0UsedThisFrame = false;
|
||||
}
|
||||
|
||||
private static final long FINAL_TARGET_SIZE_POLL_NANOS = 1_000_000_000L;
|
||||
|
||||
private long finalTargetPollNanos;
|
||||
private volatile Thread mainThread;
|
||||
|
||||
public void setMainThread(Thread thread) {
|
||||
this.mainThread = thread;
|
||||
}
|
||||
|
||||
// Resizing releases a texture an in-flight present may still reference, so drain before ensureSize.
|
||||
private void maintainFinalTarget() {
|
||||
if (resourceManager == null) return;
|
||||
final Thread owner = mainThread;
|
||||
if (owner != null && Thread.currentThread() != owner) return;
|
||||
|
||||
final long now = System.nanoTime();
|
||||
final boolean poll = finalTargetPollNanos == 0L || now - finalTargetPollNanos >= FINAL_TARGET_SIZE_POLL_NANOS;
|
||||
if (!poll && !device.wasWindowResized() && finalTarget.isReady()) return;
|
||||
if (poll) finalTargetPollNanos = now;
|
||||
|
||||
final long size = device.getWindowSizeInPixels();
|
||||
if (size == 0L) return;
|
||||
final int width = (int) (size >> 32);
|
||||
final int height = (int) size;
|
||||
if (finalTarget.isReady() && width == finalTarget.width() && height == finalTarget.height()) return;
|
||||
|
||||
if (presenter != null) presenter.drain();
|
||||
finalTarget.ensureSize(device, resourceManager, width, height);
|
||||
}
|
||||
|
||||
public void markFrameEmpty(FrameState f) {
|
||||
f.skipPresent = true;
|
||||
f.emptyFramesThisFrame++;
|
||||
}
|
||||
|
||||
public void ensureSwapchainRenderPass(float clearR, float clearG, float clearB, float clearA, boolean clear) {
|
||||
ensureSwapchainRenderPass(frame(), clearR, clearG, clearB, clearA, clear);
|
||||
static boolean swapchainClearNeedsPassBreak(ContextState st, FrameState f) {
|
||||
return (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) && f.renderPass != 0 && f.activeLayoutHash == FBO0_LAYOUT_HASH;
|
||||
}
|
||||
|
||||
public void ensureSwapchainRenderPass(FrameState f, float clearR, float clearG, float clearB, float clearA, boolean clear) {
|
||||
if (!ensureSwapchainAcquired(f)) return; // present-skip; don't bind tex=0
|
||||
if (f.renderPass != 0 && f.currentColorTarget == f.swapchainTexture) {
|
||||
return;
|
||||
public boolean ensureFbo0RenderPass(FrameState f, ContextState st) {
|
||||
final boolean clearColor = st.pendingSwapchainClear;
|
||||
if (swapchainClearNeedsPassBreak(st, f)) {
|
||||
endRenderPassIfActive(f);
|
||||
}
|
||||
final boolean applied = ensureFbo0RenderPass(f,
|
||||
clearColor ? st.pendingSwapchainR : st.clearR,
|
||||
clearColor ? st.pendingSwapchainG : st.clearG,
|
||||
clearColor ? st.pendingSwapchainB : st.clearB,
|
||||
clearColor ? st.pendingSwapchainA : st.clearA,
|
||||
clearColor,
|
||||
st.pendingSwapchainDepthClear, st.pendingSwapchainDepthClear ? st.pendingSwapchainDepth : st.depthClearValue,
|
||||
st.pendingSwapchainStencilClear, st.pendingSwapchainStencilClear ? st.pendingSwapchainStencil : st.stencilClearValue);
|
||||
if (!applied) return false;
|
||||
st.pendingSwapchainClear = false;
|
||||
st.pendingSwapchainDepthClear = false;
|
||||
st.pendingSwapchainStencilClear = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean ensureFbo0RenderPass(FrameState f, float clearR, float clearG, float clearB, float clearA, boolean clear, boolean clearDepth, float depthValue, boolean clearStencil, int stencilValue) {
|
||||
if (!finalTarget.isReady() || f.commandBuffer == 0) return false;
|
||||
if (f.renderPass != 0 && f.activeLayoutHash == FBO0_LAYOUT_HASH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
endActiveEncoders(f);
|
||||
@@ -258,10 +309,16 @@ public final class FrameManager {
|
||||
clear = true;
|
||||
}
|
||||
|
||||
final long depthTexture = resourceManager != null ? resourceManager.getOrCreateSwapchainDepthStencil(finalTarget.width(), finalTarget.height()) : 0L;
|
||||
if (depthTexture != 0 && !f.depthClearedThisFrame) {
|
||||
clearDepth = true;
|
||||
clearStencil = true;
|
||||
}
|
||||
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUColorTargetInfo.Buffer colorTargets = SDL_GPUColorTargetInfo.calloc(1, stack);
|
||||
final long ctAddr = colorTargets.get(0).address();
|
||||
MemoryAccess.putAddress(ctAddr + SDL_GPUColorTargetInfo.TEXTURE, f.swapchainTexture);
|
||||
MemoryAccess.putAddress(ctAddr + SDL_GPUColorTargetInfo.TEXTURE, finalTarget.colorTexture());
|
||||
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.LOAD_OP, clear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
|
||||
@@ -274,19 +331,35 @@ public final class FrameManager {
|
||||
f.clearedThisFrame = true;
|
||||
}
|
||||
|
||||
SDL_GPUDepthStencilTargetInfo depthTarget = null;
|
||||
if (depthTexture != 0) {
|
||||
depthTarget = SDL_GPUDepthStencilTargetInfo.calloc(stack);
|
||||
final long dtAddr = depthTarget.address();
|
||||
MemoryAccess.putAddress(dtAddr + SDL_GPUDepthStencilTargetInfo.TEXTURE, depthTexture);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, clearDepth ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putFloat(dtAddr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, depthValue);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, clearStencil ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putByte(dtAddr + SDL_GPUDepthStencilTargetInfo.CLEAR_STENCIL, (byte) stencilValue);
|
||||
if (clearDepth && clearStencil) f.depthClearedThisFrame = true;
|
||||
}
|
||||
|
||||
if (preRenderPassHook != null) preRenderPassHook.run();
|
||||
assertNoEncoderActive(f, "SDL_BeginGPURenderPass(swapchain)");
|
||||
f.renderPass = SDL_BeginGPURenderPass(f.commandBuffer, colorTargets, null);
|
||||
f.renderPass = SDL_BeginGPURenderPass(f.commandBuffer, colorTargets, depthTarget);
|
||||
if (f.renderPass == 0) {
|
||||
throw new RuntimeException("Failed to begin render pass: " + SDLError.SDL_GetError());
|
||||
}
|
||||
f.renderPassGeneration++;
|
||||
f.swapchainPassesThisFrame++;
|
||||
f.currentColorTarget = f.swapchainTexture;
|
||||
f.currentDepthTarget = 0;
|
||||
f.activeLayoutHash = SWAPCHAIN_LAYOUT_HASH;
|
||||
f.swapchainUsedThisFrame = true;
|
||||
f.currentColorTarget = finalTarget.colorTexture();
|
||||
f.currentDepthTarget = depthTexture;
|
||||
f.activeLayoutHash = FBO0_LAYOUT_HASH;
|
||||
f.fbo0UsedThisFrame = true;
|
||||
finalTarget.markContent();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -336,6 +409,8 @@ public final class FrameManager {
|
||||
|
||||
public void noteMipGen() { frame().mipGensThisFrame++; }
|
||||
|
||||
public void noteMipGenSubmit() { frame().mipGenSubmitsThisFrame++; }
|
||||
|
||||
public void noteBlit() { frame().blitsThisFrame++; }
|
||||
|
||||
public void noteClearPass() { frame().clearPassesThisFrame++; }
|
||||
@@ -389,6 +464,24 @@ public final class FrameManager {
|
||||
}
|
||||
}
|
||||
|
||||
private volatile boolean acquireThreadReported;
|
||||
|
||||
static boolean acquireAffinityViolated(Thread self, Thread window) {
|
||||
return window != null && window != self;
|
||||
}
|
||||
|
||||
private void checkAcquireOnWindowThread() {
|
||||
if (acquireThreadReported) return;
|
||||
final Thread self = Thread.currentThread();
|
||||
final Thread window = device.getWindowThread();
|
||||
if (!acquireAffinityViolated(self, window)) return;
|
||||
acquireThreadReported = true;
|
||||
|
||||
final String message = "Swapchain acquired on '" + self.getName() + "', window created on '" + window.getName() + "'";
|
||||
if (SystemProperties.isDeobf() && !SystemProperties.DISABLE_SDL_PRESENTER_THREAD) throw new IllegalStateException(message);
|
||||
LOG.warn(message);
|
||||
}
|
||||
|
||||
public long ensureCopyPass() {
|
||||
final FrameState f = frame();
|
||||
if (f.copyPass != 0) {
|
||||
@@ -425,17 +518,14 @@ public final class FrameManager {
|
||||
}
|
||||
|
||||
public void requestFlushOnAllRegisteredFrames() {
|
||||
final int n = registeredFrames.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
registeredFrames.get(i).flushRequested = true;
|
||||
for (final FrameState f : registeredFrames.snapshot()) {
|
||||
f.flushRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void requestSyncFlushOnAllRegisteredFrames() {
|
||||
globalSyncAtNextBeginFrame = true;
|
||||
final int n = registeredFrames.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
final FrameState f = registeredFrames.get(i);
|
||||
for (final FrameState f : registeredFrames.snapshot()) {
|
||||
f.flushRequested = true;
|
||||
f.syncOnNextFlush = true;
|
||||
}
|
||||
@@ -462,10 +552,6 @@ public final class FrameManager {
|
||||
endCopyPassIfActive(f);
|
||||
endRenderPassIfActive(f);
|
||||
|
||||
if (!f.swapchainAcquiredThisFrame && !f.swapchainUnavailable && !f.skipPresent && f.commandBuffer != 0) {
|
||||
ensureSwapchainAcquired(f);
|
||||
}
|
||||
|
||||
if (f.commandBuffer != 0) {
|
||||
Tracy.beginZone(Z_SDL_SUBMIT);
|
||||
try {
|
||||
@@ -486,9 +572,14 @@ public final class FrameManager {
|
||||
f.commandBuffer = 0;
|
||||
}
|
||||
|
||||
f.drawableStarved = f.presentSkipsThisFrame > 0;
|
||||
drainWindowPresentCounters(f);
|
||||
|
||||
if (Tracy.ENABLED && resourceManager != null) {
|
||||
SdlFramePlots.emit(f, resourceManager);
|
||||
}
|
||||
|
||||
f.midFrameSubmitsThisFrame = 0;
|
||||
|
||||
if (Tracy.ENABLED && resourceManager != null) SdlFramePlots.emit(f, resourceManager);
|
||||
f.lastPlottedRenderPassGen = f.renderPassGeneration;
|
||||
f.computePassesThisFrame = 0;
|
||||
f.submitsThisFrame = 0;
|
||||
@@ -506,6 +597,8 @@ public final class FrameManager {
|
||||
f.stateAppliesThisFrame = 0;
|
||||
f.acquireWaitNanosThisFrame = 0;
|
||||
f.mipGensThisFrame = 0;
|
||||
f.mipGenSubmitsThisFrame = 0;
|
||||
f.uploadFlushSubmitsThisFrame = 0;
|
||||
f.blitsThisFrame = 0;
|
||||
f.clearPassesThisFrame = 0;
|
||||
f.swapchainPassesThisFrame = 0;
|
||||
@@ -520,9 +613,6 @@ public final class FrameManager {
|
||||
f.computeBatchJoinsThisFrame = 0;
|
||||
|
||||
f.frameActive = false;
|
||||
f.swapchainTexture = 0;
|
||||
|
||||
if (afterPresent != null) afterPresent.run();
|
||||
}
|
||||
|
||||
public void submitMidFrame() {
|
||||
@@ -546,6 +636,7 @@ public final class FrameManager {
|
||||
Tracy.endZone();
|
||||
}
|
||||
f.submitsThisFrame++;
|
||||
f.midFrameSubmitsThisFrame++;
|
||||
f.commandBuffer = 0;
|
||||
if (f.frameActive) {
|
||||
reacquireCommandBuffer();
|
||||
@@ -557,6 +648,7 @@ public final class FrameManager {
|
||||
if (f.frameActive) {
|
||||
endFrame();
|
||||
}
|
||||
presentFinalTarget();
|
||||
beginFrame();
|
||||
}
|
||||
|
||||
@@ -633,6 +725,7 @@ public final class FrameManager {
|
||||
device.reportGpuFailure("submit pending upload command buffer");
|
||||
}
|
||||
f.submitsThisFrame++;
|
||||
f.uploadFlushSubmitsThisFrame++;
|
||||
f.pendingUploadCommandBuffer = 0;
|
||||
f.pendingUploadBytes = 0;
|
||||
f.pendingUploadCommands = 0;
|
||||
@@ -667,22 +760,16 @@ public final class FrameManager {
|
||||
|
||||
public long getActiveLayoutHash(FrameState f) { return f.activeLayoutHash; }
|
||||
|
||||
public long getSwapchainTexture() {
|
||||
final FrameState f = frame();
|
||||
ensureSwapchainAcquired(f);
|
||||
return f.swapchainTexture;
|
||||
public long getFbo0Texture() {
|
||||
return finalTarget.colorTexture();
|
||||
}
|
||||
|
||||
public int getSwapchainWidth() {
|
||||
final FrameState f = frame();
|
||||
ensureSwapchainAcquired(f);
|
||||
return f.swapchainWidth;
|
||||
public int getFbo0Width() {
|
||||
return finalTarget.width();
|
||||
}
|
||||
|
||||
public int getSwapchainHeight() {
|
||||
final FrameState f = frame();
|
||||
ensureSwapchainAcquired(f);
|
||||
return f.swapchainHeight;
|
||||
public int getFbo0Height() {
|
||||
return finalTarget.height();
|
||||
}
|
||||
|
||||
public int getSwapchainFormat() {
|
||||
@@ -717,8 +804,8 @@ public final class FrameManager {
|
||||
return frame().frameNumber;
|
||||
}
|
||||
|
||||
public boolean isSwapchainUsedThisFrame() {
|
||||
return frame().swapchainUsedThisFrame;
|
||||
public boolean isFbo0UsedThisFrame() {
|
||||
return frame().fbo0UsedThisFrame;
|
||||
}
|
||||
|
||||
public void reacquireCommandBuffer() {
|
||||
@@ -731,24 +818,18 @@ public final class FrameManager {
|
||||
f.currentDepthTarget = 0;
|
||||
|
||||
if (f.frameActive) {
|
||||
final boolean wasAcquired = f.swapchainAcquiredThisFrame;
|
||||
f.swapchainAcquiredThisFrame = false;
|
||||
f.swapchainTexture = 0;
|
||||
if (wasAcquired && !ensureSwapchainAcquired(f)) {
|
||||
LOG.warn("Failed to re-acquire swapchain after readback: {}", SDLError.SDL_GetError());
|
||||
}
|
||||
f.clearedThisFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseThreadState() {
|
||||
final FrameState f = frame();
|
||||
final FrameState f = tlFrame.get();
|
||||
if (f == null) return;
|
||||
if (resourceManager != null) resourceManager.flushUploadArena(f);
|
||||
flushPendingUploadCommandBuffer(f);
|
||||
releaseFrameStateResources(f);
|
||||
registeredFrames.remove(f);
|
||||
tlFrame.remove();
|
||||
refreshSoleFrame();
|
||||
registeredFrames.remove(f);
|
||||
}
|
||||
|
||||
private void releaseFrameStateResources(FrameState f) {
|
||||
@@ -786,25 +867,97 @@ public final class FrameManager {
|
||||
}
|
||||
|
||||
public void releaseAllRegisteredFrames() {
|
||||
for (FrameState f : registeredFrames) {
|
||||
for (final FrameState f : registeredFrames.snapshot()) {
|
||||
releaseFrameStateResources(f);
|
||||
}
|
||||
registeredFrames.clear();
|
||||
soleFrame = null;
|
||||
}
|
||||
|
||||
public boolean ensureSwapchainAcquired() {
|
||||
return ensureSwapchainAcquired(frame());
|
||||
public long lastFrameGateNanos() {
|
||||
return frame().lastFrameGateNanos;
|
||||
}
|
||||
|
||||
public boolean ensureSwapchainAcquired(FrameState f) {
|
||||
if (f.swapchainAcquiredThisFrame) return true;
|
||||
if (f.swapchainUnavailable) return false;
|
||||
if (!f.frameActive || f.commandBuffer == 0) return false;
|
||||
public long lastFrameGateEndNanos() {
|
||||
return frame().lastFrameGateEndNanos;
|
||||
}
|
||||
|
||||
endActiveEncoders(f);
|
||||
private volatile Presenter presenter;
|
||||
|
||||
final long t0 = Tracy.ENABLED ? System.nanoTime() : 0L;
|
||||
public void setPresenter(Presenter presenter) {
|
||||
this.presenter = presenter;
|
||||
}
|
||||
|
||||
private final FrameState windowFrame = new FrameState();
|
||||
|
||||
public FrameState windowFrame() {
|
||||
return windowFrame;
|
||||
}
|
||||
|
||||
public void presentFinalTarget() {
|
||||
if (!finalTarget.hasContent()) return;
|
||||
present(finalTarget.colorTexture(), finalTarget.width(), finalTarget.height(), SDL_FLIP_NONE, false);
|
||||
}
|
||||
|
||||
public void presentSplash(long srcTexture, int srcW, int srcH) {
|
||||
present(srcTexture, srcW, srcH, SDL_FLIP_VERTICAL, true);
|
||||
}
|
||||
|
||||
private void present(long srcTexture, int srcW, int srcH, int flipMode, boolean splash) {
|
||||
final FrameState f = frame();
|
||||
if (f.presentedThisFrame) return;
|
||||
final Presenter p = presenter;
|
||||
if (p != null) {
|
||||
f.presentedThisFrame = true;
|
||||
p.requestPresent(srcTexture, srcW, srcH, flipMode);
|
||||
} else {
|
||||
presentBlit(f, srcTexture, srcW, srcH, flipMode);
|
||||
}
|
||||
if (splash) f.presentedThisFrame = false;
|
||||
}
|
||||
|
||||
private final AtomicLong windowAcquireWaitNanos = new AtomicLong();
|
||||
private final AtomicInteger windowPresentSkips = new AtomicInteger();
|
||||
|
||||
private void drainWindowPresentCounters(FrameState f) {
|
||||
if (f == windowFrame) return;
|
||||
final long acquire = windowAcquireWaitNanos.getAndSet(0L);
|
||||
if (acquire != 0L) {
|
||||
f.acquireWaitNanosThisFrame += acquire;
|
||||
f.lastFrameGateNanos = windowFrame.lastFrameGateNanos;
|
||||
f.lastFrameGateEndNanos = windowFrame.lastFrameGateEndNanos;
|
||||
}
|
||||
f.presentSkipsThisFrame += windowPresentSkips.getAndSet(0);
|
||||
}
|
||||
|
||||
private void noteAcquireWait(FrameState f, long waitNanos) {
|
||||
if (f == windowFrame) windowAcquireWaitNanos.addAndGet(waitNanos);
|
||||
else f.acquireWaitNanosThisFrame += waitNanos;
|
||||
}
|
||||
|
||||
private void notePresentSkip(FrameState f) {
|
||||
if (f == windowFrame) windowPresentSkips.incrementAndGet();
|
||||
else f.presentSkipsThisFrame++;
|
||||
}
|
||||
|
||||
public void presentOnWindowThread(long srcTexture, int srcW, int srcH, int flipMode) {
|
||||
presentBlit(windowFrame, srcTexture, srcW, srcH, flipMode);
|
||||
windowFrame.presentedThisFrame = false;
|
||||
windowFrame.swapchainUnavailable = false;
|
||||
}
|
||||
|
||||
public boolean presentBlit(FrameState f, long srcTexture, int srcW, int srcH, int flipMode) {
|
||||
if (srcTexture == 0 || srcW <= 0 || srcH <= 0) return false;
|
||||
if (f.presentedThisFrame || f.swapchainUnavailable) return false;
|
||||
|
||||
checkAcquireOnWindowThread();
|
||||
|
||||
final long window = device.getClaimedWindow();
|
||||
if (window == 0) return false;
|
||||
|
||||
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
|
||||
if (cb == 0) return false;
|
||||
|
||||
final long t0 = System.nanoTime();
|
||||
final long tex;
|
||||
final int w;
|
||||
final int h;
|
||||
@@ -815,7 +968,7 @@ public final class FrameManager {
|
||||
final IntBuffer pHeight = stack.ints(0);
|
||||
Tracy.beginZone(Z_SDL_ACQUIRE_WAIT);
|
||||
try {
|
||||
callOk = SDL_AcquireGPUSwapchainTexture(f.commandBuffer, Display.getWindow(), pTexture, pWidth, pHeight);
|
||||
callOk = SDL_WaitAndAcquireGPUSwapchainTexture(cb, window, pTexture, pWidth, pHeight);
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
@@ -823,25 +976,43 @@ public final class FrameManager {
|
||||
w = pWidth.get(0);
|
||||
h = pHeight.get(0);
|
||||
} catch (RuntimeException re) {
|
||||
if (Tracy.ENABLED) f.acquireWaitNanosThisFrame += System.nanoTime() - t0;
|
||||
SDL_CancelGPUCommandBuffer(cb);
|
||||
f.swapchainUnavailable = true;
|
||||
throw re;
|
||||
}
|
||||
if (Tracy.ENABLED) f.acquireWaitNanosThisFrame += System.nanoTime() - t0;
|
||||
final long tEnd = System.nanoTime();
|
||||
f.lastFrameGateNanos = tEnd - t0;
|
||||
f.lastFrameGateEndNanos = tEnd;
|
||||
noteAcquireWait(f, tEnd - t0);
|
||||
|
||||
if (!callOk) {
|
||||
if (!callOk || tex == 0) {
|
||||
SDL_CancelGPUCommandBuffer(cb);
|
||||
f.swapchainUnavailable = true;
|
||||
notePresentSkip(f);
|
||||
return false;
|
||||
}
|
||||
if (tex == 0) {
|
||||
f.swapchainUnavailable = true;
|
||||
f.presentSkipsThisFrame++;
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final long info = stack.ncalloc(SDL_GPUBlitInfo.ALIGNOF, 1, SDL_GPUBlitInfo.SIZEOF);
|
||||
writeBlitRegion(info + SDL_GPUBlitInfo.SOURCE, srcTexture, srcW, srcH);
|
||||
writeBlitRegion(info + SDL_GPUBlitInfo.DESTINATION, tex, w, h);
|
||||
MemoryAccess.putInt(info + SDL_GPUBlitInfo.FILTER, SDL_GPU_FILTER_LINEAR);
|
||||
MemoryAccess.putInt(info + SDL_GPUBlitInfo.FLIP_MODE, flipMode);
|
||||
nSDL_BlitGPUTexture(cb, info);
|
||||
}
|
||||
|
||||
if (!SDL_SubmitGPUCommandBuffer(cb)) {
|
||||
device.reportGpuFailure("submit present blit");
|
||||
return false;
|
||||
}
|
||||
f.swapchainTexture = tex;
|
||||
f.swapchainWidth = w;
|
||||
f.swapchainHeight = h;
|
||||
f.swapchainAcquiredThisFrame = true;
|
||||
f.presentedThisFrame = true;
|
||||
if (afterPresent != null) afterPresent.run();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void writeBlitRegion(long region, long texture, int w, int h) {
|
||||
MemoryAccess.putAddress(region + SDL_GPUBlitRegion.TEXTURE, texture);
|
||||
MemoryAccess.putInt(region + SDL_GPUBlitRegion.W, w);
|
||||
MemoryAccess.putInt(region + SDL_GPUBlitRegion.H, h);
|
||||
}
|
||||
}
|
||||
+14
-3
@@ -1,7 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.splash;
|
||||
package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -9,13 +8,14 @@ import org.lwjgl.opengl.GL11;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
|
||||
public final class SplashOffscreenTarget {
|
||||
public final class OffscreenTarget {
|
||||
|
||||
private int fboGlId;
|
||||
private int colorTexGlId;
|
||||
private long colorTexHandle;
|
||||
private int width;
|
||||
private int height;
|
||||
private boolean hasContent;
|
||||
|
||||
public void create(Device device, ResourceManager rm, int width, int height, int swapchainSdlFormat) {
|
||||
this.width = width;
|
||||
@@ -37,10 +37,20 @@ public final class SplashOffscreenTarget {
|
||||
fbo.cachedFormatsDirty = true;
|
||||
}
|
||||
|
||||
public void ensureSize(Device device, ResourceManager rm, int width, int height) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
if (colorTexHandle != 0 && width == this.width && height == this.height) return;
|
||||
destroy(rm);
|
||||
create(device, rm, width, height, device.getSwapchainTextureFormat());
|
||||
}
|
||||
|
||||
public int fboId() { return fboGlId; }
|
||||
public long colorTexture() { return colorTexHandle; }
|
||||
public int width() { return width; }
|
||||
public int height() { return height; }
|
||||
public boolean isReady() { return colorTexHandle != 0; }
|
||||
public boolean hasContent() { return hasContent; }
|
||||
public void markContent() { hasContent = true; }
|
||||
|
||||
public boolean isFor(ContextState st) {
|
||||
return fboGlId != 0 && st.boundFboId == fboGlId;
|
||||
@@ -56,5 +66,6 @@ public final class SplashOffscreenTarget {
|
||||
colorTexGlId = 0;
|
||||
colorTexHandle = 0;
|
||||
}
|
||||
hasContent = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
public final class Presenter {
|
||||
|
||||
private final FrameManager frameManager;
|
||||
private final Executor windowThreadExecutor;
|
||||
private final Semaphore pending = new Semaphore(1);
|
||||
|
||||
public Presenter(FrameManager frameManager, Executor windowThreadExecutor) {
|
||||
this.frameManager = frameManager;
|
||||
this.windowThreadExecutor = windowThreadExecutor;
|
||||
}
|
||||
|
||||
public void requestPresent(long srcTexture, int srcW, int srcH, int flipMode) {
|
||||
pending.acquireUninterruptibly();
|
||||
boolean submitted = false;
|
||||
try {
|
||||
windowThreadExecutor.execute(() -> {
|
||||
try {
|
||||
frameManager.presentOnWindowThread(srcTexture, srcW, srcH, flipMode);
|
||||
} finally {
|
||||
pending.release();
|
||||
}
|
||||
});
|
||||
submitted = true;
|
||||
} finally {
|
||||
if (!submitted) pending.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void drain() {
|
||||
pending.acquireUninterruptibly();
|
||||
pending.release();
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ final class SdlFramePlots {
|
||||
private static final long PLOT_PRESENT_TO_PRESENT_US = Tracy.plotHandle("sdl.presentToPresentUs");
|
||||
private static final long PLOT_FRAMES_IN_FLIGHT = Tracy.plotHandle("sdl.framesInFlight");
|
||||
private static final long PLOT_MIP_GENS = Tracy.plotHandle("sdl.mipGens");
|
||||
private static final long PLOT_MIP_GEN_SUBMITS = Tracy.plotHandle("sdl.mipGenSubmits");
|
||||
private static final long PLOT_UPLOAD_FLUSH_SUBMITS = Tracy.plotHandle("sdl.uploadFlushSubmits");
|
||||
private static final long PLOT_BLIT_FALLBACKS = Tracy.plotHandle("sdl.blitFallbacks");
|
||||
private static final long PLOT_PRESENT_SKIPS = Tracy.plotHandle("sdl.presentSkips");
|
||||
private static final long PLOT_EMPTY_FRAMES = Tracy.plotHandle("sdl.emptyFrames");
|
||||
@@ -54,6 +56,7 @@ final class SdlFramePlots {
|
||||
private static final long PLOT_BUFFER_POOL_HITS = Tracy.plotHandle("sdl.bufferPoolHits");
|
||||
private static final long PLOT_PIPELINE_KEY_RECOMPUTES = Tracy.plotHandle("sdl.pipelineKeyRecomputes");
|
||||
private static final long PLOT_PIPELINE_FAST_PATH_HITS = Tracy.plotHandle("sdl.pipelineFastPathHits");
|
||||
private static final long PLOT_MID_FRAME_SUBMITS = Tracy.plotHandle("sdl.midFrameSubmits");
|
||||
|
||||
private static final int[] contextCounters = new int[SDLGPURenderBackend.CTR_COUNT];
|
||||
|
||||
@@ -97,11 +100,14 @@ final class SdlFramePlots {
|
||||
final long nowNanos = System.nanoTime();
|
||||
if (f.lastEndFrameNanos != 0) Tracy.plotInt(PLOT_PRESENT_TO_PRESENT_US, (nowNanos - f.lastEndFrameNanos) / 1000);
|
||||
f.lastEndFrameNanos = nowNanos;
|
||||
Tracy.plotInt(PLOT_FRAMES_IN_FLIGHT, Device.FRAMES_IN_FLIGHT);
|
||||
Tracy.plotInt(PLOT_FRAMES_IN_FLIGHT, Device.framesInFlight());
|
||||
Tracy.plotInt(PLOT_MIP_GENS, f.mipGensThisFrame);
|
||||
Tracy.plotInt(PLOT_MIP_GEN_SUBMITS, f.mipGenSubmitsThisFrame);
|
||||
Tracy.plotInt(PLOT_UPLOAD_FLUSH_SUBMITS, f.uploadFlushSubmitsThisFrame);
|
||||
Tracy.plotInt(PLOT_BLIT_FALLBACKS, f.blitsThisFrame);
|
||||
Tracy.plotInt(PLOT_PRESENT_SKIPS, f.presentSkipsThisFrame);
|
||||
Tracy.plotInt(PLOT_EMPTY_FRAMES, f.emptyFramesThisFrame);
|
||||
Tracy.plotInt(PLOT_DROPPED_DRAWS, f.droppedDrawsThisFrame);
|
||||
Tracy.plotInt(PLOT_MID_FRAME_SUBMITS, f.midFrameSubmitsThisFrame);
|
||||
}
|
||||
}
|
||||
+78
-35
@@ -20,6 +20,7 @@ import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentBufferSync;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.SamplerBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageBufferBinder;
|
||||
@@ -108,11 +109,8 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
if (st.boundFboId == 0) {
|
||||
if (st.pendingSwapchainClear) {
|
||||
frameManager.ensureSwapchainRenderPass(f, st.pendingSwapchainR, st.pendingSwapchainG, st.pendingSwapchainB, st.pendingSwapchainA, true);
|
||||
st.pendingSwapchainClear = false;
|
||||
} else {
|
||||
frameManager.ensureSwapchainRenderPass(f, st.clearR, st.clearG, st.clearB, st.clearA, false);
|
||||
if (frameManager.ensureFbo0RenderPass(f, st)) {
|
||||
st.pipeline.setDepthTargetFormat(resourceManager.getSwapchainDepthStencilFormat());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -145,47 +143,50 @@ public final class PipelineApplier {
|
||||
tex = dummyTex;
|
||||
}
|
||||
final boolean pendingClear = tex != dummyTex && st.pendingColorTextures.contains(tex);
|
||||
final boolean firstUse = !pendingClear && tex != dummyTex && !st.clearedTexturesThisFrame.contains(tex);
|
||||
final boolean defined = tex != dummyTex && resourceManager.isTextureContentDefined(tex);
|
||||
final boolean firstUse = !pendingClear && tex != dummyTex && !defined;
|
||||
if (pendingClear || firstUse) proposedClearOps |= 1 << i;
|
||||
layoutHash = Hashing.fmix64(layoutHash, tex);
|
||||
layoutHash = Hashing.fmix64(layoutHash, db);
|
||||
if (pendingClear || firstUse) {
|
||||
final float[] c = pendingClear ? st.pendingColorValues.get(tex) : null;
|
||||
layoutHash = foldClearColor(layoutHash, c != null ? c[0] : st.clearR, c != null ? c[1] : st.clearG, c != null ? c[2] : st.clearB, c != null ? c[3] : st.clearA);
|
||||
}
|
||||
}
|
||||
final boolean proposedDepthClear;
|
||||
final boolean proposedStencilClear;
|
||||
final boolean depthHasStencil = fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat);
|
||||
if (fbo.depthTexture != 0) {
|
||||
final boolean pendingClear = st.pendingDepthTextures.contains(fbo.depthTexture);
|
||||
final boolean firstUse = !pendingClear && !st.clearedTexturesThisFrame.contains(fbo.depthTexture);
|
||||
proposedDepthClear = pendingClear || firstUse;
|
||||
final boolean pendingStencil = depthHasStencil && st.pendingStencilTextures.contains(fbo.depthTexture);
|
||||
if (depthHasStencil) {
|
||||
final boolean firstStencilUse = !pendingStencil && !st.clearedStencilTexturesThisFrame.contains(fbo.depthTexture);
|
||||
proposedStencilClear = pendingStencil || firstStencilUse;
|
||||
} else {
|
||||
proposedStencilClear = false;
|
||||
}
|
||||
layoutHash = Hashing.fmix64(layoutHash, fbo.depthTexture);
|
||||
if (proposedDepthClear) {
|
||||
layoutHash = foldClearDepth(layoutHash, pendingClear ? st.pendingDepthValues.get(fbo.depthTexture) : st.depthClearValue);
|
||||
}
|
||||
if (proposedStencilClear) {
|
||||
layoutHash = foldClearStencil(layoutHash, pendingStencil ? st.pendingStencilValues.get(fbo.depthTexture) : st.stencilClearValue);
|
||||
}
|
||||
} else {
|
||||
proposedDepthClear = false;
|
||||
proposedStencilClear = false;
|
||||
}
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.clearR));
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.clearG));
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.clearB));
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.clearA));
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.depthClearValue));
|
||||
|
||||
frameManager.endRenderPassIfActive(f);
|
||||
|
||||
final boolean reuse = fbo.cachedTargetsValid && fbo.cachedTargetsLayoutHash == layoutHash && fbo.cachedTargetsCount == totalTargets && fbo.cachedClearOpFlags == proposedClearOps && fbo.cachedDepthClearLast == proposedDepthClear;
|
||||
final boolean reuse = fbo.cachedTargetsValid && fbo.cachedTargetsLayoutHash == layoutHash && fbo.cachedTargetsCount == totalTargets && fbo.cachedClearOpFlags == proposedClearOps && fbo.cachedDepthClearLast == proposedDepthClear && fbo.cachedStencilClearLast == proposedStencilClear;
|
||||
|
||||
SDL_GPUColorTargetInfo.Buffer colorTargets = null;
|
||||
SDL_GPUDepthStencilTargetInfo depthTarget = null;
|
||||
|
||||
if (reuse) {
|
||||
for (int i = 0; i < totalTargets; i++) {
|
||||
final int db = fbo.drawBuffers[i];
|
||||
final long tex = (db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS)
|
||||
? fbo.colorTextures[db] : 0L;
|
||||
if (tex != 0) {
|
||||
st.pendingColorTextures.remove(tex);
|
||||
if ((proposedClearOps & (1 << i)) != 0) st.clearedTexturesThisFrame.add(tex);
|
||||
}
|
||||
}
|
||||
if (fbo.depthTexture != 0) {
|
||||
st.pendingDepthTextures.remove(fbo.depthTexture);
|
||||
if (proposedDepthClear) st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
}
|
||||
if (totalTargets > 0) colorTargets = fbo.cachedColorTargets.position(0).limit(totalTargets);
|
||||
if (fbo.depthTexture != 0) depthTarget = fbo.cachedDepthTarget;
|
||||
} else {
|
||||
@@ -199,9 +200,8 @@ public final class PipelineApplier {
|
||||
} else {
|
||||
tex = dummyTex;
|
||||
}
|
||||
final boolean pendingClear = tex != dummyTex && st.pendingColorTextures.remove(tex);
|
||||
final boolean firstUse = !pendingClear && tex != dummyTex && !st.clearedTexturesThisFrame.contains(tex);
|
||||
final boolean clearThis = pendingClear || firstUse;
|
||||
final boolean pendingClear = tex != dummyTex && st.pendingColorTextures.contains(tex);
|
||||
final boolean clearThis = (proposedClearOps & (1 << i)) != 0;
|
||||
final long ctAddr = colorTargets.address() + (long) i * SDL_GPUColorTargetInfo.SIZEOF;
|
||||
MemoryAccess.putAddress(ctAddr + SDL_GPUColorTargetInfo.TEXTURE, tex);
|
||||
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.LOAD_OP, clearThis ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
@@ -220,36 +220,79 @@ public final class PipelineApplier {
|
||||
MemoryAccess.putFloat(ccAddr + SDL_FColor.B, st.clearB);
|
||||
MemoryAccess.putFloat(ccAddr + SDL_FColor.A, st.clearA);
|
||||
}
|
||||
if (tex != dummyTex) st.clearedTexturesThisFrame.add(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fbo.depthTexture != 0) {
|
||||
final boolean pendingClear = st.pendingDepthTextures.remove(fbo.depthTexture);
|
||||
final boolean firstUse = !pendingClear && !st.clearedTexturesThisFrame.contains(fbo.depthTexture);
|
||||
final boolean clearDepth = pendingClear || firstUse;
|
||||
final boolean pendingClear = st.pendingDepthTextures.contains(fbo.depthTexture);
|
||||
depthTarget = fbo.cachedDepthTarget;
|
||||
final long dtAddr = depthTarget.address();
|
||||
MemoryAccess.putAddress(dtAddr + SDL_GPUDepthStencilTargetInfo.TEXTURE, fbo.depthTexture);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, clearDepth ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, proposedDepthClear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
if (clearDepth) {
|
||||
if (proposedDepthClear) {
|
||||
final float dv = pendingClear ? st.pendingDepthValues.get(fbo.depthTexture) : st.depthClearValue;
|
||||
MemoryAccess.putFloat(dtAddr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, dv);
|
||||
st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
}
|
||||
if (depthHasStencil) {
|
||||
final boolean pendingStencil = st.pendingStencilTextures.contains(fbo.depthTexture);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, proposedStencilClear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
if (proposedStencilClear) {
|
||||
final int sv = pendingStencil ? st.pendingStencilValues.get(fbo.depthTexture) : st.stencilClearValue;
|
||||
MemoryAccess.putByte(dtAddr + SDL_GPUDepthStencilTargetInfo.CLEAR_STENCIL, (byte) sv);
|
||||
}
|
||||
} else {
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
}
|
||||
}
|
||||
fbo.cachedTargetsLayoutHash = layoutHash;
|
||||
fbo.cachedTargetsCount = totalTargets;
|
||||
fbo.cachedClearOpFlags = proposedClearOps;
|
||||
fbo.cachedDepthClearLast = proposedDepthClear;
|
||||
fbo.cachedStencilClearLast = proposedStencilClear;
|
||||
fbo.cachedTargetsValid = true;
|
||||
}
|
||||
|
||||
consumeClears(resourceManager, st, fbo, dummyTex, proposedClearOps, proposedDepthClear, proposedStencilClear, depthHasStencil);
|
||||
|
||||
frameManager.beginRenderPass(colorTargets, depthTarget);
|
||||
frameManager.setActiveLayoutHash(fbo.structuralLayoutHash);
|
||||
}
|
||||
|
||||
static void consumeClears(ResourceManager rm, ContextState st, FboState fbo, long dummyTex, int clearOps, boolean depthClear, boolean stencilClear, boolean depthHasStencil) {
|
||||
for (int i = 0, n = fbo.drawBuffers.length; i < n; i++) {
|
||||
final int db = fbo.drawBuffers[i];
|
||||
final long tex = (db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS) ? fbo.colorTextures[db] : 0L;
|
||||
if (tex == 0 || tex == dummyTex) continue;
|
||||
st.pendingColorTextures.remove(tex);
|
||||
if ((clearOps & (1 << i)) != 0) rm.markTextureContentDefined(tex);
|
||||
}
|
||||
if (fbo.depthTexture == 0) return;
|
||||
st.pendingDepthTextures.remove(fbo.depthTexture);
|
||||
if (depthClear) st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
if (depthHasStencil) {
|
||||
st.pendingStencilTextures.remove(fbo.depthTexture);
|
||||
if (stencilClear) st.clearedStencilTexturesThisFrame.add(fbo.depthTexture);
|
||||
}
|
||||
}
|
||||
|
||||
static long foldClearColor(long h, float r, float g, float b, float a) {
|
||||
h = Hashing.fmix64(h, Float.floatToRawIntBits(r));
|
||||
h = Hashing.fmix64(h, Float.floatToRawIntBits(g));
|
||||
h = Hashing.fmix64(h, Float.floatToRawIntBits(b));
|
||||
return Hashing.fmix64(h, Float.floatToRawIntBits(a));
|
||||
}
|
||||
|
||||
static long foldClearDepth(long h, float depth) {
|
||||
return Hashing.fmix64(h, Float.floatToRawIntBits(depth));
|
||||
}
|
||||
|
||||
static long foldClearStencil(long h, int stencil) {
|
||||
return Hashing.fmix64(h, stencil);
|
||||
}
|
||||
|
||||
private static final int RING_BLOCK_BYTES = ContextState.MAX_VERTEX_ATTRIBS * 16;
|
||||
private static final int RING_CHUNK_BYTES = ResourceManager.ATTRIB_RING_CHUNK_BLOCKS * RING_BLOCK_BYTES;
|
||||
|
||||
|
||||
+21
-1
@@ -86,6 +86,17 @@ public final class PipelineCache {
|
||||
public int stencilCompareMask = 0xFF;
|
||||
public int stencilWriteMask = 0xFF;
|
||||
|
||||
private static final LongOpenHashSet loggedStencilMaskLost = new LongOpenHashSet();
|
||||
|
||||
private void warnOnceOnStencilMaskLost() {
|
||||
if (!stencilTestEnabled || effectiveStencilTestEnabled()) return;
|
||||
if (stencilFrontCompareOp == SDL_GPU_COMPAREOP_ALWAYS && stencilBackCompareOp == SDL_GPU_COMPAREOP_ALWAYS && stencilFrontPassOp == SDL_GPU_STENCILOP_KEEP && stencilBackPassOp == SDL_GPU_STENCILOP_KEEP)
|
||||
return;
|
||||
if (!loggedStencilMaskLost.add(Hashing.packHiLo(programId, debugFboId))) return;
|
||||
LOG.warn("Stencil mask dropped: bound target has no stencil aspect. fbo={} depthFormat={} program={} frontCmp={} frontPass={} writeMask=0x{}",
|
||||
debugFboId, textureFormatName(depthTargetFormat), programId, stencilFrontCompareOp, stencilFrontPassOp, Integer.toHexString(stencilWriteMask));
|
||||
}
|
||||
|
||||
public boolean cullEnabled = false;
|
||||
public int cullFaceMode = SDL_GPU_CULLMODE_BACK;
|
||||
public int frontFace = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
||||
@@ -141,6 +152,14 @@ public final class PipelineCache {
|
||||
this.drawBuffers = drawBuffers;
|
||||
}
|
||||
|
||||
public boolean setDepthTargetFormat(int sdlFormat) {
|
||||
if (depthTargetFormat == sdlFormat && hasDepthTarget == (sdlFormat != 0)) return false;
|
||||
hasDepthTarget = sdlFormat != 0;
|
||||
depthTargetFormat = sdlFormat;
|
||||
markOutputDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
private int attachmentForSlot(int slot) {
|
||||
final int[] db = drawBuffers;
|
||||
if (db == null || slot < 0 || slot >= db.length) return 0;
|
||||
@@ -340,7 +359,7 @@ public final class PipelineCache {
|
||||
private void warnOnceOnUnconsumedFragOutput(int numColorTargets) {
|
||||
if (maxFragOutputLocation < numColorTargets) return;
|
||||
if (!loggedUnconsumedFragOutput.add(Hashing.packHiLo(programId, numColorTargets))) return;
|
||||
LOG.warn("[PipelineCache] program {} declares fragment output location {} but framebuffer {} ({} colour attachment(s)) has {} draw buffer(s) {}; writes past location {} are discarded", programId, maxFragOutputLocation, debugFboId, debugFboAttachments, numColorTargets, Arrays.toString(drawBuffers), numColorTargets - 1);
|
||||
LOG.warn("[PipelineCache] program {} declares fragment output location {} but framebuffer {} ({} color attachment(s)) has {} draw buffer(s) {}; writes past location {} are discarded", programId, maxFragOutputLocation, debugFboId, debugFboAttachments, numColorTargets, Arrays.toString(drawBuffers), numColorTargets - 1);
|
||||
}
|
||||
|
||||
private long createPipeline(PipelineStore store, ContextState cs) {
|
||||
@@ -360,6 +379,7 @@ public final class PipelineCache {
|
||||
try (var stack = stackPush()) {
|
||||
final int numColorTargets = colorTargetFormats().length;
|
||||
warnOnceOnUnconsumedFragOutput(numColorTargets);
|
||||
warnOnceOnStencilMaskLost();
|
||||
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(numColorTargets, stack);
|
||||
for (int i = 0; i < numColorTargets; i++) {
|
||||
final var desc = colorDesc.get(i);
|
||||
|
||||
+55
-30
@@ -49,8 +49,19 @@ public final class FBOClearTracker {
|
||||
st.clearedTexturesThisFrame.remove(tex);
|
||||
}
|
||||
|
||||
public static void recordPendingStencilClear(ContextState st, long tex, int value) {
|
||||
st.pendingStencilValues.put(tex, value);
|
||||
if (st.pendingStencilTextures.add(tex)) {
|
||||
st.pendingMutationGen++;
|
||||
}
|
||||
st.clearedStencilTexturesThisFrame.remove(tex);
|
||||
}
|
||||
|
||||
public static boolean fboHasPendingClear(ContextState st, FboState fbo) {
|
||||
if (fbo.depthTexture != 0 && st.pendingDepthTextures.contains(fbo.depthTexture)) return true;
|
||||
if (fbo.depthTexture != 0
|
||||
&& (st.pendingDepthTextures.contains(fbo.depthTexture) || st.pendingStencilTextures.contains(fbo.depthTexture))) {
|
||||
return true;
|
||||
}
|
||||
if (st.pendingColorTextures.isEmpty()) return false;
|
||||
for (int db : fbo.drawBuffers) {
|
||||
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS) continue;
|
||||
@@ -67,7 +78,7 @@ public final class FBOClearTracker {
|
||||
}
|
||||
|
||||
public void flushPendingClearsForBoundSamplers(ContextState st) {
|
||||
if (st.pendingColorTextures.isEmpty() && st.pendingDepthTextures.isEmpty()) return;
|
||||
if (st.pendingColorTextures.isEmpty() && st.pendingDepthTextures.isEmpty() && st.pendingStencilTextures.isEmpty()) return;
|
||||
|
||||
if (st.lastFlushedSamplerBindGen == st.samplerBindGen && st.lastFlushedProgram == st.boundProgram && st.lastFlushedPendingMutationGen == st.pendingMutationGen) {
|
||||
return;
|
||||
@@ -115,7 +126,7 @@ public final class FBOClearTracker {
|
||||
if (st.pendingColorTextures.remove(handle)) {
|
||||
colorHandles.add(handle);
|
||||
colorGlIds.add(glTexId);
|
||||
} else if (st.pendingDepthTextures.remove(handle)) {
|
||||
} else if (st.pendingDepthTextures.contains(handle) || st.pendingStencilTextures.contains(handle)) {
|
||||
depthHandles.add(handle);
|
||||
depthGlIds.add(glTexId);
|
||||
}
|
||||
@@ -169,6 +180,7 @@ public final class FBOClearTracker {
|
||||
MemoryAccess.putFloat(ccAddr + SDL_FColor.B, color[2]);
|
||||
MemoryAccess.putFloat(ccAddr + SDL_FColor.A, color[3]);
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
resourceManager.markTextureContentDefined(handle);
|
||||
consumed[idx] = true;
|
||||
}
|
||||
frameManager.noteClearPass();
|
||||
@@ -180,19 +192,38 @@ public final class FBOClearTracker {
|
||||
final int dn = depthHandles.size();
|
||||
for (int i = 0; i < dn; i++) {
|
||||
final long handle = depthHandles.getLong(i);
|
||||
final float val = st.pendingDepthValues.get(handle);
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUDepthStencilTargetInfo dt = SDL_GPUDepthStencilTargetInfo.calloc(stack);
|
||||
final long addr = dt.address();
|
||||
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, handle);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, SDL_GPU_LOADOP_CLEAR);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putFloat(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, val);
|
||||
frameManager.noteClearPass();
|
||||
frameManager.beginRenderPass(null, dt);
|
||||
frameManager.endRenderPassIfActive();
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
emitDepthStencilClearPass(st, handle, st.pendingDepthTextures.remove(handle), st.pendingStencilTextures.remove(handle), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitDepthStencilClearPass(ContextState st, long handle, boolean clearDepth, boolean clearStencil, boolean materialized) {
|
||||
if (!clearDepth && !clearStencil) return;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUDepthStencilTargetInfo dt = SDL_GPUDepthStencilTargetInfo.calloc(stack);
|
||||
final long addr = dt.address();
|
||||
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, handle);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, clearDepth ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
if (clearDepth) {
|
||||
MemoryAccess.putFloat(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, st.pendingDepthValues.get(handle));
|
||||
}
|
||||
if (clearStencil) {
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, SDL_GPU_LOADOP_CLEAR);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putByte(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_STENCIL, (byte) st.pendingStencilValues.get(handle));
|
||||
}
|
||||
frameManager.noteClearPass();
|
||||
if (materialized) frameManager.noteMaterializedClearPass();
|
||||
frameManager.beginRenderPass(null, dt);
|
||||
frameManager.endRenderPassIfActive();
|
||||
}
|
||||
if (clearDepth) {
|
||||
st.pendingDepthValues.remove(handle);
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
}
|
||||
if (clearStencil) {
|
||||
st.pendingStencilValues.remove(handle);
|
||||
st.clearedStencilTexturesThisFrame.add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,28 +237,18 @@ public final class FBOClearTracker {
|
||||
if (depth) st.pendingDepthValues.remove(handle);
|
||||
else st.pendingColorValues.remove(handle);
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
resourceManager.markTextureContentDefined(handle);
|
||||
st.pendingMutationGen++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void materializePendingClearForTexture(ContextState st, long handle) {
|
||||
if (handle == 0) return;
|
||||
if (st.pendingDepthTextures.remove(handle)) {
|
||||
final float val = st.pendingDepthValues.remove(handle);
|
||||
final boolean depthPending = st.pendingDepthTextures.remove(handle);
|
||||
final boolean stencilPending = st.pendingStencilTextures.remove(handle);
|
||||
if (depthPending || stencilPending) {
|
||||
frameManager.endRenderPassIfActive();
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUDepthStencilTargetInfo dt = SDL_GPUDepthStencilTargetInfo.calloc(stack);
|
||||
final long addr = dt.address();
|
||||
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, handle);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, SDL_GPU_LOADOP_CLEAR);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putFloat(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, val);
|
||||
frameManager.noteClearPass();
|
||||
frameManager.noteMaterializedClearPass();
|
||||
frameManager.beginRenderPass(null, dt);
|
||||
frameManager.endRenderPassIfActive();
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
}
|
||||
emitDepthStencilClearPass(st, handle, depthPending, stencilPending, true);
|
||||
st.pendingMutationGen++;
|
||||
return;
|
||||
}
|
||||
@@ -250,6 +271,7 @@ public final class FBOClearTracker {
|
||||
frameManager.beginRenderPass(targets, null);
|
||||
frameManager.endRenderPassIfActive();
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
resourceManager.markTextureContentDefined(handle);
|
||||
}
|
||||
st.pendingMutationGen++;
|
||||
}
|
||||
@@ -262,7 +284,10 @@ public final class FBOClearTracker {
|
||||
st.pendingColorValues.remove(handle);
|
||||
st.pendingDepthTextures.remove(handle);
|
||||
st.pendingDepthValues.remove(handle);
|
||||
st.pendingStencilTextures.remove(handle);
|
||||
st.pendingStencilValues.remove(handle);
|
||||
st.clearedTexturesThisFrame.remove(handle);
|
||||
st.clearedStencilTexturesThisFrame.remove(handle);
|
||||
}
|
||||
|
||||
public boolean fbosHaveSameAttachments(ContextState st, int a, int b) {
|
||||
|
||||
@@ -35,6 +35,7 @@ public final class FboState {
|
||||
public int cachedTargetsCount = -1;
|
||||
public int cachedClearOpFlags;
|
||||
public boolean cachedDepthClearLast;
|
||||
public boolean cachedStencilClearLast;
|
||||
public boolean cachedTargetsValid;
|
||||
|
||||
public long getColorTexture() { return colorTextures[0]; }
|
||||
@@ -63,7 +64,7 @@ public final class FboState {
|
||||
h = Hashing.fmix64(h, db);
|
||||
}
|
||||
h = Hashing.fmix64(h, depthTexture);
|
||||
if (h == 0L || h == FrameManager.SWAPCHAIN_LAYOUT_HASH) h = 0xD1CE5EEDL;
|
||||
if (h == 0L || h == FrameManager.FBO0_LAYOUT_HASH) h = 0xD1CE5EEDL;
|
||||
structuralLayoutHash = h;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,85 @@ public final class PixelOps {
|
||||
return sdlFormat == SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT || sdlFormat == SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
|
||||
}
|
||||
|
||||
public static int depthBits(int sdlFormat) {
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_D16_UNORM -> 16;
|
||||
case SDL_GPU_TEXTUREFORMAT_D24_UNORM, SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT -> 24;
|
||||
case SDL_GPU_TEXTUREFORMAT_D32_FLOAT, SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT -> 32;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
public static int stencilBits(int sdlFormat) {
|
||||
return isDepthStencilFormat(sdlFormat) ? 8 : 0;
|
||||
}
|
||||
|
||||
public static int colorChannelBits(int sdlFormat, int channel) {
|
||||
if (channel < 0 || channel > 3) return 0;
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_A8_UNORM -> channel == 3 ? 8 : 0;
|
||||
case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM -> channel == 3 ? 2 : 10;
|
||||
case SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT -> switch (channel) {
|
||||
case 0, 1 -> 11;
|
||||
case 2 -> 10;
|
||||
default -> 0;
|
||||
};
|
||||
case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM -> switch (channel) {
|
||||
case 0, 2 -> 5;
|
||||
case 1 -> 6;
|
||||
default -> 0;
|
||||
};
|
||||
case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM -> channel == 3 ? 1 : 5;
|
||||
case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM -> 4;
|
||||
default -> channel < sdlFormatColorComponents(sdlFormat) ? uniformChannelBits(sdlFormat) : 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int uniformChannelBits(int sdlFormat) {
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8_UINT, SDL_GPU_TEXTUREFORMAT_R8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB -> 8;
|
||||
case SDL_GPU_TEXTUREFORMAT_R16_UNORM, SDL_GPU_TEXTUREFORMAT_R16_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R16G16_UNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_SNORM, SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_UINT, SDL_GPU_TEXTUREFORMAT_R16G16_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT -> 16;
|
||||
case SDL_GPU_TEXTUREFORMAT_R32_FLOAT, SDL_GPU_TEXTUREFORMAT_R32_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32_INT, SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32G32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT -> 32;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int sdlFormatColorComponents(int sdlFormat) {
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8_UINT, SDL_GPU_TEXTUREFORMAT_R8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_UNORM, SDL_GPU_TEXTUREFORMAT_R16_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R32_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32_UINT, SDL_GPU_TEXTUREFORMAT_R32_INT -> 1;
|
||||
case SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16G16_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_INT, SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32G32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32_INT -> 2;
|
||||
default -> 4;
|
||||
};
|
||||
}
|
||||
|
||||
public static int glPixelSize(int format, int type) {
|
||||
final int packed = GLTypes.packedTexelBytes(type);
|
||||
if (packed != 0) return packed;
|
||||
|
||||
+128
-5
@@ -127,6 +127,10 @@ public final class ResourceManager {
|
||||
|
||||
private int preferredD24 = SDL_GPU_TEXTUREFORMAT_D24_UNORM;
|
||||
private int preferredD24S8 = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
private volatile int swapchainDepthStencilFormat = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
private long swapchainDepthStencil;
|
||||
private int swapchainDepthStencilWidth;
|
||||
private int swapchainDepthStencilHeight;
|
||||
|
||||
private static final Supplier<ContextState.VAOState> VAO_FACTORY = ContextState.VAOState::new;
|
||||
private static final Supplier<TextureSamplerState> TEX_SAMPLER_FACTORY = TextureSamplerState::new;
|
||||
@@ -154,6 +158,21 @@ public final class ResourceManager {
|
||||
preferredD24S8 = SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
|
||||
LOG.info("D24_UNORM_S8_UINT not supported for depth+sampler, using D32_FLOAT_S8_UINT");
|
||||
}
|
||||
|
||||
swapchainDepthStencilFormat = preferredD24S8;
|
||||
if (!SDL_GPUTextureSupportsFormat(dev, swapchainDepthStencilFormat, texType, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) {
|
||||
final int alt = swapchainDepthStencilFormat == SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT ? SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT : SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
if (SDL_GPUTextureSupportsFormat(dev, alt, texType, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) {
|
||||
swapchainDepthStencilFormat = alt;
|
||||
} else {
|
||||
swapchainDepthStencilFormat = 0;
|
||||
LOG.warn("No depth+stencil format usable as a render target; default framebuffer will have no depth or stencil");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getSwapchainDepthStencilFormat() {
|
||||
return swapchainDepthStencilFormat;
|
||||
}
|
||||
|
||||
public int genTexture() {
|
||||
@@ -292,7 +311,7 @@ public final class ResourceManager {
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
releaseTextureHandle(handle);
|
||||
releaseTextureHandleAfterPendingUploads(handle);
|
||||
}
|
||||
|
||||
public void releaseTextureHandleForRealloc(int glId) {
|
||||
@@ -304,9 +323,24 @@ public final class ResourceManager {
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
releaseTextureHandleAfterPendingUploads(handle);
|
||||
}
|
||||
|
||||
private void releaseTextureHandleAfterPendingUploads(long handle) {
|
||||
if (handle == 0) return;
|
||||
if (shouldDeferTextureRelease()) {
|
||||
releaseTextureDeferred(handle);
|
||||
return;
|
||||
}
|
||||
releaseTextureHandle(handle);
|
||||
}
|
||||
|
||||
boolean shouldDeferTextureRelease() {
|
||||
final TransferThread tt = transferThread;
|
||||
if (tt != null && tt.getSubmittedSeq() < lastTextureUploadSeq) return true;
|
||||
return hasBatchedUploads();
|
||||
}
|
||||
|
||||
public void trackBufferHandle(long handle) {
|
||||
if (handle == 0) return;
|
||||
wLock.lock();
|
||||
@@ -319,6 +353,40 @@ public final class ResourceManager {
|
||||
try { liveTextureHandles.add(handle); } finally { wLock.unlock(); }
|
||||
}
|
||||
|
||||
private final LongOpenHashSet definedContentTextures = new LongOpenHashSet();
|
||||
|
||||
public boolean isTextureContentDefined(long handle) {
|
||||
if (handle == 0) return false;
|
||||
if (fastRead()) return definedContentTextures.contains(handle);
|
||||
rLock.lock();
|
||||
try { return definedContentTextures.contains(handle); } finally { rLock.unlock(); }
|
||||
}
|
||||
|
||||
public void markTextureContentDefined(long handle) {
|
||||
if (handle == 0) return;
|
||||
wLock.lock();
|
||||
try { definedContentTextures.add(handle); } finally { wLock.unlock(); }
|
||||
}
|
||||
|
||||
void markTextureContentsDefined(LongArrayList handles) {
|
||||
if (handles.isEmpty()) return;
|
||||
wLock.lock();
|
||||
try {
|
||||
for (int i = 0, n = handles.size(); i < n; i++) {
|
||||
final long handle = handles.getLong(i);
|
||||
if (handle != 0) definedContentTextures.add(handle);
|
||||
}
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void markTextureContentUndefined(long handle) {
|
||||
if (handle == 0) return;
|
||||
wLock.lock();
|
||||
try { definedContentTextures.remove(handle); } finally { wLock.unlock(); }
|
||||
}
|
||||
|
||||
public void releaseBufferHandle(long handle) {
|
||||
if (handle == 0) return;
|
||||
final boolean live;
|
||||
@@ -338,6 +406,8 @@ public final class ResourceManager {
|
||||
|
||||
public void releaseTextureHandle(long handle) {
|
||||
if (handle == 0) return;
|
||||
dropBatchedUploadsTargeting(handle);
|
||||
markTextureContentUndefined(handle);
|
||||
final boolean live;
|
||||
wLock.lock();
|
||||
try {
|
||||
@@ -517,7 +587,7 @@ public final class ResourceManager {
|
||||
final GpuBucket b = gpuBufferPool.get(Hashing.packHiLo(usage, bucket));
|
||||
if (b == null || b.handles.isEmpty()) return 0;
|
||||
for (int i = 0, n = b.handles.size(); i < n; i++) {
|
||||
if (gpuPoolCurrentFrame - b.frames.getLong(i) >= Device.FRAMES_IN_FLIGHT) {
|
||||
if (gpuPoolCurrentFrame - b.frames.getLong(i) >= Device.MAX_FRAMES_IN_FLIGHT) {
|
||||
final long handle = b.handles.getLong(i);
|
||||
final int last = n - 1;
|
||||
if (i != last) {
|
||||
@@ -620,6 +690,7 @@ public final class ResourceManager {
|
||||
|
||||
private final LongArrayList deferredBufferReleases = new LongArrayList();
|
||||
private final LongArrayList deferredTextureReleases = new LongArrayList();
|
||||
private volatile long lastTextureUploadSeq;
|
||||
|
||||
public void deleteBuffer(int glId) {
|
||||
final ByteBuffer droppedPboStaging;
|
||||
@@ -1305,7 +1376,7 @@ public final class ResourceManager {
|
||||
for (int i = 0; i < n; i += 2) {
|
||||
final long handle = batchSegmentPending.getLong(i);
|
||||
final long stamp = batchSegmentPending.getLong(i + 1);
|
||||
if (currentFrame - stamp >= Device.FRAMES_IN_FLIGHT) {
|
||||
if (currentFrame - stamp >= Device.MAX_FRAMES_IN_FLIGHT) {
|
||||
batchSegmentFree.enqueue(handle);
|
||||
} else {
|
||||
batchSegmentPending.set(w, handle);
|
||||
@@ -1560,12 +1631,16 @@ public final class ResourceManager {
|
||||
f.batchMapped = null;
|
||||
}
|
||||
|
||||
markTextureContentsDefined(f.pendingTexHandles);
|
||||
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTextureTransferInfo src = SDL_GPUTextureTransferInfo.calloc(stack);
|
||||
final SDL_GPUTextureRegion dst = SDL_GPUTextureRegion.calloc(stack);
|
||||
for (int i = 0; i < n; i++) {
|
||||
final long texture = f.pendingTexHandles.getLong(i);
|
||||
if (texture == 0) continue;
|
||||
src.transfer_buffer(f.batchXfer).offset(f.pendingTexOffsets.getInt(i));
|
||||
dst.texture(f.pendingTexHandles.getLong(i)).mip_level(f.pendingTexLevels.getInt(i))
|
||||
dst.texture(texture).mip_level(f.pendingTexLevels.getInt(i))
|
||||
.x(f.pendingTexX.getInt(i)).y(f.pendingTexY.getInt(i)).z(0)
|
||||
.w(f.pendingTexW.getInt(i)).h(f.pendingTexH.getInt(i)).d(1);
|
||||
SDL_UploadToGPUTexture(copyPass, src, dst, false);
|
||||
@@ -1586,6 +1661,14 @@ public final class ResourceManager {
|
||||
frameManager.recordUploadCommands(totalBytes, n);
|
||||
}
|
||||
|
||||
void dropBatchedUploadsTargeting(long handle) {
|
||||
final LongArrayList handles = frameManager.frame().pendingTexHandles;
|
||||
if (handles.isEmpty()) return;
|
||||
for (int i = 0, n = handles.size(); i < n; i++) {
|
||||
if (handles.getLong(i) == handle) handles.set(i, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasBatchedUploads() {
|
||||
return !frameManager.frame().pendingTexHandles.isEmpty();
|
||||
}
|
||||
@@ -1616,6 +1699,7 @@ public final class ResourceManager {
|
||||
.w(w).h(h).d(d);
|
||||
SDL_UploadToGPUTexture(copyPass, src, dst, false);
|
||||
}
|
||||
markTextureContentDefined(gpuTexture);
|
||||
|
||||
frameManager.recordUploadCommands(size, 1);
|
||||
returnTransferBuffer(xfer, size);
|
||||
@@ -1649,6 +1733,7 @@ public final class ResourceManager {
|
||||
|
||||
SDL_UploadToGPUTexture(copyPass, src, dst, false);
|
||||
}
|
||||
markTextureContentDefined(gpuTexture);
|
||||
|
||||
frameManager.recordUploadCommands(size, 1);
|
||||
returnTransferBuffer(xfer, size);
|
||||
@@ -1670,6 +1755,8 @@ public final class ResourceManager {
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
final long seq = TransferThread.nextSeq();
|
||||
st.frameHighestEnqueuedSeq = seq;
|
||||
lastTextureUploadSeq = seq;
|
||||
markTextureContentDefined(texHandle);
|
||||
tt.enqueue(TransferThread.TextureRegionUpload.acquire(xfer, texHandle, x, y, w, h, level, size, seq));
|
||||
tt.wake();
|
||||
return true;
|
||||
@@ -1999,8 +2086,36 @@ public final class ResourceManager {
|
||||
return dummyColorTarget;
|
||||
}
|
||||
|
||||
public long getOrCreateSwapchainDepthStencil(int width, int height) {
|
||||
if (swapchainDepthStencilFormat == 0 || width <= 0 || height <= 0) return 0;
|
||||
if (swapchainDepthStencil != 0 && swapchainDepthStencilWidth == width && swapchainDepthStencilHeight == height) {
|
||||
return swapchainDepthStencil;
|
||||
}
|
||||
if (swapchainDepthStencil != 0) {
|
||||
releaseTextureDeferred(swapchainDepthStencil);
|
||||
swapchainDepthStencil = 0;
|
||||
}
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTextureCreateInfo ci = SDL_GPUTextureCreateInfo.calloc(stack)
|
||||
.type(SDL_GPU_TEXTURETYPE_2D)
|
||||
.format(swapchainDepthStencilFormat)
|
||||
.usage(SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)
|
||||
.width(width).height(height).layer_count_or_depth(1).num_levels(1);
|
||||
swapchainDepthStencil = SDL_CreateGPUTexture(device.getDevice(), ci);
|
||||
}
|
||||
if (swapchainDepthStencil == 0) {
|
||||
swapchainDepthStencilFormat = 0;
|
||||
LOG.warn("Failed to create the default framebuffer depth+stencil target ({}x{}); it will have neither", width, height);
|
||||
return 0;
|
||||
}
|
||||
trackTextureHandle(swapchainDepthStencil);
|
||||
swapchainDepthStencilWidth = width;
|
||||
swapchainDepthStencilHeight = height;
|
||||
return swapchainDepthStencil;
|
||||
}
|
||||
|
||||
private static final int DUMMY_VBO_SIZE = 16 * 16;
|
||||
public static final int ATTRIB_RING_BLOCKS = 2048 * Device.FRAMES_IN_FLIGHT;
|
||||
public static final int ATTRIB_RING_BLOCKS = 2048 * Device.MAX_FRAMES_IN_FLIGHT;
|
||||
public static final int ATTRIB_RING_CHUNK_BLOCKS = 64;
|
||||
private static final int ATTRIB_RING_CHUNKS = ATTRIB_RING_BLOCKS / ATTRIB_RING_CHUNK_BLOCKS;
|
||||
private final AtomicLong attribRingCounter = new AtomicLong();
|
||||
@@ -2114,6 +2229,8 @@ public final class ResourceManager {
|
||||
case GL14.GL_DEPTH_COMPONENT32, GL30.GL_DEPTH_COMPONENT32F -> SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
|
||||
case GL30.GL_DEPTH24_STENCIL8 -> preferredD24S8;
|
||||
case GL30.GL_DEPTH32F_STENCIL8 -> SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
|
||||
case GL11.GL_STENCIL_INDEX, GL30.GL_STENCIL_INDEX1, GL30.GL_STENCIL_INDEX4,
|
||||
GL30.GL_STENCIL_INDEX8, GL30.GL_STENCIL_INDEX16 -> preferredD24S8;
|
||||
case GL30.GL_R8UI -> SDL_GPU_TEXTUREFORMAT_R8_UINT;
|
||||
case GL30.GL_R16UI -> SDL_GPU_TEXTUREFORMAT_R16_UINT;
|
||||
case GL30.GL_R32UI -> SDL_GPU_TEXTUREFORMAT_R32_UINT;
|
||||
@@ -2233,6 +2350,12 @@ public final class ResourceManager {
|
||||
releaseTextureHandle(dummyColorTarget);
|
||||
dummyColorTarget = 0;
|
||||
}
|
||||
if (swapchainDepthStencil != 0) {
|
||||
releaseTextureHandle(swapchainDepthStencil);
|
||||
swapchainDepthStencil = 0;
|
||||
swapchainDepthStencilWidth = 0;
|
||||
swapchainDepthStencilHeight = 0;
|
||||
}
|
||||
if (fallbackStorageTexture3D != 0) {
|
||||
releaseTextureHandle(fallbackStorageTexture3D);
|
||||
fallbackStorageTexture3D = 0;
|
||||
|
||||
@@ -101,19 +101,11 @@ public final class TextureOps {
|
||||
|
||||
public void readbackTexture(long texHandle, int x, int y, int w, int h, int level, ByteBuffer output) {
|
||||
if (texHandle == 0 || output == null) return;
|
||||
frameManager.endCopyPassIfActive();
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.submitMidFrame();
|
||||
|
||||
if (texHandle == frameManager.getSwapchainTexture()) {
|
||||
final long cb = frameManager.getCommandBuffer();
|
||||
if (cb == 0) return;
|
||||
resourceManager.downloadFromTexture(cb, texHandle, x, y, w, h, level, output);
|
||||
frameManager.reacquireCommandBuffer();
|
||||
} else {
|
||||
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
|
||||
if (cb == 0) return;
|
||||
resourceManager.downloadFromTexture(cb, texHandle, x, y, w, h, level, output);
|
||||
}
|
||||
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
|
||||
if (cb == 0) return;
|
||||
resourceManager.downloadFromTexture(cb, texHandle, x, y, w, h, level, output);
|
||||
}
|
||||
|
||||
public void copyTexSubImageImpl(ContextState st, int destGlId, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
@@ -210,6 +202,7 @@ public final class TextureOps {
|
||||
if (!SDL_SubmitGPUCommandBuffer(uploadCb)) {
|
||||
device.reportGpuFailure("submit mipmap generation CB");
|
||||
}
|
||||
frameManager.noteMipGenSubmit();
|
||||
|
||||
if (f.commandBuffer == uploadCb) {
|
||||
f.commandBuffer = 0;
|
||||
|
||||
+45
-9
@@ -34,6 +34,7 @@ public final class TransferThread {
|
||||
private static final Tracy.ZoneId Z_SDL_UPLOAD = Tracy.zoneId("sdlUpload", Tracy.COLOR_WORKER);
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU-Transfer");
|
||||
private boolean warnedNullTextureUpload;
|
||||
|
||||
private static final AtomicLong SEQ = new AtomicLong();
|
||||
|
||||
@@ -214,6 +215,19 @@ public final class TransferThread {
|
||||
return flushRequestedSeq > submittedSeq || openBytes >= FLUSH_COALESCE_BYTES || openCount >= FLUSH_COALESCE_COMMANDS;
|
||||
}
|
||||
|
||||
static boolean shouldPublishRetired(boolean hasOpenCB, long openHighestSeq, long submittedSeq) {
|
||||
return !hasOpenCB && openHighestSeq > submittedSeq;
|
||||
}
|
||||
|
||||
private void publishSubmittedSeq() {
|
||||
if (openHighestSeq <= submittedSeq) return;
|
||||
synchronized (submittedLock) {
|
||||
submittedSeq = openHighestSeq;
|
||||
submittedLock.notifyAll();
|
||||
}
|
||||
drainDuePendingFrees();
|
||||
}
|
||||
|
||||
private void run() {
|
||||
LOG.info("Transfer thread started");
|
||||
try {
|
||||
@@ -230,6 +244,9 @@ public final class TransferThread {
|
||||
|
||||
if (shouldFlush(openCB != 0, flushRequestedSeq, submittedSeq, openBytes, openCount)) {
|
||||
flushOpenCB();
|
||||
} else if (shouldPublishRetired(openCB != 0, openHighestSeq, submittedSeq)) {
|
||||
publishSubmittedSeq();
|
||||
openHighestSeq = 0;
|
||||
}
|
||||
|
||||
if (Tracy.ENABLED) pendingActiveNanos += System.nanoTime() - workStart;
|
||||
@@ -315,10 +332,18 @@ public final class TransferThread {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void retireSeq(long seq) {
|
||||
openHighestSeq = Math.max(openHighestSeq, seq);
|
||||
}
|
||||
|
||||
private void recordBufferUpload(long transferBuffer, long size, long dstGpuBuffer, long dstOffset, long seq, boolean cycle) {
|
||||
if (transferBuffer == 0) return;
|
||||
if (transferBuffer == 0) {
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
if (!ensureOpenCB()) {
|
||||
resourceManager.returnTransferBufferThreadSafe(transferBuffer, size);
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
srcLoc.transfer_buffer(transferBuffer).offset(0);
|
||||
@@ -333,7 +358,10 @@ public final class TransferThread {
|
||||
}
|
||||
|
||||
private void recordGpuCopy(long srcHandle, long dstHandle, long readOffset, long writeOffset, long size, long seq) {
|
||||
if (!ensureOpenCB()) return;
|
||||
if (!ensureOpenCB()) {
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
srcBufLoc.buffer(srcHandle).offset((int) readOffset);
|
||||
dstBufLoc.buffer(dstHandle).offset((int) writeOffset);
|
||||
SDL_CopyGPUBufferToBuffer(openCopyPass, srcBufLoc, dstBufLoc, (int) size, false);
|
||||
@@ -344,9 +372,22 @@ public final class TransferThread {
|
||||
}
|
||||
|
||||
private void recordTextureUpload(long transferBuffer, long texHandle, int x, int y, int w, int h, int level, long size, long seq) {
|
||||
if (transferBuffer == 0) return;
|
||||
if (transferBuffer == 0) {
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
if (texHandle == 0) {
|
||||
if (!warnedNullTextureUpload) {
|
||||
warnedNullTextureUpload = true;
|
||||
LOG.warn("Deferred texture upload with no destination texture; dropped (level={} {}x{} at {},{})", level, w, h, x, y);
|
||||
}
|
||||
resourceManager.returnTransferBufferThreadSafe(transferBuffer, size);
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
if (!ensureOpenCB()) {
|
||||
resourceManager.returnTransferBufferThreadSafe(transferBuffer, size);
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
texXferInfo.transfer_buffer(transferBuffer).offset(0);
|
||||
@@ -368,12 +409,7 @@ public final class TransferThread {
|
||||
device.reportGpuFailure("submit transfer command buffer");
|
||||
}
|
||||
|
||||
synchronized (submittedLock) {
|
||||
submittedSeq = openHighestSeq;
|
||||
submittedLock.notifyAll();
|
||||
}
|
||||
|
||||
drainDuePendingFrees();
|
||||
publishSubmittedSeq();
|
||||
|
||||
for (int i = 0, n = pendingReturnHandles.size(); i < n; i++) {
|
||||
resourceManager.returnTransferBufferThreadSafe(pendingReturnHandles.getLong(i), pendingReturnSizes.getLong(i));
|
||||
|
||||
+15
-11
@@ -31,11 +31,6 @@ public final class PerFrameBlockInjector {
|
||||
private PerFrameBlockInjector() {}
|
||||
|
||||
public static String inject(String source, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
final PerFrameUniformBlock[] blocks = new PerFrameUniformBlock[ShaderManager.BLOCK_COUNT];
|
||||
blocks[ShaderManager.BLOCK_PER_FRAME] = normalize(perFrame);
|
||||
blocks[ShaderManager.BLOCK_PER_PASS] = normalize(perPass);
|
||||
if (blocks[0] == null && blocks[1] == null) return source;
|
||||
|
||||
final GLSLParser.Translation_unitContext root;
|
||||
try {
|
||||
root = GlslTransformUtils.parseFullQuiet(source);
|
||||
@@ -43,8 +38,19 @@ public final class PerFrameBlockInjector {
|
||||
return source;
|
||||
}
|
||||
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
collectEdits(root, perFrame, perPass, edits);
|
||||
return edits.isEmpty() ? source : applyEdits(source, edits);
|
||||
}
|
||||
|
||||
public static void collectEdits(GLSLParser.Translation_unitContext root, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass, List<Edit> out) {
|
||||
final PerFrameUniformBlock[] blocks = new PerFrameUniformBlock[ShaderManager.BLOCK_COUNT];
|
||||
blocks[ShaderManager.BLOCK_PER_FRAME] = normalize(perFrame);
|
||||
blocks[ShaderManager.BLOCK_PER_PASS] = normalize(perPass);
|
||||
if (blocks[0] == null && blocks[1] == null) return;
|
||||
|
||||
final int insertAt = firstDeclarationStart(root);
|
||||
if (insertAt < 0) return source;
|
||||
if (insertAt < 0) return;
|
||||
|
||||
final Map<String, String> memberTypes = new LinkedHashMap<>();
|
||||
final Object2IntOpenHashMap<String> memberBlock = new Object2IntOpenHashMap<>();
|
||||
@@ -71,18 +77,16 @@ public final class PerFrameBlockInjector {
|
||||
if (referenced.contains(m.name())) { injected[b] = true; anyInjected = true; break; }
|
||||
}
|
||||
}
|
||||
if (!anyInjected) return source;
|
||||
if (!anyInjected) return;
|
||||
|
||||
final List<Edit> edits = new ArrayList<>(stripEdits.size() + 2);
|
||||
final StringBuilder blockDecls = new StringBuilder(128);
|
||||
for (int b = 0; b < blocks.length; b++) {
|
||||
if (injected[b]) appendBlockText(blockDecls, b, blocks[b].members(), shadowed);
|
||||
}
|
||||
edits.add(new Edit(insertAt, insertAt - 1, blockDecls.toString()));
|
||||
out.add(new Edit(insertAt, insertAt - 1, blockDecls.toString()));
|
||||
for (int i = 0; i < stripEdits.size(); i++) {
|
||||
if (injected[stripBlocks.getInt(i)]) edits.add(stripEdits.get(i));
|
||||
if (injected[stripBlocks.getInt(i)]) out.add(stripEdits.get(i));
|
||||
}
|
||||
return applyEdits(source, edits);
|
||||
}
|
||||
|
||||
private static PerFrameUniformBlock normalize(PerFrameUniformBlock block) {
|
||||
|
||||
@@ -188,19 +188,46 @@ public final class ShaderManager {
|
||||
return new PrewarmTransformResult(transformedSource, Set.of());
|
||||
}
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
|
||||
PerFrameBlockInjector.collectEdits(root, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock, edits);
|
||||
}
|
||||
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(transformedSource, root, glShaderType, "prewarm", true, edits);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
ClipZRemap.collectEdits(root, edits);
|
||||
}
|
||||
SamplerStripper.collectEdits(root, transformedSource, edits);
|
||||
String s = edits.isEmpty() ? transformedSource : GlslVulkanPreprocess.applyEdits(transformedSource, edits);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
|
||||
s = PerFrameBlockInjector.inject(s, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
|
||||
}
|
||||
final String s = edits.isEmpty() ? transformedSource : GlslVulkanPreprocess.applyEdits(transformedSource, edits);
|
||||
return new PrewarmTransformResult(s, meta.boolUniforms());
|
||||
}
|
||||
|
||||
public static PrewarmTransformResult applyPrewarmTransformsFull(String finalSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, int glShaderType) {
|
||||
final String header = finalSource.substring(0, headerLen);
|
||||
final String body = finalSource.substring(headerLen);
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
|
||||
PerFrameBlockInjector.collectEdits(bodyTree, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock, edits);
|
||||
}
|
||||
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(null, bodyTree, glShaderType, "prewarm", true, edits);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
ClipZRemap.collectEdits(bodyTree, edits);
|
||||
}
|
||||
SamplerStripper.collectEdits(bodyTree, body, edits);
|
||||
String outHeader = header;
|
||||
if (meta.needsSamplerless()) {
|
||||
final int nl = header.indexOf('\n');
|
||||
outHeader = header.substring(0, nl) + "\n" + GlslVulkanPreprocess.SAMPLERLESS_EXTENSION + header.substring(nl);
|
||||
}
|
||||
if (edits.isEmpty() && outHeader == header) {
|
||||
return new PrewarmTransformResult(finalSource, meta.boolUniforms());
|
||||
}
|
||||
return new PrewarmTransformResult(outHeader + GlslVulkanPreprocess.applyEdits(body, edits), meta.boolUniforms());
|
||||
}
|
||||
|
||||
public static void prewarmSpirv(String transformedSource, int glShaderType) {
|
||||
prewarmSpirv(transformedSource, null, 0, glShaderType);
|
||||
}
|
||||
|
||||
public static void prewarmSpirv(String transformedSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, int glShaderType) {
|
||||
if (glShaderType != GL20.GL_VERTEX_SHADER && glShaderType != GL20.GL_FRAGMENT_SHADER
|
||||
&& glShaderType != GL43.GL_COMPUTE_SHADER && glShaderType != GL32.GL_GEOMETRY_SHADER) {
|
||||
return;
|
||||
@@ -211,7 +238,9 @@ public final class ShaderManager {
|
||||
if (PREWARM_CACHE.containsKey(key)) return;
|
||||
}
|
||||
|
||||
final PrewarmTransformResult pre = applyPrewarmTransformsFull(transformedSource, glShaderType);
|
||||
final PrewarmTransformResult pre = bodyTree != null
|
||||
? applyPrewarmTransformsFull(transformedSource, bodyTree, headerLen, glShaderType)
|
||||
: applyPrewarmTransformsFull(transformedSource, glShaderType);
|
||||
|
||||
final SpirvCompiler.Result r = SpirvCompiler.compile(pre.source(), shaderKindFor(glShaderType), "prewarm", SpirvCompiler.Options.vulkanForced460Core());
|
||||
if (r.spirv() == null) return;
|
||||
@@ -652,7 +681,7 @@ public final class ShaderManager {
|
||||
return null;
|
||||
}
|
||||
LOG.info("Vertex attribute conversion: program {} converted {}", program, attribs);
|
||||
if (SystemProperties.DUMP_SHADERS) dumpVariantSource(program, src);
|
||||
if (SystemProperties.dumpShaders()) dumpVariantSource(program, src);
|
||||
return v;
|
||||
} finally {
|
||||
memFree(r.spirv());
|
||||
|
||||
Loaded 100 of 324 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user