mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 17:24:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b4ebcf611 | ||
|
|
2845c9e078 | ||
|
|
6f054a09a9 | ||
|
|
37a10eee3f | ||
|
|
744c1f971f | ||
|
|
32d1c27b9c | ||
|
|
f180894da7 | ||
|
|
bb67f82701 | ||
|
|
73f5556595 | ||
|
|
19bc7cbaed | ||
|
|
d189a644e2 | ||
|
|
94ba40ae4d | ||
|
|
961b23fd2d |
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
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$REPO_ROOT/umbra"
|
||||
|
||||
gradle_args=(:compileJava :compileMixinJava :test --no-daemon --console=plain --stacktrace)
|
||||
if [[ -n "${UMBRA_GRADLE_ARGS:-}" ]]; then
|
||||
read -ra extra_args <<< "$UMBRA_GRADLE_ARGS"
|
||||
gradle_args+=("${extra_args[@]}")
|
||||
fi
|
||||
|
||||
sh ./gradlew "${gradle_args[@]}"
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Umbra build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, main ]
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
|
||||
jobs:
|
||||
umbra-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: |
|
||||
21
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
validate-wrappers: true
|
||||
- run: .github/scripts/umbra-build.sh
|
||||
@@ -1,5 +1,6 @@
|
||||
.gradle
|
||||
.kotlin/
|
||||
.lwjgl/
|
||||
.settings
|
||||
/.idea/
|
||||
/.vs/
|
||||
|
||||
@@ -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)
|
||||
+4
-82
@@ -1,19 +1,16 @@
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
|
||||
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
|
||||
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
|
||||
val lwjglDebug = false
|
||||
val gpuHud = false
|
||||
val renderdoc = false
|
||||
val rgp = false
|
||||
extra["lwjglDebug"] = lwjglDebug
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
|
||||
@@ -24,24 +21,13 @@ minecraft {
|
||||
|
||||
extraRunJvmArguments.add("-Dsun.net.client.defaultConnectTimeout=5000")
|
||||
extraRunJvmArguments.add("-Dsun.net.client.defaultReadTimeout=5000")
|
||||
// TODO: Remove once GTNHGradle includes shaderc/spvc in the default lwjgl3Bindings list
|
||||
lwjgl3Bindings.addAll("shaderc", "spvc")
|
||||
lwjgl3Version = libs.versions.lwjgl3.get()
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.debug.redirectorLogspam=true")
|
||||
|
||||
}
|
||||
|
||||
// Forward -Dangelica.* from the gradle invocation to the client
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
for ((key, value) in System.getProperties()) {
|
||||
val name = key.toString()
|
||||
if (name.startsWith("angelica.")) {
|
||||
jvmArgs("-D$name=$value")
|
||||
}
|
||||
}
|
||||
}
|
||||
apply(from = "gradle/angelica-run-common.gradle.kts")
|
||||
|
||||
if (gpuHud) {
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
@@ -57,47 +43,12 @@ if (gpuHud) {
|
||||
|
||||
configurations.all {
|
||||
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(module("com.github.GTNewHorizons:lwjgl3ify"))
|
||||
.using(module("com.github.GTNewHorizons:lwjgl3ify:${libs.versions.lwjgl3ify.get()}"))
|
||||
}
|
||||
}
|
||||
|
||||
// lwjgl3ify pulls a pinned lwjgl 3.4.2 snapshot transitively from here.
|
||||
repositories {
|
||||
maven {
|
||||
name = "Maven Central Snapshots"
|
||||
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
|
||||
content { includeGroup("org.lwjgl") }
|
||||
}
|
||||
}
|
||||
|
||||
val osName = System.getProperty("os.name").lowercase()
|
||||
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
|
||||
val isMacOsArm64 = isMacOs && System.getProperty("os.arch") == "aarch64"
|
||||
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient") && name != "runClient") {
|
||||
if (isMacOs) {
|
||||
// SDL3 / Cocoa / Metal must initialize on the JVM main thread.
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
|
||||
if (lwjglDebug) {
|
||||
environment("METAL_DEVICE_WRAPPER_TYPE", "1")
|
||||
environment("METAL_DEBUG_ERROR_MODE", "0")
|
||||
environment("MTL_SHADER_VALIDATION", "1")
|
||||
environment("MTL_SHADER_VALIDATION_REPORT_TO_STDERR", "1")
|
||||
environment("MTL_DEBUG_LAYER", "1")
|
||||
environment("MallocScribble", "1")
|
||||
environment("MallocPreScribble", "1")
|
||||
environment("MallocGuardEdges", "1")
|
||||
environment("MallocStackLogging", "1")
|
||||
environment("MallocStackLoggingNoCompact", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linux runClient: RenderDoc frame capture (F12) OR RADV RGP/SQTT capture. Mutually exclusive; RGP wins
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient") && !isMacOs && !osName.contains("windows")) {
|
||||
@@ -126,14 +77,6 @@ tasks.withType<JavaExec>().configureEach {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
val projectVersion = project.version.toString()
|
||||
inputs.property("version", projectVersion)
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to projectVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copyDependencies") {
|
||||
group = "Angelica"
|
||||
description = "Collect dependencies into the testDependencies folder"
|
||||
@@ -206,6 +149,7 @@ fun Test.configureAngelicaJava8() {
|
||||
tasks.test {
|
||||
useJUnitPlatform { excludeTags = setOf("gl-core") }
|
||||
configureAngelicaJava8()
|
||||
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
|
||||
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
|
||||
}
|
||||
|
||||
@@ -233,29 +177,7 @@ val glCoreTest by tasks.registering(Test::class) {
|
||||
|
||||
tasks.test { finalizedBy(glCoreTest) }
|
||||
|
||||
tasks.shadowJar {
|
||||
dependsOn(embedOnly)
|
||||
from(embedOnly.map(::zipTree))
|
||||
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
mergeServiceFiles()
|
||||
|
||||
minimize {
|
||||
exclude(project(rootProject.path))
|
||||
exclude(project(":glsm"))
|
||||
exclude(project(":lwjgl3-backend"))
|
||||
exclude(project(":sdl-gpu"))
|
||||
exclude(dependency("org.taumc:.*:.*"))
|
||||
exclude(dependency("org.antlr:.*:.*"))
|
||||
}
|
||||
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
relocate("com.gtnewhorizons.angelica.config", "com.gtnewhorizons.angelica.config")
|
||||
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
|
||||
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
|
||||
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
|
||||
}
|
||||
apply(from = "gradle/angelica-shadow-common.gradle.kts")
|
||||
|
||||
tasks.withType<TaskPublishCurseForge>().configureEach {
|
||||
uploadArtifacts.forEach { it.addGameVersion("Client") }
|
||||
|
||||
@@ -52,8 +52,8 @@ configurations.configureEach {
|
||||
}
|
||||
|
||||
DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
@@ -104,7 +104,6 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
|
||||
// Celeritas (isTransitive=false: deps provided by GTNHLib/runtime)
|
||||
embedOnly(libs.celeritas.common) { isTransitive = false }
|
||||
embedOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.109-GTNH:dev")
|
||||
@@ -177,7 +176,7 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.5-GTNH:dev")
|
||||
|
||||
// Distant Horizons
|
||||
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
|
||||
compileOnly(libs.distant.horizons.api)
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
@@ -208,5 +207,6 @@ afterEvaluate {
|
||||
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
testImplementation(libs.distant.horizons.api)
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
# A specification for models
|
||||
|
||||
🌐 **Languages:** [English](model-spec.md) | [Українська](model-spec_uk.md)
|
||||
|
||||
This is borrowed from [the unofficial Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) under the CC BY-NC-SA 3.0.
|
||||
|
||||
The folder `assets/<namespace>/models/block` holds the model files for all the specified variants. The names of the files can be changed, but must always correspond with the names used in the variant files.
|
||||
@@ -8,7 +11,7 @@ The folder `assets/<namespace>/models/block` holds the model files for all the s
|
||||
* Can be set to `"builtin/generated"` to use a model that is created out of the specified icon. Note that only the first layer is supported, and rotation can be achieved only by using block states files.
|
||||
* `[TAG_byte]` **ambientocclusion**: Whether to use [ambient occlusion](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` - default), or not (`false`). Note:only works on Parent file
|
||||
* `[TAG_object]` **display**: Holds the different places where item models are displayed.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. '''fixed''' refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. **'''fixed'''** refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_list]` **rotation**: Specifies the rotation of the model according to the scheme `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Specifies the position of the model according to the scheme `[x, y, z]`. The values are clamped between -80 and 80.
|
||||
* `[TAG_list]` **scale**: Specifies the scale of the model according to the scheme `[x, y, z]`. If the value is greater than 4, it is displayed as 4.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Специфікація моделей
|
||||
|
||||
🌐 **Мови:** [English](model-spec.md) | [Українська](model-spec_uk.md)
|
||||
|
||||
Запозичено з [неофіційної Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) на умовах ліцензії CC BY-NC-SA 3.0.
|
||||
|
||||
Папка `assets/<namespace>/models/block` містить файли моделей для усіх вказаних варіантів. Назви файлів можна змінювати, але вони завжди повинні відповідати назвам, що використовуються у файлах варіантів.
|
||||
|
||||
* `[TAG_compound]` Кореневий тег
|
||||
* `[TAG_string]` **parent**: Завантажує іншу модель із вказаного шляху у формі [локації ресурсу](https://minecraft.wiki/w/Tutorials/Models#File_path). Якщо задано і `"parent"`, і `"elements"`, тег `"elements"` перевизначає тег `"elements"` із попередньої моделі.
|
||||
* Може мати значення `"builtin/generated"`, щоб використовувати модель, створену із вказаної піктограми. Зверніть увагу: підтримується лише перший шар, а обертання можна досягти лише за допомогою файлів станів блоку.
|
||||
* `[TAG_byte]` **ambientocclusion**: Визначає, чи використовувати [розсіяне затінення](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` — за замовчуванням), чи ні (`false`). Примітка: працює лише у батьківському файлі.
|
||||
* `[TAG_object]` **display**: Містить різні місця відображення моделей предметів.
|
||||
* `[TAG_compound]` ***Position***: Має назву `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground` або `fixed`. Місце, де відображається модель предмета. Містить її обертання, зміщення та масштаб для вказаної ситуації. **'''fixed'''** стосується рамок для предметів, а решта відповідають своїм назвам. Зверніть увагу: зміщення застосовується до моделі перед обертанням.
|
||||
* `[TAG_list]` **rotation**: Задає обертання моделі за схемою `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Задає зміщення моделі за схемою `[x, y, z]`. Значення обмежені в діапазоні від -80 до 80.
|
||||
* `[TAG_list]` **scale**: Задає масштаб моделі за схемою `[x, y, z]`. Якщо значення більше за 4, воно відображається як 4.
|
||||
* `[TAG_compound]` **textures**: Містить текстури моделі у формі [локації ресурсу](https://minecraft.wiki/w/Tutorials/Models#File_path) або у вигляді іншої текстурної змінної.
|
||||
* `[TAG_string]` **particle**: Текстура, з якої завантажуються частинки. Ця текстура також використовується як оверлей, якщо ви перебуваєте в [порталі Незеру](https://minecraft.wiki/w/Nether_portal), а також для нерухомих текстур [води](https://minecraft.wiki/w/water) та [лави](https://minecraft.wiki/w/Lava).<sup>[[1]](https://bugs.mojang.com/browse/MC-240042)</sup> Ця текстура також вважається текстурною змінною, на яку можна посилатися як `"#particle"`. Примітка: Усі частинки руйнування для блоків без моделей вшиті в код, наприклад, для [бар’єрів](https://minecraft.wiki/w/Barrier).
|
||||
* `[TAG_string]` ***Texture variable***: Визначає текстурну змінну з ім’ям `name` та призначає її текстурі або іншій змінній.
|
||||
* `[TAG_list]` **elements**: Містить усі елементи моделі. Вони можуть мати лише кубічну форму. Якщо задано і `"parent"`, і `"elements"`, тег `"elements"` перевизначає тег `"elements"` із попередньої моделі.
|
||||
* `[TAG_compound]` Елемент.
|
||||
* `[TAG_list]` **from**: Початкова точка кубоїда за схемою `[x, y, z]`. Значення мають бути від -16 до 32.
|
||||
* `[TAG_list]` **to**: Кінцева точка кубоїда за схемою `[x, y, z]`. Значення мають бути від -16 до 32.
|
||||
* `[TAG_compound]` **rotation**: Визначає обертання елемента.
|
||||
* `[TAG_list]` **origin**: Встановлює центр обертання за схемою `[x, y, z]`.
|
||||
* `[TAG_string]` **axis**: Задає вісь обертання: може бути `"x"`, `"y"` або `"z"`.
|
||||
* `[TAG_float]` **angle**: Задає кут обертання. Може бути від 45 до -45 градусів із кроком у 22,5 градуса.
|
||||
* `[TAG_byte]` **rescale**: Визначає, чи масштабувати грані по всьому блоку. Може бути `true` або `false`. За замовчуванням — `false`.
|
||||
* `[TAG_byte]` **shade**: Визначає, чи рендерити тіні (`true` — за замовчуванням), чи ні (`false`).
|
||||
* `[TAG_compound]` **faces**: Містить усі грані кубоїда. Якщо грань пропущено, вона не рендериться.
|
||||
* `[TAG_compound]` ***Face***: Має назву `down`, `up`, `north`, `south`, `west` або `east`. Містить властивості вказаної грані.
|
||||
* `[TAG_list]` **uv**: Визначає ділянку текстури для використання за схемою `[x1, y1, x2, y2]`. Поведінка текстури стає непередбачуваною, якщо значення UV виходять за межі 0 або 16. Якщо значення `x1` та `x2` поміняти місцями (наприклад, з `0, 0, 16, 16` на `16, 0, 0, 16`), текстура віддзеркалюється. Параметр UV є необов’язковим; якщо його не вказано, він автоматично генерується на основі позиції елемента.
|
||||
* `[TAG_string]` **texture**: Вказує текстуру у формі текстурної змінної зі знаком `#` на початку.
|
||||
* `[TAG_string]` **cullface**: Визначає, чи не потрібно рендерити грань, якщо до неї в указаній позиції торкається інший блок. Позиція може бути: `down`, `up`, `north`, `south`, `west` або `east`. Цей параметр також визначає сторону блоку, з якої береться рівень освітлення для цієї грані; якщо параметр не задано, за замовчуванням використовується відповідна сторона.
|
||||
* `[TAG_int]` **rotation**: Обертає текстуру за годинниковою стрілкою на вказаний кут у градусах. Може бути 0, 90, 180 або 270. За замовчуванням — 0. Обертання не впливає на те, яка частина текстури використовується. Натомість воно зводиться до перестановки вершин вибраної текстури (вибраних неявно або явно через `uv`).
|
||||
* `[TAG_int]` **tintindex**: Визначає, чи потрібно тонувати текстуру за допомогою вшитого індексу відтінку. Значення за замовчуванням, -1, вказує на те, що відтінок не використовується. Будь-яке інше число передається в `BlockColors` для отримання значення відтінку, відповідного цьому індексу. Однак більшість блоків не мають визначеного значення відтінку (у такому разі використовується білий колір). Крім того, жоден ванільний блок наразі не використовує кілька значень відтінку, тому значення індексу відтінку ігнорується (якщо воно відмінне від -1); втім, його можна використовувати для модифікованих блоків, яким потрібні кілька різних значень відтінку в одному блоці.
|
||||
+22
-1
@@ -46,6 +46,27 @@ tasks.withType<Jar>().configureEach {
|
||||
exclude("net/minecraft/**")
|
||||
}
|
||||
|
||||
val stubsElements by configurations.creating {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
attributes {
|
||||
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, Usage.JAVA_RUNTIME))
|
||||
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class.java, Category.LIBRARY))
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
outgoing {
|
||||
capability("${project.group}:glsm-stubs:${project.version}")
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
add(stubsElements.name, sourceSets["stubs"].java.classesDirectory) {
|
||||
type = "java-classes-directory"
|
||||
builtBy(tasks.named("stubsClasses"))
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Mojang"
|
||||
@@ -114,6 +135,7 @@ dependencies {
|
||||
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
|
||||
// @Lwjgl3Aware annotation
|
||||
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
|
||||
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
|
||||
compileOnly(libs.lombok) { isTransitive = false }
|
||||
annotationProcessor(libs.lombok)
|
||||
@@ -140,7 +162,6 @@ dependencies {
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.log4j.core)
|
||||
testImplementation(libs.celeritas.common) { isTransitive = false }
|
||||
testRuntimeOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
|
||||
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
|
||||
testRuntimeOnly(libs.commons.lang3)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ public final class SystemProperties {
|
||||
public static final int FLYBY_LENGTH = Integer.getInteger("angelica.flyby.length", 0); // Blocks for moving routes, ticks for stationary
|
||||
public static final double FLYBY_SPEED = parseDouble("angelica.flyby.speed"); // Travel speed in blocks per tick
|
||||
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
|
||||
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
|
||||
|
||||
// Debug
|
||||
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
|
||||
@@ -62,12 +63,14 @@ public final class SystemProperties {
|
||||
private static final boolean DUMP_SHADERS_PROP = Boolean.getBoolean("angelica.debug.dumpShaders");
|
||||
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
|
||||
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
|
||||
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
|
||||
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
|
||||
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
|
||||
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
|
||||
|
||||
// Set by us, read by celeritas
|
||||
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
|
||||
public static final String KEY_CELERITAS_LWJGL_SERVICE = "celeritas.lwjglService";
|
||||
|
||||
public static boolean dumpShaders() {
|
||||
return DUMP_SHADERS_PROP || isDeobf();
|
||||
|
||||
@@ -564,7 +564,7 @@ public final class GLDebug {
|
||||
/**
|
||||
* Overload that lets you gate the String concat behind the debugState
|
||||
*/
|
||||
public static void pushGroup(String prefix, int value) {
|
||||
public static void pushGroup(String prefix, long value) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.pushGroup(prefix + value);
|
||||
}
|
||||
|
||||
@@ -493,7 +493,9 @@ public class GLStateManager {
|
||||
|
||||
public static void initialize(GLSMInitConfig config) {
|
||||
initConfig = config;
|
||||
preInit(config.getDisplayWidth(), config.getDisplayHeight());
|
||||
final int width = config.getDisplayWidth() > 0 ? config.getDisplayWidth() : Display.getWidth();
|
||||
final int height = config.getDisplayHeight() > 0 ? config.getDisplayHeight() : Display.getHeight();
|
||||
preInit(width, height);
|
||||
init(config.getPostInitCallback());
|
||||
}
|
||||
|
||||
@@ -2003,6 +2005,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glBindTexture(int target, int texture) {
|
||||
if (TextureInfoCache.isProxyTarget(target)) return;
|
||||
final GLContextState glCtx = ctx();
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
@@ -3534,6 +3537,9 @@ public class GLStateManager {
|
||||
Display.swapBuffers();
|
||||
}
|
||||
|
||||
private static boolean firstClearPending;
|
||||
private static long firstClearBlockNanos;
|
||||
|
||||
public static void updateDisplay() throws LWJGLException {
|
||||
updateDisplay(true);
|
||||
}
|
||||
@@ -3541,17 +3547,19 @@ public class GLStateManager {
|
||||
public static void updateDisplay(boolean processMessages) throws LWJGLException {
|
||||
if (Thread.currentThread() != MainThread) {
|
||||
swapBuffers();
|
||||
if (processMessages) Display.processMessages();
|
||||
if (processMessages) pumpDisplayMessages();
|
||||
return;
|
||||
}
|
||||
if (processMessages) pumpDisplayMessages();
|
||||
if (!RENDER_BACKEND.wantsDisplayUpdateGateTiming()) {
|
||||
Display.update(processMessages);
|
||||
Display.update(false);
|
||||
return;
|
||||
}
|
||||
final long start = System.nanoTime();
|
||||
Display.update(processMessages);
|
||||
Display.update(false);
|
||||
final long end = System.nanoTime();
|
||||
RENDER_BACKEND.recordFrameGate(end - start, end);
|
||||
RENDER_BACKEND.recordGate(end - start, end);
|
||||
firstClearPending = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3902,9 +3910,26 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (firstClearPending && Thread.currentThread() == MainThread && getDrawFramebuffer() == 0) {
|
||||
firstClearPending = false;
|
||||
final long start = System.nanoTime();
|
||||
RENDER_BACKEND.clear(mask);
|
||||
final long end = System.nanoTime();
|
||||
if (end - start > 0) {
|
||||
firstClearBlockNanos += end - start;
|
||||
RENDER_BACKEND.recordGate(end - start, end);
|
||||
}
|
||||
return;
|
||||
}
|
||||
RENDER_BACKEND.clear(mask);
|
||||
}
|
||||
|
||||
public static long takeFirstClearBlockNanos() {
|
||||
final long value = firstClearBlockNanos;
|
||||
firstClearBlockNanos = 0L;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void glPushAttrib(int mask) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
@@ -4554,6 +4579,11 @@ public class GLStateManager {
|
||||
};
|
||||
}
|
||||
|
||||
private static int mipLevelSize(int base, int level) {
|
||||
if (base <= 0 || level < 0 || level >= Integer.SIZE - 1) return 1;
|
||||
return Math.max(base >> level, 1);
|
||||
}
|
||||
|
||||
public static int glGetTexLevelParameteri(int target, int level, int pname) {
|
||||
if (target != GL11.GL_TEXTURE_2D || !isCachingEnabled()) {
|
||||
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
|
||||
@@ -4568,8 +4598,8 @@ public class GLStateManager {
|
||||
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> Math.max(info.getWidth() >> level, 1);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> Math.max(info.getHeight() >> level, 1);
|
||||
case GL11.GL_TEXTURE_WIDTH -> mipLevelSize(info.getWidth(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> mipLevelSize(info.getHeight(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
if (info.needsInternalFormatResolve() && isRecordingDisplayList()) {
|
||||
throw new IllegalStateException(String.format("glGetTexLevelParameteri(GL_TEXTURE_INTERNAL_FORMAT) needs to resolve a generic compressed format for texture %d during display list recording", getBoundTextureForServerState()));
|
||||
@@ -5246,20 +5276,8 @@ public class GLStateManager {
|
||||
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 void setPresentSuppressed(boolean suppressed) {
|
||||
RENDER_BACKEND.setPresentSuppressed(suppressed);
|
||||
}
|
||||
|
||||
public static boolean hasSwapchainBackpressure() {
|
||||
@@ -5267,7 +5285,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void pumpDisplayMessages() {
|
||||
Display.processMessages();
|
||||
RENDER_BACKEND.pumpDisplayMessages();
|
||||
}
|
||||
|
||||
public static int glGetError() {
|
||||
|
||||
@@ -9,7 +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 org.taumc.celeritas.lwjgl.LWJGLServiceProvider;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
+4
-4
@@ -40,7 +40,7 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
|
||||
/**
|
||||
* LWJGL2 GL implementation of {@link RenderBackend}.
|
||||
@@ -122,11 +122,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int queryDisplayRefreshRateHz() {
|
||||
protected long queryRefreshPeriodNanos() {
|
||||
final DisplayMode mode = Display.getDisplayMode();
|
||||
if (mode != null && mode.getFrequency() > 0) return mode.getFrequency();
|
||||
if (mode != null && mode.getFrequency() > 0) return periodFromIntegerHz(mode.getFrequency());
|
||||
final DisplayMode desktop = Display.getDesktopDisplayMode();
|
||||
return desktop == null ? 0 : desktop.getFrequency();
|
||||
return desktop == null ? 0L : periodFromIntegerHz(desktop.getFrequency());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.input.Mouse;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public final class MainThreadPump {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("MainThreadPump");
|
||||
|
||||
private static final Tracy.ZoneId Z_DISPLAY_MESSAGES = Tracy.zoneId("displayMessages", Tracy.COLOR_CLIENT);
|
||||
|
||||
private static final long PARK_MILLIS = 20L;
|
||||
private static final long STALL_WARN_NANOS = 5_000_000_000L;
|
||||
|
||||
public static void pollInput() {
|
||||
Keyboard.poll();
|
||||
Mouse.poll();
|
||||
}
|
||||
|
||||
private final Executor mainExecutor;
|
||||
private final BooleanSupplier isMainThread;
|
||||
private final Runnable pumpBody;
|
||||
private final Runnable pollBody;
|
||||
private final Runnable task = this::runPump;
|
||||
private final Object lock = new Object();
|
||||
|
||||
private boolean done;
|
||||
private Throwable failure;
|
||||
private ClassLoader callerLoader;
|
||||
|
||||
public MainThreadPump(Executor mainExecutor, BooleanSupplier isMainThread, Runnable pumpBody, Runnable pollBody) {
|
||||
this.mainExecutor = mainExecutor;
|
||||
this.isMainThread = isMainThread;
|
||||
this.pumpBody = pumpBody;
|
||||
this.pollBody = pollBody;
|
||||
}
|
||||
|
||||
public void pumpMessages() {
|
||||
Tracy.beginZone(Z_DISPLAY_MESSAGES);
|
||||
try {
|
||||
if (isMainThread.getAsBoolean()) {
|
||||
pumpBody.run();
|
||||
} else {
|
||||
synchronized (lock) {
|
||||
runPumpOnMainThread();
|
||||
}
|
||||
}
|
||||
pollBody.run();
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private void runPumpOnMainThread() {
|
||||
final Thread self = Thread.currentThread();
|
||||
callerLoader = self.getContextClassLoader();
|
||||
failure = null;
|
||||
done = false;
|
||||
mainExecutor.execute(task);
|
||||
boolean interrupted = false;
|
||||
boolean stallReported = false;
|
||||
final long stallAt = System.nanoTime() + STALL_WARN_NANOS;
|
||||
while (!done) {
|
||||
try {
|
||||
lock.wait(PARK_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
interrupted = true;
|
||||
}
|
||||
if (!done && !stallReported && System.nanoTime() - stallAt >= 0L) {
|
||||
stallReported = true;
|
||||
LOGGER.warn("Display message pump has been waiting on the RFB main thread for over {} ms", STALL_WARN_NANOS / 1_000_000L);
|
||||
}
|
||||
}
|
||||
if (interrupted) self.interrupt();
|
||||
final Throwable t = failure;
|
||||
if (t != null) {
|
||||
throw asUnchecked(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void runPump() {
|
||||
final Thread self = Thread.currentThread();
|
||||
final ClassLoader saved = self.getContextClassLoader();
|
||||
try {
|
||||
self.setContextClassLoader(callerLoader);
|
||||
pumpBody.run();
|
||||
} catch (Throwable t) {
|
||||
failure = t;
|
||||
} finally {
|
||||
try {
|
||||
self.setContextClassLoader(saved);
|
||||
} finally {
|
||||
synchronized (lock) {
|
||||
done = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static RuntimeException asUnchecked(Throwable t) {
|
||||
if (t instanceof RuntimeException runtime) return runtime;
|
||||
if (t instanceof Error error) throw error;
|
||||
return new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.backend;
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -12,6 +13,7 @@ import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
@@ -72,7 +74,9 @@ public abstract class RenderBackend {
|
||||
|
||||
public final void setVSyncMode(VSyncMode preferred) {
|
||||
setVSyncPreference(preferred);
|
||||
publishVSyncMode(applyVSyncMode(preferred));
|
||||
final VSyncMode mode = applyVSyncMode(preferred);
|
||||
vsyncHonored = applyHonored();
|
||||
publishVSyncMode(mode);
|
||||
}
|
||||
|
||||
public final void setVSyncPreference(VSyncMode preferred) {
|
||||
@@ -90,50 +94,146 @@ public abstract class RenderBackend {
|
||||
|
||||
protected abstract VSyncMode applyVSyncMode(VSyncMode preferred);
|
||||
|
||||
protected boolean applyHonored() { return true; }
|
||||
|
||||
private boolean vsyncHonored = true;
|
||||
|
||||
public final boolean vsyncHonored() { return vsyncHonored; }
|
||||
|
||||
public boolean supportsVSyncMode(VSyncMode mode) { return mode != VSyncMode.MAILBOX; }
|
||||
|
||||
protected final void publishVSyncMode(VSyncMode mode) { effectiveVSyncMode = mode; }
|
||||
protected final void publishVSyncMode(VSyncMode mode) {
|
||||
effectiveVSyncMode = mode;
|
||||
bumpDisplayGeneration();
|
||||
}
|
||||
|
||||
public final VSyncMode getEffectiveVSyncMode() { return effectiveVSyncMode; }
|
||||
|
||||
private int displayGeneration;
|
||||
|
||||
public final int displayGeneration() { return displayGeneration; }
|
||||
|
||||
protected final void bumpDisplayGeneration() { displayGeneration++; }
|
||||
|
||||
private static final long REFRESH_POLL_NANOS = 1_000_000_000L;
|
||||
private static final long MIN_PLAUSIBLE_PERIOD_NANOS = 1_000_000L;
|
||||
private static final long MAX_PLAUSIBLE_PERIOD_NANOS = 50_000_000L;
|
||||
|
||||
private long refreshPollNanos;
|
||||
private int cachedRefreshHz;
|
||||
private long cachedRefreshPeriodNanos;
|
||||
|
||||
public final int getDisplayRefreshRateHz() {
|
||||
public final long refreshPeriodNanos() {
|
||||
final long now = System.nanoTime();
|
||||
if (refreshPollNanos == 0L || now - refreshPollNanos >= REFRESH_POLL_NANOS) {
|
||||
refreshPollNanos = now;
|
||||
cachedRefreshHz = queryDisplayRefreshRateHz();
|
||||
final long fresh = plausiblePeriodNanos(queryRefreshPeriodNanos());
|
||||
if (fresh != cachedRefreshPeriodNanos) bumpDisplayGeneration();
|
||||
cachedRefreshPeriodNanos = fresh;
|
||||
}
|
||||
return cachedRefreshHz;
|
||||
return cachedRefreshPeriodNanos;
|
||||
}
|
||||
|
||||
protected int queryDisplayRefreshRateHz() { return 0; }
|
||||
protected long queryRefreshPeriodNanos() { return 0L; }
|
||||
|
||||
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;
|
||||
static long plausiblePeriodNanos(long periodNanos) {
|
||||
return (periodNanos >= MIN_PLAUSIBLE_PERIOD_NANOS && periodNanos <= MAX_PLAUSIBLE_PERIOD_NANOS) ? periodNanos : 0L;
|
||||
}
|
||||
|
||||
public static long periodFromRational(int numerator, int denominator, float fallbackHz) {
|
||||
if (numerator > 0 && denominator > 0) {
|
||||
final long scaledDenominator = 1_000_000_000L * denominator;
|
||||
return (scaledDenominator + numerator / 2) / numerator;
|
||||
}
|
||||
return fallbackHz > 0.0f ? Math.round(1_000_000_000.0 / fallbackHz) : 0L;
|
||||
}
|
||||
|
||||
private static final int[] TRUNCATED_NTSC_HZ = {23, 29, 59, 71, 119, 143, 239};
|
||||
|
||||
private static boolean isTruncatedNtscRate(int hz) {
|
||||
for (int candidate : TRUNCATED_NTSC_HZ) {
|
||||
if (candidate == hz) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static long periodFromIntegerHz(int hz) {
|
||||
if (hz <= 0) return 0L;
|
||||
if (isTruncatedNtscRate(hz)) {
|
||||
final long denominator = (hz + 1) * 1000L;
|
||||
return Math.round(1_000_000_000.0 * 1001 / denominator);
|
||||
}
|
||||
return Math.round(1_000_000_000.0 / hz);
|
||||
}
|
||||
|
||||
public static int hzFromPeriod(long periodNanos) {
|
||||
return periodNanos <= 0L ? 0 : (int) Math.round(1_000_000_000.0 / periodNanos);
|
||||
}
|
||||
|
||||
public boolean hasSwapchainBackpressure() { return false; }
|
||||
|
||||
private long frameGateNanos;
|
||||
private long frameGateEndNanos;
|
||||
public static final class GateSample {
|
||||
public long durationNanos;
|
||||
public long endNanos;
|
||||
public long gpuWaitNanos;
|
||||
}
|
||||
|
||||
public boolean gateAnchorsNextFrameStart() { return false; }
|
||||
static final class GateRecorder {
|
||||
|
||||
private long gateDurationNanos;
|
||||
private long gateEndNanos;
|
||||
private long gpuWaitNanos;
|
||||
|
||||
synchronized void recordGate(long durationNanos, long endNanos) {
|
||||
if (gateEndNanos != 0L && gateDurationNanos >= durationNanos) return;
|
||||
gateDurationNanos = durationNanos;
|
||||
gateEndNanos = endNanos;
|
||||
}
|
||||
|
||||
synchronized void recordGpuWait(long nanos) {
|
||||
gpuWaitNanos += nanos;
|
||||
}
|
||||
|
||||
synchronized void readGateSample(GateSample out) {
|
||||
final long end = gateEndNanos;
|
||||
out.durationNanos = end == 0L ? 0L : gateDurationNanos;
|
||||
out.endNanos = end;
|
||||
out.gpuWaitNanos = gpuWaitNanos;
|
||||
gpuWaitNanos = 0L;
|
||||
gateEndNanos = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private final GateRecorder gateRecorder = new GateRecorder();
|
||||
|
||||
public boolean wantsDisplayUpdateGateTiming() { return true; }
|
||||
|
||||
public void recordFrameGate(long durationNanos, long endNanos) {
|
||||
frameGateNanos = durationNanos;
|
||||
frameGateEndNanos = endNanos;
|
||||
public final void recordGate(long durationNanos, long endNanos) {
|
||||
gateRecorder.recordGate(durationNanos, endNanos);
|
||||
}
|
||||
|
||||
public long lastFrameGateNanos() { return frameGateNanos; }
|
||||
public final void recordGpuWait(long nanos) {
|
||||
gateRecorder.recordGpuWait(nanos);
|
||||
}
|
||||
|
||||
public long lastFrameGateEndNanos() { return frameGateEndNanos; }
|
||||
public final void readGateSample(GateSample out) {
|
||||
gateRecorder.readGateSample(out);
|
||||
}
|
||||
|
||||
public void awaitPresent() {}
|
||||
|
||||
public void parkNanos(long nanos) {
|
||||
LockSupport.parkNanos(nanos);
|
||||
}
|
||||
|
||||
public void pumpDisplayMessages() {
|
||||
Display.processMessages();
|
||||
}
|
||||
|
||||
private boolean presentSuppressed;
|
||||
|
||||
public final void setPresentSuppressed(boolean suppressed) { presentSuppressed = suppressed; }
|
||||
|
||||
public final boolean isPresentSuppressed() { return presentSuppressed; }
|
||||
|
||||
public void onFrameBegin() {
|
||||
if (GLStateManager.takeStateSeedPending()) {
|
||||
@@ -144,7 +244,7 @@ public abstract class RenderBackend {
|
||||
public void onFrameEnd() {}
|
||||
|
||||
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
|
||||
public void onPreSwapchainInvalidatingChange(Object change) {}
|
||||
public void onPreSwapchainInvalidatingChange(Object change) { bumpDisplayGeneration(); }
|
||||
|
||||
public boolean handleMakeCurrent(Object drawable) { return false; }
|
||||
public boolean handleReleaseContext(Object drawable) { return false; }
|
||||
|
||||
@@ -80,6 +80,10 @@ public final class ShaderManager {
|
||||
warmUp();
|
||||
enabled = true;
|
||||
|
||||
if (GLStateManager.getActiveProgram() == 0) {
|
||||
activate();
|
||||
}
|
||||
|
||||
GLStateManager.LOGGER.info("FFP shader emulation enabled");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
public final class FrameHooks {
|
||||
|
||||
private FrameHooks() {}
|
||||
|
||||
private static boolean gameLoopStarted;
|
||||
private static long frameCounter;
|
||||
private static boolean frameGroupActive;
|
||||
|
||||
public static void bootstrapFirstFrame() {
|
||||
DisplayListManager.abortIfLeaked();
|
||||
if (!gameLoopStarted) {
|
||||
gameLoopStarted = true;
|
||||
RENDER_BACKEND.onFrameBegin();
|
||||
pushFrameGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public static void frameEnd() {
|
||||
popFrameGroup();
|
||||
RENDER_BACKEND.onFrameEnd();
|
||||
}
|
||||
|
||||
public static void frameBegin() {
|
||||
if (gameLoopStarted) {
|
||||
RENDER_BACKEND.onFrameBegin();
|
||||
pushFrameGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
BackendManager.shutdown();
|
||||
}
|
||||
|
||||
public static void bindSplashVao() {
|
||||
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
|
||||
}
|
||||
|
||||
public static void splashFinished() {
|
||||
GLStateManager.markSplashComplete("SplashProgress.finish");
|
||||
}
|
||||
|
||||
private static void pushFrameGroup() {
|
||||
CaptureGate.refresh();
|
||||
GLDebug.pushGroup("frame:", frameCounter);
|
||||
frameGroupActive = true;
|
||||
}
|
||||
|
||||
private static void popFrameGroup() {
|
||||
if (frameGroupActive) {
|
||||
GLDebug.popGroup();
|
||||
frameGroupActive = false;
|
||||
frameCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@@ -14,7 +16,7 @@ public final class GLSMInitConfig {
|
||||
@Getter private final int displayWidth;
|
||||
@Getter private final int displayHeight;
|
||||
@Getter private final Runnable postInitCallback;
|
||||
@Getter private final boolean DSAEnabled;
|
||||
private final boolean dsaEnabled;
|
||||
@Getter private final boolean noErrorChecks;
|
||||
|
||||
private GLSMInitConfig(Builder builder) {
|
||||
@@ -26,7 +28,11 @@ public final class GLSMInitConfig {
|
||||
this.displayWidth = builder.displayWidth;
|
||||
this.displayHeight = builder.displayHeight;
|
||||
this.postInitCallback = builder.postInitCallback;
|
||||
this.DSAEnabled = builder.enableDSA;
|
||||
this.dsaEnabled = builder.dsaEnabled;
|
||||
}
|
||||
|
||||
public boolean isDSAEnabled() {
|
||||
return dsaEnabled;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
@@ -34,14 +40,14 @@ public final class GLSMInitConfig {
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private boolean lwjglDebug = false;
|
||||
private boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
|
||||
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
|
||||
private Consumer<DirectTessellator> directDrawer = null;
|
||||
private Runnable streamingDrawerDestroy = null;
|
||||
private Consumer<DirectTessellator> directDrawer = TessellatorStreamingDrawer::drawDirect;
|
||||
private Runnable streamingDrawerDestroy = TessellatorStreamingDrawer::destroy;
|
||||
private int displayWidth = 0;
|
||||
private int displayHeight = 0;
|
||||
private Runnable postInitCallback = null;
|
||||
private boolean enableDSA = false;
|
||||
private boolean dsaEnabled = true;
|
||||
private boolean noErrorChecks;
|
||||
|
||||
private Builder() {}
|
||||
@@ -83,7 +89,7 @@ public final class GLSMInitConfig {
|
||||
}
|
||||
|
||||
public Builder enableDSA(boolean enableDSA) {
|
||||
this.enableDSA = enableDSA;
|
||||
this.dsaEnabled = enableDSA;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
public final class TextureUtilHooks {
|
||||
|
||||
private TextureUtilHooks() {}
|
||||
|
||||
private static int savedFilterTextureId;
|
||||
|
||||
public static void setMaxLevel(int mipmapLevels) {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
|
||||
}
|
||||
|
||||
public static void captureBoundFilterTexture() {
|
||||
savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
|
||||
}
|
||||
|
||||
public static void clearSavedFilterTexture() {
|
||||
savedFilterTextureId = 0;
|
||||
}
|
||||
|
||||
public static void restoreFilter(int min, int mag) {
|
||||
final int id = savedFilterTextureId;
|
||||
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
|
||||
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
|
||||
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
|
||||
} else {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
|
||||
}
|
||||
}
|
||||
|
||||
public static void restoreAniso(float aniso) {
|
||||
final int id = savedFilterTextureId;
|
||||
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
|
||||
RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
|
||||
} else {
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -4,5 +4,6 @@ import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class TextureUnitStateEvent extends MutableEvent {
|
||||
public int unit;
|
||||
public int cap;
|
||||
public boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.asm.ASMUtil;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
|
||||
public final class ClassDump {
|
||||
|
||||
public static final ClassDump DISABLED = new ClassDump(false);
|
||||
|
||||
private final boolean enabled;
|
||||
|
||||
public ClassDump(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void dumpClass(String className, byte[] originalBytes, byte[] transformedBytes, Object transformer) {
|
||||
if (!enabled) return;
|
||||
ASMUtil.saveAsRawClassFile(originalBytes, className + "_PRE", transformer);
|
||||
ASMUtil.saveAsRawClassFile(transformedBytes, className + "_POST", transformer);
|
||||
}
|
||||
|
||||
public void dumpRFBClass(String className, ClassNodeHandle classNode, Object transformer) {
|
||||
if (!enabled) return;
|
||||
dumpClass(className, classNode.getOriginalBytes(), classNode.computeBytes(), transformer);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -15,6 +20,10 @@ public final class DependencyVerifier {
|
||||
*/
|
||||
public record Check(String resourcePath, String errorMessage) {}
|
||||
|
||||
public interface FatalDialog {
|
||||
void show(String title, String message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify all checks pass. Throws RuntimeException on first failure.
|
||||
* @param anchor Class whose classloader to use for resource lookups
|
||||
@@ -28,5 +37,39 @@ public final class DependencyVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Check> gtnhLibChecks(String productName) {
|
||||
return List.of(
|
||||
new Check("/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class", "Missing dependency: " + productName + " requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new Check("/com/gtnewhorizon/gtnhlib/client/renderer/VertexCallbackManager.class", "GTNHLib is outdated: " + productName + " requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new Check("/it/unimi/dsi/fastutil/objects/ObjectBooleanBiConsumer.class", "GTNHLib is outdated: " + productName + " requires GTNHLib 0.11.19 or newer (fastutil 8.5.18+)! Download: https://modrinth.com/mod/gtnhlib"));
|
||||
}
|
||||
|
||||
public static void verifyOrHalt(Class<?> anchor, String productName, List<Check> checks, Logger logger, FatalDialog sdlDialog) {
|
||||
final String message;
|
||||
try {
|
||||
verify(anchor, checks);
|
||||
return;
|
||||
} catch (RuntimeException ex) {
|
||||
message = ex.getMessage();
|
||||
}
|
||||
|
||||
final String title = productName + " - Missing Dependency";
|
||||
System.err.println("FATAL: " + message);
|
||||
try {
|
||||
logger.fatal(message);
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
final int lwjgl3ifyMajor = ((Integer) Launch.blackboard.getOrDefault("lwjgl3ify:major-version", Integer.MIN_VALUE));
|
||||
try {
|
||||
if (lwjgl3ifyMajor >= 3) {
|
||||
sdlDialog.show(title, message);
|
||||
} else if (!GraphicsEnvironment.isHeadless()) {
|
||||
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
Runtime.getRuntime().halt(1);
|
||||
}
|
||||
|
||||
private DependencyVerifier() {}
|
||||
}
|
||||
+2
-48
@@ -1,54 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class EarlyRedirectorCore {
|
||||
|
||||
private final GLSMRedirector core;
|
||||
private final String[] targets;
|
||||
private final String[] exclusions;
|
||||
public final class EarlyRedirectorCore extends ModRedirector {
|
||||
|
||||
public EarlyRedirectorCore(String... extraExclusions) {
|
||||
this.core = new GLSMRedirector();
|
||||
this.targets = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone();
|
||||
|
||||
final List<String> excl = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
|
||||
if (extraExclusions != null && extraExclusions.length > 0) {
|
||||
Collections.addAll(excl, extraExclusions);
|
||||
}
|
||||
this.exclusions = excl.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public byte[] transform(String transformedName, byte[] basicClass) {
|
||||
if (basicClass == null) return null;
|
||||
|
||||
if (!startsWithAny(transformedName, targets)) return basicClass;
|
||||
if (startsWithAny(transformedName, exclusions)) return basicClass;
|
||||
if (!core.shouldTransform(basicClass)) return basicClass;
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
if (core.transformClassNode(transformedName, cn)) {
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
cn.accept(cw);
|
||||
return cw.toByteArray();
|
||||
}
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
private static boolean startsWithAny(String name, String[] prefixes) {
|
||||
for (String p : prefixes) {
|
||||
if (name.startsWith(p)) return true;
|
||||
}
|
||||
return false;
|
||||
super(ClassDump.DISABLED, EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone(), extraExclusions);
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -91,9 +91,8 @@ public final class EcosystemNarrowRules {
|
||||
public static final String[] LWJGL3IFY_EXCLUSIONS_SHARED = {
|
||||
"org.embeddedt.embeddium",
|
||||
"org.taumc.celeritas",
|
||||
"com.mitchej123.lwjgl.lwjgl3",
|
||||
"com.mitchej123.glsm",
|
||||
"com.gtnewhorizons.angelica.lwjgl3",
|
||||
"com.gtnewhorizons.angelica.sdlgpu",
|
||||
};
|
||||
|
||||
public static final String[] EARLY_REDIRECTOR_TARGETS = {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
|
||||
|
||||
public final class Lwjgl3ifyExclusions {
|
||||
|
||||
public static void apply() {
|
||||
final var handle = SharedConfig.getRfbTransformers().stream()
|
||||
.filter(transformer -> transformer.id().equals("lwjgl3ify:redirect"))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (handle == null) return;
|
||||
for (String exclusion : EcosystemNarrowRules.LWJGL3IFY_EXCLUSIONS_SHARED) {
|
||||
handle.exclusions().add(exclusion);
|
||||
}
|
||||
}
|
||||
|
||||
private Lwjgl3ifyExclusions() {}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ModRedirector {
|
||||
|
||||
private final GLSMRedirector core = new GLSMRedirector();
|
||||
private final ClassDump dump;
|
||||
private final String[] targets;
|
||||
private final String[] allExclusions;
|
||||
|
||||
public ModRedirector(ClassDump dump, String... extraExclusions) {
|
||||
this(dump, (String[]) null, extraExclusions);
|
||||
}
|
||||
|
||||
public ModRedirector(ClassDump dump, String[] targets, String[] extraExclusions) {
|
||||
this.dump = dump;
|
||||
this.targets = targets;
|
||||
final List<String> exclusions = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
|
||||
exclusions.add("com.gtnewhorizons.angelica.lwjgl3.");
|
||||
Collections.addAll(exclusions, extraExclusions);
|
||||
this.allExclusions = exclusions.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public ClassDump getDump() {
|
||||
return dump;
|
||||
}
|
||||
|
||||
public String[] getTransformerExclusions() {
|
||||
return allExclusions;
|
||||
}
|
||||
|
||||
public boolean shouldTransform(byte[] basicClass) {
|
||||
return core.shouldTransform(basicClass);
|
||||
}
|
||||
|
||||
public boolean transformClassNode(String transformedName, ClassNode cn, boolean lwjgl3Aware) {
|
||||
return core.transformClassNode(transformedName, cn, lwjgl3Aware);
|
||||
}
|
||||
|
||||
public byte[] transform(String transformedName, byte[] basicClass, Object transformer) {
|
||||
if (basicClass == null) return null;
|
||||
|
||||
if (targets != null && !startsWithAny(transformedName, targets)) return basicClass;
|
||||
if (startsWithAny(transformedName, allExclusions)) return basicClass;
|
||||
|
||||
if (!core.shouldTransform(basicClass)) {
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
if (!core.transformClassNode(transformedName, cn)) {
|
||||
return basicClass;
|
||||
}
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
cn.accept(cw);
|
||||
final byte[] bytes = cw.toByteArray();
|
||||
dump.dumpClass(transformedName, basicClass, bytes, transformer);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static boolean startsWithAny(String name, String[] prefixes) {
|
||||
for (String prefix : prefixes) {
|
||||
if (name.startsWith(prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public abstract class RfbModRedirector implements RfbClassTransformer {
|
||||
|
||||
private static final Attributes.Name LWJGL3_AWARE = new Attributes.Name("Lwjgl3ify-Aware");
|
||||
|
||||
private final String id;
|
||||
private final ModRedirector inner;
|
||||
|
||||
protected RfbModRedirector(@Pattern("[a-z0-9-]+") String id, ModRedirector inner) {
|
||||
this.id = id;
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private static boolean isLwjgl3Aware(@Nullable Manifest manifest) {
|
||||
return manifest != null && "true".equals(manifest.getMainAttributes().getValue(LWJGL3_AWARE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] sortAfter() {
|
||||
return new String[] {"*", "mixin:mixin"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] sortBefore() {
|
||||
return new String[] {"lwjgl3ify:redirect"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] additionalExclusions() {
|
||||
return inner.getTransformerExclusions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldTransformClass(@NotNull ExtensibleClassLoader classLoader,
|
||||
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
|
||||
@NotNull ClassNodeHandle classNode) {
|
||||
if (!classNode.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
return inner.shouldTransform(classNode.getOriginalBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader,
|
||||
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
|
||||
@NotNull ClassNodeHandle classNode) {
|
||||
final boolean changed = inner.transformClassNode(className, classNode.getNode(), isLwjgl3Aware(manifest));
|
||||
if (changed) {
|
||||
classNode.computeMaxs();
|
||||
inner.getDump().dumpRFBClass(className, classNode, this);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
|
||||
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
|
||||
transformClassIfNeeded(classLoader, context, manifest, className, classNode);
|
||||
}
|
||||
}
|
||||
+1
@@ -44,6 +44,7 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
|
||||
if (GLSMHooks.TEXTURE_UNIT_STATE.hasListeners()) {
|
||||
GLSMHooks.textureUnitStateEvent.unit = unitIndex;
|
||||
GLSMHooks.textureUnitStateEvent.cap = glCap;
|
||||
GLSMHooks.textureUnitStateEvent.enabled = enabled;
|
||||
GLSMHooks.TEXTURE_UNIT_STATE.post(GLSMHooks.textureUnitStateEvent);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -101,6 +104,22 @@ public class TextureInfoCache {
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean isProxyTarget(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_PROXY_TEXTURE_1D,
|
||||
GL11.GL_PROXY_TEXTURE_2D,
|
||||
GL12.GL_PROXY_TEXTURE_3D,
|
||||
GL13.GL_PROXY_TEXTURE_CUBE_MAP,
|
||||
GL30.GL_PROXY_TEXTURE_1D_ARRAY,
|
||||
GL30.GL_PROXY_TEXTURE_2D_ARRAY,
|
||||
GL31.GL_PROXY_TEXTURE_RECTANGLE,
|
||||
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE,
|
||||
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY,
|
||||
GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isTrackedTarget(int target) {
|
||||
return target == GL11.GL_TEXTURE_2D
|
||||
|| target == GL11.GL_TEXTURE_1D
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.gtnewhorizons.angelica.glsm.texture;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
public final class TextureTargets {
|
||||
|
||||
private TextureTargets() {}
|
||||
|
||||
public static boolean isProxyTarget(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_PROXY_TEXTURE_1D,
|
||||
GL11.GL_PROXY_TEXTURE_2D,
|
||||
GL12.GL_PROXY_TEXTURE_3D,
|
||||
GL13.GL_PROXY_TEXTURE_CUBE_MAP,
|
||||
GL30.GL_PROXY_TEXTURE_1D_ARRAY,
|
||||
GL30.GL_PROXY_TEXTURE_2D_ARRAY,
|
||||
GL31.GL_PROXY_TEXTURE_RECTANGLE,
|
||||
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE,
|
||||
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY,
|
||||
GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
+22
@@ -42,6 +42,28 @@ public class GLSM_TextureBindingTargetCache_UnitTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTargetBindIsNeitherForwardedNorCached() {
|
||||
drainErrors();
|
||||
final int t2d = GL11.glGenTextures();
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE5);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
|
||||
|
||||
GLStateManager.glBindTexture(GL11.GL_PROXY_TEXTURE_2D, 0);
|
||||
GLStateManager.glBindTexture(GL12.GL_PROXY_TEXTURE_3D, t2d);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "a proxy target must never reach the driver");
|
||||
assertEquals(t2d, GLStateManager.getBoundTextureForServerState(5), "a proxy bind must not disturb the cache");
|
||||
assertEquals(t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D));
|
||||
} finally {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glDeleteTextures(t2d);
|
||||
drainErrors();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void popAttribRestoresRecordedTarget() {
|
||||
drainErrors();
|
||||
|
||||
+15
@@ -22,6 +22,21 @@ public class GLSM_TextureUploadGeneration_GLTest {
|
||||
return texId;
|
||||
}
|
||||
|
||||
@Test
|
||||
void aLevelBeyondTheShiftWidthClampsToOne() {
|
||||
final int texId = allocate(64);
|
||||
try {
|
||||
assertEquals(64, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 6, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 31, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 32, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 64, GL11.GL_TEXTURE_HEIGHT));
|
||||
} finally {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDeleteTextures(texId);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUploadLeavesANonZeroGeneration() {
|
||||
final int texId = allocate(4);
|
||||
|
||||
@@ -44,22 +44,48 @@ class BackendPolicyTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshPrefersTheExactRational() {
|
||||
assertEquals(144, RenderBackend.refreshHzFrom(144, 1, 0.0f));
|
||||
assertEquals(60, RenderBackend.refreshHzFrom(60000, 1001, 0.0f));
|
||||
void periodPrefersTheExactRational() {
|
||||
assertEquals(16_683_333L, RenderBackend.periodFromRational(60000, 1001, 0.0f));
|
||||
assertEquals(6_944_444L, RenderBackend.periodFromRational(144, 1, 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));
|
||||
void periodFallsBackToTheFloatRate() {
|
||||
assertEquals(16_683_350L, RenderBackend.periodFromRational(0, 0, 59.94f));
|
||||
assertEquals(16_666_667L, RenderBackend.periodFromRational(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));
|
||||
void periodIsZeroWhenUnknown() {
|
||||
assertEquals(0L, RenderBackend.periodFromRational(0, 0, 0.0f));
|
||||
assertEquals(0L, RenderBackend.periodFromRational(-1, -1, -1.0f));
|
||||
assertEquals(0L, RenderBackend.periodFromRational(144, 0, 0.0f));
|
||||
}
|
||||
|
||||
@Test
|
||||
void integerNtscRatesAreAdjusted() {
|
||||
assertEquals(16_683_333L, RenderBackend.periodFromIntegerHz(59));
|
||||
assertEquals(6_951_389L, RenderBackend.periodFromIntegerHz(143));
|
||||
assertEquals(16_666_667L, RenderBackend.periodFromIntegerHz(60));
|
||||
assertEquals(6_944_444L, RenderBackend.periodFromIntegerHz(144));
|
||||
assertEquals(0L, RenderBackend.periodFromIntegerHz(0));
|
||||
assertEquals(0L, RenderBackend.periodFromIntegerHz(-60));
|
||||
assertEquals(60, RenderBackend.hzFromPeriod(RenderBackend.periodFromIntegerHz(59)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hzFromPeriodRoundsToNearest() {
|
||||
assertEquals(60, RenderBackend.hzFromPeriod(16_666_667L));
|
||||
assertEquals(144, RenderBackend.hzFromPeriod(6_944_444L));
|
||||
assertEquals(0, RenderBackend.hzFromPeriod(0L));
|
||||
assertEquals(0, RenderBackend.hzFromPeriod(-1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void plausiblePeriodFilterKeeps20To1000Hz() {
|
||||
assertEquals(0L, RenderBackend.plausiblePeriodNanos(999_000L));
|
||||
assertEquals(1_000_000L, RenderBackend.plausiblePeriodNanos(1_000_000L));
|
||||
assertEquals(50_000_000L, RenderBackend.plausiblePeriodNanos(50_000_000L));
|
||||
assertEquals(0L, RenderBackend.plausiblePeriodNanos(51_000_000L));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class GateRecorderTest {
|
||||
|
||||
@Test
|
||||
void theLongestBlockWithinAFrameWinsRegardlessOfOrder() {
|
||||
final RenderBackend.GateRecorder longestFirst = new RenderBackend.GateRecorder();
|
||||
longestFirst.recordGate(5_000_000L, 100L);
|
||||
longestFirst.recordGate(1_000_000L, 200L);
|
||||
|
||||
final RenderBackend.GateSample out = new RenderBackend.GateSample();
|
||||
longestFirst.readGateSample(out);
|
||||
assertEquals(5_000_000L, out.durationNanos);
|
||||
assertEquals(100L, out.endNanos);
|
||||
|
||||
final RenderBackend.GateRecorder shortestFirst = new RenderBackend.GateRecorder();
|
||||
shortestFirst.recordGate(1_000_000L, 10L);
|
||||
shortestFirst.recordGate(4_000_000L, 20L);
|
||||
|
||||
shortestFirst.readGateSample(out);
|
||||
assertEquals(4_000_000L, out.durationNanos);
|
||||
assertEquals(20L, out.endNanos);
|
||||
}
|
||||
|
||||
@Test
|
||||
void aReadConsumesTheSample() {
|
||||
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
|
||||
recorder.recordGate(2_000_000L, 50L);
|
||||
|
||||
final RenderBackend.GateSample out = new RenderBackend.GateSample();
|
||||
recorder.readGateSample(out);
|
||||
|
||||
recorder.readGateSample(out);
|
||||
assertEquals(0L, out.endNanos);
|
||||
assertEquals(0L, out.durationNanos);
|
||||
}
|
||||
|
||||
@Test
|
||||
void gpuWaitAccumulatesAndResetsOnRead() {
|
||||
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
|
||||
recorder.recordGate(1_000_000L, 10L);
|
||||
recorder.recordGpuWait(300L);
|
||||
recorder.recordGpuWait(700L);
|
||||
|
||||
final RenderBackend.GateSample out = new RenderBackend.GateSample();
|
||||
recorder.readGateSample(out);
|
||||
assertEquals(1_000L, out.gpuWaitNanos);
|
||||
|
||||
recorder.recordGate(1_000_000L, 11L);
|
||||
recorder.readGateSample(out);
|
||||
assertEquals(0L, out.gpuWaitNanos);
|
||||
}
|
||||
|
||||
@Test
|
||||
void gpuWaitIsDrainedOnASampleFreeRead() {
|
||||
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
|
||||
recorder.recordGpuWait(450L);
|
||||
|
||||
final RenderBackend.GateSample out = new RenderBackend.GateSample();
|
||||
recorder.readGateSample(out);
|
||||
|
||||
assertEquals(0L, out.endNanos);
|
||||
assertEquals(450L, out.gpuWaitNanos);
|
||||
|
||||
recorder.readGateSample(out);
|
||||
assertEquals(0L, out.gpuWaitNanos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Timeout(10)
|
||||
class MainThreadPumpTest {
|
||||
|
||||
private static final class DeferredExecutor implements Executor {
|
||||
final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
tasks.add(command);
|
||||
}
|
||||
|
||||
void runNext() {
|
||||
tasks.removeFirst().run();
|
||||
}
|
||||
|
||||
void awaitTask() {
|
||||
while (tasks.isEmpty()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class InlineExecutor implements Executor {
|
||||
final List<Runnable> submitted = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
submitted.add(command);
|
||||
command.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitWaiting(Thread t) {
|
||||
while (t.getState() != Thread.State.WAITING && t.getState() != Thread.State.TIMED_WAITING) {
|
||||
assertTrue(t.isAlive(), "thread died instead of blocking");
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
|
||||
private static MainThreadPump pump(Executor executor, Runnable pumpBody, Runnable pollBody) {
|
||||
return new MainThreadPump(executor, () -> false, pumpBody, pollBody);
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedPumpsSubmitTheSameRunnable() {
|
||||
final InlineExecutor executor = new InlineExecutor();
|
||||
final AtomicInteger pumps = new AtomicInteger();
|
||||
final AtomicInteger polls = new AtomicInteger();
|
||||
final MainThreadPump p = pump(executor, pumps::incrementAndGet, polls::incrementAndGet);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
p.pumpMessages();
|
||||
}
|
||||
|
||||
assertEquals(16, executor.submitted.size());
|
||||
assertEquals(16, pumps.get());
|
||||
assertEquals(16, polls.get());
|
||||
final Runnable first = executor.submitted.get(0);
|
||||
for (Runnable r : executor.submitted) {
|
||||
assertSame(first, r, "pump must not allocate a new task per call");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void pumpBlocksUntilTheMainThreadRunsTheTask() throws Exception {
|
||||
final DeferredExecutor executor = new DeferredExecutor();
|
||||
final AtomicInteger polls = new AtomicInteger();
|
||||
final MainThreadPump p = pump(executor, () -> {}, polls::incrementAndGet);
|
||||
|
||||
final Thread caller = new Thread(p::pumpMessages, "pump-caller");
|
||||
caller.start();
|
||||
awaitWaiting(caller);
|
||||
executor.awaitTask();
|
||||
assertEquals(1, executor.tasks.size());
|
||||
assertEquals(0, polls.get());
|
||||
|
||||
executor.runNext();
|
||||
caller.join();
|
||||
assertEquals(1, polls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void aThrowingBodyPropagatesToTheCallerAndDoesNotLeaveItParked() {
|
||||
final InlineExecutor executor = new InlineExecutor();
|
||||
final AtomicInteger polls = new AtomicInteger();
|
||||
final AtomicInteger pumps = new AtomicInteger();
|
||||
final MainThreadPump p = pump(executor, () -> {
|
||||
if (pumps.incrementAndGet() == 1) throw new IllegalStateException("boom");
|
||||
}, polls::incrementAndGet);
|
||||
|
||||
final IllegalStateException thrown = assertThrows(IllegalStateException.class, p::pumpMessages);
|
||||
assertEquals("boom", thrown.getMessage());
|
||||
assertEquals(0, polls.get(), "poll must not run when the pump body failed");
|
||||
|
||||
p.pumpMessages();
|
||||
assertEquals(1, polls.get(), "a failed pump must not poison the next one");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mainThreadCallersRunInlineWithoutTheExecutor() {
|
||||
final InlineExecutor executor = new InlineExecutor();
|
||||
final AtomicInteger pumps = new AtomicInteger();
|
||||
final AtomicInteger polls = new AtomicInteger();
|
||||
final MainThreadPump p = new MainThreadPump(executor, () -> true, pumps::incrementAndGet,
|
||||
polls::incrementAndGet);
|
||||
|
||||
p.pumpMessages();
|
||||
|
||||
assertTrue(executor.submitted.isEmpty(), "the main thread must not hop through the executor");
|
||||
assertEquals(1, pumps.get());
|
||||
assertEquals(1, polls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void anInterruptedCallerCompletesAndKeepsItsInterruptFlag() throws Exception {
|
||||
final DeferredExecutor executor = new DeferredExecutor();
|
||||
final MainThreadPump p = pump(executor, () -> {}, () -> {});
|
||||
final AtomicInteger stillInterrupted = new AtomicInteger(-1);
|
||||
|
||||
final Thread caller = new Thread(() -> {
|
||||
p.pumpMessages();
|
||||
stillInterrupted.set(Thread.currentThread().isInterrupted() ? 1 : 0);
|
||||
}, "pump-interrupted-caller");
|
||||
caller.start();
|
||||
awaitWaiting(caller);
|
||||
executor.awaitTask();
|
||||
caller.interrupt();
|
||||
assertFalse(executor.tasks.isEmpty());
|
||||
|
||||
executor.runNext();
|
||||
caller.join();
|
||||
assertEquals(1, stillInterrupted.get());
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class GLSMInitConfigDefaultsTest {
|
||||
|
||||
@Test
|
||||
void emptyBuilderLeavesDsaToTheProbe() {
|
||||
assertTrue(GLSMInitConfig.builder().build().isDSAEnabled());
|
||||
assertFalse(GLSMInitConfig.builder().enableDSA(false).build().isDSAEnabled());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.InsnNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ModRedirectorTest {
|
||||
|
||||
private static final String EXTRA_EXCLUSION = "com.example.excluded";
|
||||
|
||||
private static ClassNode emptyClass() {
|
||||
final ClassNode cn = new ClassNode();
|
||||
cn.version = Opcodes.V1_8;
|
||||
cn.access = Opcodes.ACC_PUBLIC;
|
||||
cn.name = "com/example/TestClass";
|
||||
cn.superName = "java/lang/Object";
|
||||
cn.methods = new ArrayList<>();
|
||||
|
||||
final MethodNode mn = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "test", "()V", null, null);
|
||||
mn.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||
cn.methods.add(mn);
|
||||
return cn;
|
||||
}
|
||||
|
||||
private static byte[] bytesWithoutGlCall() {
|
||||
return toBytes(emptyClass());
|
||||
}
|
||||
|
||||
private static byte[] bytesWithGlCall() {
|
||||
final ClassNode cn = emptyClass();
|
||||
final MethodNode mn = cn.methods.get(0);
|
||||
mn.instructions.clear();
|
||||
mn.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/lwjgl/opengl/GL11", "glPopMatrix", "()V", false));
|
||||
mn.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||
return toBytes(cn);
|
||||
}
|
||||
|
||||
private static byte[] toBytes(ClassNode cn) {
|
||||
final ClassWriter cw = new ClassWriter(0);
|
||||
cn.accept(cw);
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private static ModRedirector redirector() {
|
||||
return new ModRedirector(ClassDump.DISABLED, EXTRA_EXCLUSION);
|
||||
}
|
||||
|
||||
private static void assertStandardExclusions(String[] actual) {
|
||||
final List<String> exclusions = Arrays.asList(actual);
|
||||
final String[] coreExclusions = new GLSMRedirector().getCoreExclusions();
|
||||
for (String coreExclusion : coreExclusions) {
|
||||
assertTrue(exclusions.contains(coreExclusion), coreExclusion);
|
||||
}
|
||||
assertTrue(exclusions.contains("com.gtnewhorizons.angelica.lwjgl3."));
|
||||
assertTrue(exclusions.contains(EXTRA_EXCLUSION));
|
||||
assertEquals(coreExclusions.length + 2, exclusions.size(), exclusions.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void exclusionsCombineCoreLwjgl3AndExtra() {
|
||||
assertStandardExclusions(redirector().getTransformerExclusions());
|
||||
}
|
||||
|
||||
@Test
|
||||
void earlyRedirectorExclusionsCombineCoreLwjgl3AndExtra() {
|
||||
assertStandardExclusions(new EarlyRedirectorCore(EXTRA_EXCLUSION).getTransformerExclusions());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullInputReturnsNull() {
|
||||
assertNull(redirector().transform("com.example.TestClass", null, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void excludedPrefixReturnsOriginalArray() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
assertSame(original, redirector().transform(EXTRA_EXCLUSION + ".TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void coreExcludedPrefixReturnsOriginalArray() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
assertSame(original, redirector().transform("org.lwjgl.opengl.TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unchangedClassReturnsOriginalArray() {
|
||||
final byte[] original = bytesWithoutGlCall();
|
||||
assertSame(original, redirector().transform("com.example.TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void redirectedClassReturnsNewArray() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
assertNotSame(original, redirector().transform("com.example.TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void earlyRedirectorSkipsClassesOutsideItsTargets() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
final EarlyRedirectorCore early = new EarlyRedirectorCore(EXTRA_EXCLUSION);
|
||||
assertSame(original, early.transform("com.example.TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void earlyRedirectorTransformsItsTargets() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
final EarlyRedirectorCore early = new EarlyRedirectorCore(EXTRA_EXCLUSION);
|
||||
final String target = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS[0] + "TestClass";
|
||||
assertNotSame(original, early.transform(target, original, this));
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,12 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
|
||||
new ContextAttribs(3, 3).withProfileCore(true).withForwardCompatible(true));
|
||||
|
||||
GLSMExtension.setMainThread(Thread.currentThread());
|
||||
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(800, 600).build());
|
||||
GLStateManager.initialize(GLSMInitConfig.builder()
|
||||
.displaySize(800, 600)
|
||||
.directDrawer(t -> {})
|
||||
.streamingDrawerDestroy(() -> {})
|
||||
.enableDSA(false)
|
||||
.build());
|
||||
GLStateManager.setRunningSplash(false);
|
||||
GLStateManager.markSplashComplete("test");
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
|
||||
displayMode = new DisplayMode(800, 600);
|
||||
final GLSMInitConfig config = GLSMInitConfig.builder()
|
||||
.displaySize(displayMode.getWidth(), displayMode.getHeight())
|
||||
.directDrawer(t -> {})
|
||||
.streamingDrawerDestroy(() -> {})
|
||||
.enableDSA(false)
|
||||
.build();
|
||||
setMainThread(Thread.currentThread());
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import com.gtnewhorizons.retrofuturagradle.MinecraftExtension
|
||||
import org.gradle.accessors.dm.LibrariesForLibs
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
val libs = the<LibrariesForLibs>()
|
||||
|
||||
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
|
||||
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(module("com.github.GTNewHorizons:lwjgl3ify"))
|
||||
.using(module("com.github.GTNewHorizons:lwjgl3ify:${libs.versions.lwjgl3ify.get()}"))
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Maven Central Snapshots"
|
||||
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
|
||||
content { includeGroup("org.lwjgl") }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
for ((key, value) in System.getProperties()) {
|
||||
val name = key.toString()
|
||||
if (name.startsWith("angelica.") || name.startsWith("quickPlay")) {
|
||||
jvmArgs("-D$name=$value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
|
||||
val lwjglDebug = project.extra.has("lwjglDebug") && project.extra["lwjglDebug"] as Boolean
|
||||
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient") && name != "runClient" && isMacOs) {
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
|
||||
if (lwjglDebug) {
|
||||
environment("METAL_DEVICE_WRAPPER_TYPE", "1")
|
||||
environment("METAL_DEBUG_ERROR_MODE", "0")
|
||||
environment("MTL_SHADER_VALIDATION", "1")
|
||||
environment("MTL_SHADER_VALIDATION_REPORT_TO_STDERR", "1")
|
||||
environment("MTL_DEBUG_LAYER", "1")
|
||||
environment("MallocScribble", "1")
|
||||
environment("MallocPreScribble", "1")
|
||||
environment("MallocGuardEdges", "1")
|
||||
environment("MallocStackLogging", "1")
|
||||
environment("MallocStackLoggingNoCompact", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure<MinecraftExtension> {
|
||||
lwjgl3Bindings.addAll("shaderc", "spvc")
|
||||
lwjgl3Version = libs.versions.lwjgl3.get()
|
||||
}
|
||||
|
||||
tasks.named<Copy>("processResources") {
|
||||
val projectVersion = project.version.toString()
|
||||
inputs.property("version", projectVersion)
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to projectVersion)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
dependsOn(embedOnly)
|
||||
from(embedOnly.map(::zipTree))
|
||||
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
mergeServiceFiles()
|
||||
|
||||
minimize {
|
||||
exclude(project(rootProject.path))
|
||||
exclude(project(":glsm"))
|
||||
exclude(project(":lwjgl3-backend"))
|
||||
exclude(project(":sdl-gpu"))
|
||||
exclude(dependency("org.taumc:.*:.*"))
|
||||
exclude(dependency("org.embeddedt.celeritas:.*:.*"))
|
||||
exclude(dependency("org.antlr:.*:.*"))
|
||||
}
|
||||
|
||||
relocate("org.taumc.celeritas", "org.taumc.celeritas")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
relocate("com.gtnewhorizons.angelica.config", "com.gtnewhorizons.angelica.config")
|
||||
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
|
||||
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
|
||||
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
|
||||
}
|
||||
@@ -6,10 +6,10 @@ log4j = "2.0-beta9"
|
||||
asmCompile = "5.0.3"
|
||||
# End Frozen
|
||||
|
||||
lwjgl3 = "3.4.2-20260602.093430-9"
|
||||
lwjgl3 = "3.4.2-SNAPSHOT"
|
||||
lwjgl3ify = "3.0.31"
|
||||
|
||||
celeritas = "2.5.10-GTNH"
|
||||
celeritas = "2.5.11-GTNH"
|
||||
gtnhlib = "0.11.36"
|
||||
eventbus = "7.0-beta.15"
|
||||
glslTransformLib = "0.2.0-33.g3ec381e"
|
||||
@@ -30,10 +30,10 @@ asmBuild = "9.7"
|
||||
mockito = "3.12.4"
|
||||
archunit = "1.4.1"
|
||||
ant = "1.8.2"
|
||||
distantHorizonsApi = "5.1.0"
|
||||
|
||||
[libraries]
|
||||
celeritas-common = { module = "org.embeddedt.celeritas:celeritas-common", version.ref = "celeritas" }
|
||||
celeritas-lwjgl2-service = { module = "org.embeddedt.celeritas:celeritas-lwjgl2-service", version.ref = "celeritas" }
|
||||
eventbus = { module = "net.minecraftforge:eventbus", version.ref = "eventbus" }
|
||||
glsl-transformation-lib = { module = "org.taumc:glsl-transformation-lib", version.ref = "glslTransformLib" }
|
||||
antlr4-runtime = { module = "org.antlr:antlr4-runtime", version.ref = "antlr4Runtime" }
|
||||
@@ -58,6 +58,7 @@ junit-platform-reporting = { module = "org.junit.platform:junit-platform-reporti
|
||||
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
|
||||
archunit-junit5 = { module = "com.tngtech.archunit:archunit-junit5", version.ref = "archunit" }
|
||||
ant = { module = "org.apache.ant:ant", version.ref = "ant" }
|
||||
distant-horizons-api = { module = "maven.modrinth:DistantHorizonsApi", version.ref = "distantHorizonsApi" }
|
||||
asm-tree-compile = { module = "org.ow2.asm:asm-tree", version.ref = "asmCompile" }
|
||||
asm-tree-test = { module = "org.ow2.asm:asm-tree", version.ref = "asmTest" }
|
||||
asm-build = { module = "org.ow2.asm:asm", version.ref = "asmBuild" }
|
||||
|
||||
@@ -61,6 +61,7 @@ dependencies {
|
||||
api(project(":glsm"))
|
||||
|
||||
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
|
||||
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
compileOnly(libs.celeritas.common) { isTransitive = false }
|
||||
|
||||
// LWJGL3
|
||||
|
||||
+45
-7
@@ -4,9 +4,13 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
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.MainThreadPump;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
|
||||
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
import org.lwjglx.Lwjgl3ifyEventLoop;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.ARBClearTexture;
|
||||
@@ -36,7 +40,9 @@ 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.SDLError;
|
||||
import org.lwjgl.sdl.SDLEvents;
|
||||
import org.lwjgl.sdl.SDLTimer;
|
||||
import org.lwjgl.sdl.SDLVideo;
|
||||
import org.lwjgl.system.JNI;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
@@ -71,6 +77,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
private SDL_EventFilter dropEventFilter;
|
||||
private final ConcurrentLinkedQueue<String> droppedFiles = new ConcurrentLinkedQueue<>();
|
||||
private volatile boolean watchingDrops;
|
||||
private boolean swapIntervalHonored = true;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
@@ -173,19 +180,50 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
protected VSyncMode applyVSyncMode(VSyncMode preferred) {
|
||||
final boolean enabled = preferred.tearFree();
|
||||
SDLVideo.SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
||||
return enabled ? VSyncMode.ON : VSyncMode.OFF;
|
||||
final int want = preferred.tearFree() ? 1 : 0;
|
||||
final boolean setOk = SDLVideo.SDL_GL_SetSwapInterval(want);
|
||||
final String setError = setOk ? null : SDLError.SDL_GetError();
|
||||
int got = want;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
final IntBuffer readback = stack.callocInt(1);
|
||||
if (SDLVideo.SDL_GL_GetSwapInterval(readback)) got = readback.get(0);
|
||||
}
|
||||
if (!setOk) {
|
||||
LOGGER.warn("SDL_GL_SetSwapInterval({}) failed: {}", want, setError);
|
||||
} else if (got != want && !(want == 1 && got == -1)) {
|
||||
LOGGER.warn("Requested swap interval {} but the driver reports {}", want, got);
|
||||
}
|
||||
swapIntervalHonored = want == 0 || got != 0;
|
||||
return want != 0 && swapIntervalHonored ? VSyncMode.ON : VSyncMode.OFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int queryDisplayRefreshRateHz() {
|
||||
protected boolean applyHonored() {
|
||||
return swapIntervalHonored;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long queryRefreshPeriodNanos() {
|
||||
final long window = Display.getWindow();
|
||||
final int displayId = window == 0 ? SDLVideo.SDL_GetPrimaryDisplay() : SDLVideo.SDL_GetDisplayForWindow(window);
|
||||
if (displayId == 0) return 0;
|
||||
if (displayId == 0) return 0L;
|
||||
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());
|
||||
if (mode == null) return 0L;
|
||||
return periodFromRational(mode.refresh_rate_numerator(), mode.refresh_rate_denominator(), mode.refresh_rate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parkNanos(long nanos) {
|
||||
SDLTimer.SDL_DelayNS(nanos);
|
||||
}
|
||||
|
||||
private static final class Pump {
|
||||
static final MainThreadPump INSTANCE = new MainThreadPump(MainStartOnFirstThread.instance(), MainThreadExec::isMainThread, Lwjgl3ifyEventLoop::pumpEvents, MainThreadPump::pollInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pumpDisplayMessages() {
|
||||
Pump.INSTANCE.pumpMessages();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,25 +5,38 @@
|
||||
# * Game waits for the tracy connection, runs the route, returns to the original coordinates and exits.
|
||||
# * tracy-capture finalizes
|
||||
#
|
||||
# Usage: scripts/flyby-capture.sh <output.tracy> [route] [length]
|
||||
# Usage: scripts/flyby-capture.sh <output.tracy> [route] [length] [extra gradle args...]
|
||||
# route - straight | pan | circuit | static
|
||||
# length - Units: blocks (straight), blocks per leg (circuit), degrees (pan), ticks (static).
|
||||
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
|
||||
#
|
||||
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
|
||||
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
|
||||
# QUICKPLAY_WORLD - save to auto-load (default: latest).
|
||||
# SDLGPU=1 - launch on the SDL-GPU backend.
|
||||
#
|
||||
# Requires tracy-capture on PATH (TRACY_CAPTURE=/path/to/tracy-capture).
|
||||
# Launch the game to the main menu and load your benchmark save; the run starts once the world is up.
|
||||
# QuickPlay loads QUICKPLAY_WORLD on launch; the run starts once the world is up.
|
||||
#
|
||||
# Ensure the chunks in the route have been generated before capturing
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OUT="${1:?usage: flyby-capture.sh <output.tracy> [route] [length]}"
|
||||
OUT="${1:?usage: flyby-capture.sh <output.tracy> [route] [length] [extra gradle args...]}"
|
||||
ROUTE="${2:-straight}"
|
||||
LENGTH="${3:-0}"
|
||||
if [ $# -ge 3 ]; then
|
||||
shift 3
|
||||
else
|
||||
shift $#
|
||||
fi
|
||||
WARMUP="${FLYBY_WARMUP:-400}"
|
||||
TRACY_CAPTURE="${TRACY_CAPTURE:-tracy-capture}"
|
||||
|
||||
BACKEND_ARGS=()
|
||||
if [ "${SDLGPU:-0}" = "1" ]; then
|
||||
BACKEND_ARGS+=(-Dangelica.sdlgpu.enable=true)
|
||||
fi
|
||||
|
||||
if ! command -v "$TRACY_CAPTURE" >/dev/null 2>&1 && [ ! -x "$TRACY_CAPTURE" ]; then
|
||||
cat >&2 <<'EOF'
|
||||
error: tracy-capture not found.
|
||||
@@ -56,7 +69,6 @@ cleanup() {
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default})"
|
||||
echo " load your benchmark save once the main menu appears"
|
||||
|
||||
./gradlew runClient25 --console=plain \
|
||||
-Dangelica.flyby.route="$ROUTE" \
|
||||
@@ -64,7 +76,10 @@ echo " load your benchmark save once the main menu appears"
|
||||
-Dangelica.flyby.speed="${FLYBY_SPEED:-0}" \
|
||||
-Dangelica.flyby.warmupTicks="$WARMUP" \
|
||||
-Dangelica.flyby.waitForTracy=true \
|
||||
-Dangelica.flyby.exitWhenDone=true
|
||||
-Dangelica.flyby.exitWhenDone=true \
|
||||
-DquickPlaySingleplayer="${QUICKPLAY_WORLD:-latest}" \
|
||||
${BACKEND_ARGS[@]+"${BACKEND_ARGS[@]}"} \
|
||||
"$@"
|
||||
|
||||
echo "==> game exited, finalising capture"
|
||||
cleanup
|
||||
|
||||
@@ -77,6 +77,9 @@ dependencies {
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(testFixtures(project(":glsm")))
|
||||
testImplementation(project(":glsm")) {
|
||||
capabilities { requireCapability("${project.group}:glsm-stubs") }
|
||||
}
|
||||
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.
|
||||
@@ -84,15 +87,77 @@ dependencies {
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.log4j.core)
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
testRuntimeOnly(libs.commons.lang3)
|
||||
testRuntimeOnly(libs.guava)
|
||||
testImplementation(libs.asm.tree.test)
|
||||
}
|
||||
|
||||
val glsmSdlTag = "glsm-sdl"
|
||||
|
||||
val glsmSdlStubs = sourceSets.create("glsmSdlStubs") {
|
||||
java.srcDir("src/glsmSdlStubs/java")
|
||||
}
|
||||
|
||||
val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
|
||||
java.srcDir("src/glsmSdlAgent/java")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"glsmSdlAgentCompileOnly"(project(":glsm"))
|
||||
"glsmSdlAgentCompileOnly"(libs.asm.tree.test)
|
||||
testImplementation(glsmSdlAgent.output)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
useJUnitPlatform {
|
||||
excludeTags(glsmSdlTag)
|
||||
}
|
||||
if (System.getProperty("os.name").startsWith("Mac")) {
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
}
|
||||
}
|
||||
|
||||
val glsmSdlRedirectAgentJar by tasks.registering(Jar::class) {
|
||||
archiveClassifier = "glsm-sdl-redirect-agent"
|
||||
from(glsmSdlAgent.output)
|
||||
manifest {
|
||||
attributes("Premain-Class" to "com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlRedirectAgent")
|
||||
}
|
||||
}
|
||||
|
||||
val glsmSdlTest by tasks.registering(Test::class) {
|
||||
description = "Runs the headless GLSM-on-SDL FFP rig in its own JVM, with the SDL GPU gate engaged."
|
||||
group = "verification"
|
||||
testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
classpath = glsmSdlStubs.output.classesDirs.plus(
|
||||
sourceSets["test"].runtimeClasspath.filter { file ->
|
||||
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
)
|
||||
useJUnitPlatform {
|
||||
includeTags(glsmSdlTag)
|
||||
}
|
||||
dependsOn(tasks.named("glsmSdlStubsClasses"), glsmSdlRedirectAgentJar)
|
||||
val agentJarPath = glsmSdlRedirectAgentJar.flatMap { it.archiveFile }.map { it.asFile.path }
|
||||
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-javaagent:${agentJarPath.get()}") })
|
||||
jvmArgs(
|
||||
"-Dangelica.sdlgpu.enable=true",
|
||||
"-Dangelica.sdlgpu.disablePresenterThread=true",
|
||||
"-Dceleritas.lwjglService=com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService",
|
||||
)
|
||||
if (System.getProperty("os.name").startsWith("Mac")) {
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
}
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
}
|
||||
mustRunAfter(tasks.test)
|
||||
}
|
||||
|
||||
tasks.check { dependsOn(glsmSdlTest) }
|
||||
|
||||
tasks.named<JavaCompile>("compileJava") {
|
||||
val classesDir = destinationDirectory
|
||||
doLast { injectLwjgl3Aware(classesDir.get().asFile) }
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class GlsmSdlRedirectAgent {
|
||||
|
||||
private static final String GTNHLIB = "com/gtnewhorizon/gtnhlib/";
|
||||
private static final List<String> FAILURES = Collections.synchronizedList(new ArrayList<>());
|
||||
private static final AtomicInteger TRANSFORMED_COUNT = new AtomicInteger();
|
||||
|
||||
private GlsmSdlRedirectAgent() {}
|
||||
|
||||
public static void premain(String args, Instrumentation instrumentation) {
|
||||
instrumentation.addTransformer(new Redirector());
|
||||
}
|
||||
|
||||
public static List<String> failures() {
|
||||
return List.copyOf(FAILURES);
|
||||
}
|
||||
|
||||
public static int transformedCount() {
|
||||
return TRANSFORMED_COUNT.get();
|
||||
}
|
||||
|
||||
private static final class Redirector implements ClassFileTransformer {
|
||||
|
||||
private final GLSMRedirector redirector = new GLSMRedirector();
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> redefined, ProtectionDomain domain, byte[] classfile) {
|
||||
if (className == null || !className.startsWith(GTNHLIB)) return null;
|
||||
try {
|
||||
if (!redirector.shouldTransform(classfile)) return null;
|
||||
final ClassNode node = new ClassNode();
|
||||
new ClassReader(classfile).accept(node, 0);
|
||||
if (!redirector.transformClassNode(className.replace('/', '.'), node)) return null;
|
||||
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
node.accept(writer);
|
||||
TRANSFORMED_COUNT.incrementAndGet();
|
||||
return writer.toByteArray();
|
||||
} catch (Throwable t) {
|
||||
FAILURES.add(className + ": " + t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.lwjgl;
|
||||
|
||||
public final class Sys {
|
||||
|
||||
private Sys() {}
|
||||
|
||||
public static boolean is64Bit() {
|
||||
return !"32".equals(System.getProperty("sun.arch.data.model"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
public class ContextCapabilities {
|
||||
|
||||
public boolean OpenGL30 = true;
|
||||
public boolean OpenGL31 = true;
|
||||
public boolean OpenGL32 = true;
|
||||
public boolean OpenGL33 = true;
|
||||
public boolean OpenGL40 = true;
|
||||
public boolean OpenGL42 = true;
|
||||
public boolean OpenGL43 = true;
|
||||
public boolean OpenGL44 = true;
|
||||
public boolean OpenGL45 = true;
|
||||
|
||||
public boolean GL_ARB_buffer_storage = true;
|
||||
public boolean GL_ARB_clear_texture = true;
|
||||
public boolean GL_ARB_compute_shader = true;
|
||||
public boolean GL_ARB_direct_state_access = false;
|
||||
public boolean GL_ARB_draw_buffers_blend = false;
|
||||
public boolean GL_ARB_multi_draw_indirect = false;
|
||||
public boolean GL_ARB_sampler_objects = true;
|
||||
public boolean GL_ARB_shader_image_load_store = true;
|
||||
public boolean GL_ARB_shader_storage_buffer_object = true;
|
||||
public boolean GL_ARB_tessellation_shader = false;
|
||||
|
||||
public boolean GL_EXT_direct_state_access = false;
|
||||
public boolean GL_EXT_gpu_shader4 = true;
|
||||
public boolean GL_EXT_shader_image_load_store = true;
|
||||
|
||||
public boolean GL_NVX_gpu_memory_info = false;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
public interface Drawable {
|
||||
|
||||
void makeCurrent();
|
||||
|
||||
void releaseContext();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
public final class GLContext {
|
||||
|
||||
private static final ContextCapabilities CAPABILITIES = new ContextCapabilities();
|
||||
|
||||
private GLContext() {}
|
||||
|
||||
public static ContextCapabilities getCapabilities() {
|
||||
return CAPABILITIES;
|
||||
}
|
||||
}
|
||||
@@ -150,6 +150,7 @@ public final class SDLGPUGate {
|
||||
try {
|
||||
device().claimWindow(ctx.window());
|
||||
engaged = true;
|
||||
SDLGPULWJGLService.bind();
|
||||
BackendManager.RENDER_BACKEND.onPostWindowCreate(ctx.window());
|
||||
} catch (Throwable t) {
|
||||
initFailure = t;
|
||||
@@ -158,9 +159,10 @@ public final class SDLGPUGate {
|
||||
}
|
||||
|
||||
public static void fallBackToGL() {
|
||||
if (SDLGPULWJGLService.isOfferedAsAvailable()) {
|
||||
if (SDLGPULWJGLService.isConstructed()) {
|
||||
throw new IllegalStateException("Celeritas already selected the SDL GPU LWJGL service; falling back to OpenGL would leave it bound to a dead backend");
|
||||
}
|
||||
SDLGPULWJGLService.unbind();
|
||||
disarmed = true;
|
||||
engaged = false;
|
||||
deviceReady = false;
|
||||
|
||||
@@ -5,10 +5,10 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.MemoryStackWrapper;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import com.mitchej123.lwjgl.DebugMessageHandler;
|
||||
import com.mitchej123.lwjgl.GLExtension;
|
||||
import com.mitchej123.lwjgl.LWJGLService;
|
||||
import com.mitchej123.lwjgl.MemoryStack;
|
||||
import org.taumc.celeritas.lwjgl.DebugMessageHandler;
|
||||
import org.taumc.celeritas.lwjgl.GLExtension;
|
||||
import org.taumc.celeritas.lwjgl.LWJGLService;
|
||||
import org.taumc.celeritas.lwjgl.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.Buffer;
|
||||
@@ -17,19 +17,32 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** SDL GPU implementation of {@link LWJGLService} for Celeritas */
|
||||
public final class SDLGPULWJGLService extends LWJGLService {
|
||||
public final class SDLGPULWJGLService implements LWJGLService {
|
||||
|
||||
private static volatile boolean offeredAsAvailable;
|
||||
private static final ThreadLocal<MemoryStackWrapper> STACK =
|
||||
ThreadLocal.withInitial(() -> new MemoryStackWrapper(org.lwjgl.system.MemoryStack.stackGet()));
|
||||
|
||||
public static boolean isOfferedAsAvailable() { return offeredAsAvailable; }
|
||||
private static volatile boolean constructed;
|
||||
|
||||
@Override public int getPriority() {
|
||||
if (!SystemProperties.USE_SDL_GPU || !SDLGPUGate.isSDLGPUAvailable() || !SDLGPUGate.isEngaged()) {
|
||||
return PRIORITY_UNAVAILABLE;
|
||||
public static SDLGPULWJGLService create() {
|
||||
if (!SDLGPUGate.isEngaged()) {
|
||||
throw new IllegalStateException("SDL GPU is not engaged; " + SystemProperties.KEY_CELERITAS_LWJGL_SERVICE + " must not name " + SDLGPULWJGLService.class.getName());
|
||||
}
|
||||
constructed = true;
|
||||
return new SDLGPULWJGLService();
|
||||
}
|
||||
|
||||
public static boolean isConstructed() { return constructed; }
|
||||
|
||||
static void bind() {
|
||||
System.setProperty(SystemProperties.KEY_CELERITAS_LWJGL_SERVICE, SDLGPULWJGLService.class.getName());
|
||||
}
|
||||
|
||||
static void unbind() {
|
||||
final String key = SystemProperties.KEY_CELERITAS_LWJGL_SERVICE;
|
||||
if (SDLGPULWJGLService.class.getName().equals(System.getProperty(key))) {
|
||||
System.clearProperty(key);
|
||||
}
|
||||
offeredAsAvailable = true;
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override public boolean isOpenGLVersionSupported(int major, int minor) {
|
||||
@@ -47,7 +60,6 @@ public final class SDLGPULWJGLService extends LWJGLService {
|
||||
ARB_uniform_buffer_object,
|
||||
ARB_vertex_array_object,
|
||||
ARB_shader_storage_buffer_object,
|
||||
ARB_instanced_arrays,
|
||||
KHR_debug
|
||||
-> true;
|
||||
case ARB_timer_query,
|
||||
@@ -75,11 +87,20 @@ public final class SDLGPULWJGLService extends LWJGLService {
|
||||
BackendManager.RENDER_BACKEND.bufferData(target, size, usage);
|
||||
}
|
||||
}
|
||||
@Override public void glBufferSubData(int target, long offset, ByteBuffer data) { BackendManager.RENDER_BACKEND.bufferSubData(target, offset, data); }
|
||||
@Override public void glBufferSubData(int target, long offset, long size, long data) {
|
||||
BackendManager.RENDER_BACKEND.bufferSubData(target, offset, MemoryUtil.memByteBuffer(data, (int) size));
|
||||
}
|
||||
@Override public void glBufferStorage(int target, long size, int flags) { BackendManager.RENDER_BACKEND.bufferStorage(target, size, flags); }
|
||||
@Override public void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
|
||||
BackendManager.RENDER_BACKEND.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||
}
|
||||
@Override public void glDrawArrays(int mode, int first, int count) { BackendManager.RENDER_BACKEND.drawArrays(mode, first, count); }
|
||||
@Override public void glDrawBuffers(int[] buffers) {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
BackendManager.RENDER_BACKEND.drawBuffers(stack.ints(buffers));
|
||||
}
|
||||
}
|
||||
@Override public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
|
||||
}
|
||||
@Override public void glTexParameteri(int target, int pname, int param) { GLStateManager.glTexParameteri(target, pname, param); }
|
||||
@Override public ByteBuffer glMapBufferRange(int target, long offset, long length, int flags) { return BackendManager.RENDER_BACKEND.mapBufferRange(target, offset, length, flags); }
|
||||
@Override public long nglMapBuffer(int target, int access) { return 0; }
|
||||
@Override public ByteBuffer glMapBuffer(int target, int access) { return BackendManager.RENDER_BACKEND.mapBuffer(target, access); }
|
||||
@@ -231,7 +252,8 @@ public final class SDLGPULWJGLService extends LWJGLService {
|
||||
@Override public int glGetAttribLocation(int program, CharSequence name) { return BackendManager.RENDER_BACKEND.getAttribLocation(program, name); }
|
||||
|
||||
@Override public MemoryStack stackPush() {
|
||||
return new MemoryStackWrapper(org.lwjgl.system.MemoryStack.stackPush());
|
||||
org.lwjgl.system.MemoryStack.stackPush();
|
||||
return STACK.get();
|
||||
}
|
||||
|
||||
@Override public long nmemAlloc(long size) { return MemoryUtil.nmemAlloc(size); }
|
||||
|
||||
+337
-128
@@ -7,6 +7,9 @@ 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.MainThreadPump;
|
||||
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
import org.lwjglx.Lwjgl3ifyEventLoop;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
@@ -21,11 +24,13 @@ 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.FFPTrace;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineStore;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.VertexAttribs;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.BufferParams;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.CopyRectClip;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FormatMap;
|
||||
@@ -89,6 +94,7 @@ import java.util.function.BiConsumer;
|
||||
|
||||
import org.lwjgl.sdl.SDLGPU;
|
||||
import org.lwjgl.sdl.SDL_GPUBlitInfo;
|
||||
import org.lwjgl.sdl.SDLTimer;
|
||||
import org.lwjgl.sdl.SDL_GPUBufferLocation;
|
||||
import org.lwjgl.sdl.SDL_GPUTextureLocation;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
@@ -101,6 +107,8 @@ import com.gtnewhorizons.angelica.sdlgpu.compat.Lwjgl3GLCapabilitiesShim;
|
||||
import me.eigenraven.lwjgl3ify.api.GLCapabilitiesOverride;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
|
||||
|
||||
/** SDL GPU implementation of {@link RenderBackend}. */
|
||||
public class SDLGPURenderBackend extends RenderBackend {
|
||||
@@ -143,6 +151,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
});
|
||||
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
|
||||
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
|
||||
private final FFPTrace ffpTrace = SystemProperties.FFP_TRACE ? new FFPTrace(resourceManager) : null;
|
||||
|
||||
private final IntOpenHashSet missingProgramWarned = new IntOpenHashSet();
|
||||
|
||||
@@ -216,7 +225,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void onPostWindowCreate(long window) {
|
||||
buildCachedStrings();
|
||||
populateGLCapabilities();
|
||||
if (window != 0L) populateGLCapabilities();
|
||||
}
|
||||
|
||||
private boolean hasWindow() {
|
||||
return device.getClaimedWindow() != 0L;
|
||||
}
|
||||
|
||||
@Override public void init() {
|
||||
@@ -276,12 +289,16 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
st.bumpAttribStateGen();
|
||||
}
|
||||
});
|
||||
DisplayEvents.addPreSwapchainInvalidatingChangeListener(SDLGPUGate.sdlGpuPreSwapchainInvalidatingCallback());
|
||||
if (hasWindow()) DisplayEvents.addPreSwapchainInvalidatingChangeListener(SDLGPUGate.sdlGpuPreSwapchainInvalidatingCallback());
|
||||
GLSMConfig.expandVertexFormats = true;
|
||||
resourceManager.cachePreferredDepthFormats();
|
||||
LOG.warn("Swapchain texture format: 0x{} (12=B8G8R8A8_UNORM, 53=B8G8R8A8_SRGB, 4=R8G8B8A8_UNORM)", Integer.toHexString(device.getSwapchainTextureFormat()));
|
||||
cachedSwapchainFormatArray = new int[]{device.getSwapchainTextureFormat()};
|
||||
cachedSwapchainFormatArray = new int[]{
|
||||
hasWindow() ? device.getSwapchainTextureFormat() : resourceManager.mapTextureFormat(GL11.GL_RGBA8)
|
||||
};
|
||||
PipelineCache.setSwapchainFormats(cachedSwapchainFormatArray);
|
||||
if (hasWindow()) {
|
||||
LOG.warn("Swapchain texture format: 0x{} (12=B8G8R8A8_UNORM, 53=B8G8R8A8_SRGB, 4=R8G8B8A8_UNORM)", Integer.toHexString(cachedSwapchainFormatArray[0]));
|
||||
}
|
||||
pipelineStore.setBufferHandleResolver(resourceManager::getBufferHandle);
|
||||
pipelineStore.setVertexVariantResolver(shaderManager::getOrBuildVertexVariant);
|
||||
resourceManager.setBufferLivenessListener(this::markAllPipelineInputDirty);
|
||||
@@ -294,15 +311,17 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
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 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]);
|
||||
if (hasWindow()) {
|
||||
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 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]);
|
||||
}
|
||||
|
||||
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
|
||||
|
||||
@@ -398,26 +417,28 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected int queryDisplayRefreshRateHz() {
|
||||
return device.getDisplayRefreshRateHz();
|
||||
@Override protected long queryRefreshPeriodNanos() {
|
||||
return device.getDisplayRefreshPeriodNanos();
|
||||
}
|
||||
|
||||
@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 awaitPresent() {
|
||||
if (presenter != null) presenter.drain();
|
||||
}
|
||||
|
||||
@Override public void parkNanos(long nanos) {
|
||||
SDLTimer.SDL_DelayNS(nanos);
|
||||
}
|
||||
|
||||
private static final class Pump {
|
||||
static final MainThreadPump INSTANCE = new MainThreadPump(MainStartOnFirstThread.instance(), MainThreadExec::isMainThread, Lwjgl3ifyEventLoop::pumpEvents, MainThreadPump::pollInput);
|
||||
}
|
||||
|
||||
@Override public void pumpDisplayMessages() { Pump.INSTANCE.pumpMessages(); }
|
||||
|
||||
@Override public void shutdown() {
|
||||
if (shutdown) return;
|
||||
shutdown = true;
|
||||
@@ -667,6 +688,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
awaitUploadFlush();
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.frameEnd(f);
|
||||
frameManager.endFrame();
|
||||
frameManager.presentFinalTarget();
|
||||
fenceTracker.resolvePendingFences();
|
||||
@@ -675,6 +697,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public void onPreSwapchainInvalidatingChange(Object change) {
|
||||
super.onPreSwapchainInvalidatingChange(change);
|
||||
if (presenter != null) presenter.drain();
|
||||
endFrameUploadFlush();
|
||||
if (frameManager.isFrameActive()) {
|
||||
@@ -997,13 +1020,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
|
||||
final ContextState st = s();
|
||||
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
|
||||
drawDispatch.drawTriangleFanAsTriangleList(st, first, count);
|
||||
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
|
||||
return;
|
||||
}
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
pipelineApplier.ensureRenderPass(st, f);
|
||||
if (f.renderPass == 0) { f.droppedDrawsThisFrame++; drawDispatch.warnDrawArraysNoRenderPass(mode, st.boundFboId); return; }
|
||||
if (!pipelineApplier.applyPipelineAndState(st, f)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arrays", mode, count, 0, first, 0);
|
||||
SDL_DrawGPUPrimitives(f.renderPass, count, 1, first, 0);
|
||||
}
|
||||
|
||||
@@ -1012,6 +1037,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElements");
|
||||
if (rp == 0) return;
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elements", mode, count, type, firstIndex, 0);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, 0);
|
||||
}
|
||||
|
||||
@@ -1020,6 +1046,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElementsInstanced");
|
||||
if (rp == 0) return;
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsInstanced", mode, count, type, firstIndex, 0);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, primcount, firstIndex, 0);
|
||||
}
|
||||
|
||||
@@ -1029,6 +1056,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElementsBaseVertex");
|
||||
if (rp == 0) return;
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsBaseVertex", mode, count, type, firstIndex, baseVertex);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, baseVertex);
|
||||
}
|
||||
|
||||
@@ -1057,7 +1085,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int count = MemoryUtil.memGetInt(pCount + offset);
|
||||
if (count <= 0) continue;
|
||||
final long indices = MemoryUtil.memGetAddress(pIndices + (long) i * Pointer.POINTER_SIZE);
|
||||
drawDispatch.issueIndexedDraw(st, rp, ebo, type, count, 1, (int) (indices / elementSize), MemoryUtil.memGetInt(pBaseVertex + offset));
|
||||
final int firstIndex = (int) (indices / elementSize);
|
||||
final int baseVertex = MemoryUtil.memGetInt(pBaseVertex + offset);
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "multiElementsBaseVertex", mode, count, type, firstIndex, baseVertex);
|
||||
drawDispatch.issueIndexedDraw(st, rp, ebo, type, count, 1, firstIndex, baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1082,6 +1113,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
+ "sentinel indices in the EBO will be drawn as regular indices.");
|
||||
drawDispatch.restartMultiDrawWarned = true;
|
||||
}
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsIndirect", mode, drawcount, type, (int) indirect, 0);
|
||||
SDL_DrawGPUIndexedPrimitivesIndirect(rp, indirectHandle, (int) indirect, drawcount);
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
@@ -1095,7 +1127,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public void dispatchComputeIndirect(long offset) {
|
||||
final int indirectGlId = getBoundBuffer(GL40.GL_DRAW_INDIRECT_BUFFER);
|
||||
final int indirectGlId = getBoundBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER);
|
||||
if (indirectGlId == 0) return;
|
||||
final long indirectHandle = resourceManager.getBufferHandle(indirectGlId);
|
||||
if (indirectHandle == 0) return;
|
||||
@@ -1193,6 +1225,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
|
||||
@Override public void bindTexture(int target, int texture) {
|
||||
if (isProxyTarget(target)) return;
|
||||
final ContextState st = s();
|
||||
if (st.activeTextureUnit >= 0 && st.activeTextureUnit < st.boundTextures.length) {
|
||||
if (st.boundTextures[st.activeTextureUnit] != texture) {
|
||||
@@ -1206,6 +1239,87 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
st.activeTextureUnit = texture - GL13.GL_TEXTURE0;
|
||||
}
|
||||
|
||||
static boolean isProxyTarget(int target) {
|
||||
return TextureInfoCache.isProxyTarget(target);
|
||||
}
|
||||
|
||||
private static boolean proxyLayersInHeight(int target) {
|
||||
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
|
||||
}
|
||||
|
||||
private static boolean proxyLayersInDepth(int target) {
|
||||
return isProxyArrayTarget(target) && !proxyLayersInHeight(target);
|
||||
}
|
||||
|
||||
private static boolean isProxyArrayTarget(int target) {
|
||||
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY || target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
|
||||
}
|
||||
|
||||
public static int proxyLevelSize(int base, int level) {
|
||||
return PixelOps.mipLevelSize(base, level);
|
||||
}
|
||||
|
||||
static int proxyDimensionLimit(int target) {
|
||||
return switch (target) {
|
||||
case GL12.GL_PROXY_TEXTURE_3D -> MAX_3D_TEXTURE_SIZE;
|
||||
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> MAX_CUBE_MAP_TEXTURE_SIZE;
|
||||
default -> MAX_TEXTURE_SIZE;
|
||||
};
|
||||
}
|
||||
|
||||
static int levelZeroEquivalentSize(int size, int level) {
|
||||
if (size <= 0 || level < 0 || level >= Integer.SIZE - 1) return 0;
|
||||
final long scaled = (long) size << level;
|
||||
return scaled > Integer.MAX_VALUE ? 0 : (int) scaled;
|
||||
}
|
||||
|
||||
private void recordProxyTexImage(ContextState st, int target, int level, int internalFormat, int width, int height, int depth) {
|
||||
final boolean layersInHeight = proxyLayersInHeight(target);
|
||||
final boolean layersInDepth = proxyLayersInDepth(target);
|
||||
final int dimLimit = proxyDimensionLimit(target);
|
||||
final int w = levelZeroEquivalentSize(width, level);
|
||||
final int h = layersInHeight ? height : levelZeroEquivalentSize(height, level);
|
||||
final int d = layersInDepth ? depth : levelZeroEquivalentSize(depth, level);
|
||||
final int hLimit = layersInHeight ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
|
||||
final int dLimit = layersInDepth ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
|
||||
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit
|
||||
&& resourceManager.isMappableTextureFormat(internalFormat);
|
||||
if (fits) {
|
||||
st.proxyTexture.accept(target, w, h, d, internalFormat);
|
||||
} else {
|
||||
st.proxyTexture.reject(target);
|
||||
}
|
||||
}
|
||||
|
||||
private static int boundTextureFor(ContextState st, int target) {
|
||||
return isProxyTarget(target) ? 0 : st.boundTextures[st.activeTextureUnit];
|
||||
}
|
||||
|
||||
private void ensureLevel0Storage(ContextState st, int glId, int target, int internalFormat, int width, int height, int depth, int format, boolean mipmapped) {
|
||||
final int numLevels;
|
||||
if (mipmapped) {
|
||||
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(glId);
|
||||
numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
|
||||
} else {
|
||||
numLevels = 1;
|
||||
}
|
||||
final boolean bgraSwizzle = format == GL12.GL_BGRA && (internalFormat == GL11.GL_RGBA || internalFormat == GL11.GL_RGBA8);
|
||||
final int targetSdlFormat = bgraSwizzle
|
||||
? SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM
|
||||
: resourceManager.mapTextureFormat(internalFormat);
|
||||
if (canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
target, internalFormat, targetSdlFormat, width, height, depth, numLevels)) {
|
||||
return;
|
||||
}
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
if (bgraSwizzle) {
|
||||
resourceManager.createTextureWithSdlFormat(glId, target, SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, internalFormat, width, height, depth, numLevels);
|
||||
} else {
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, height, depth, numLevels);
|
||||
}
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
|
||||
private static boolean canReuseTexture(ResourceManager.TextureMeta m, long handle, int target, int glFormat, int sdlFormat, int w, int h, int d, int levels) {
|
||||
return m != null
|
||||
&& handle != 0
|
||||
@@ -1220,26 +1334,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, level, internalFormat, width, height, 1);
|
||||
return;
|
||||
}
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
if (glId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(glId);
|
||||
final int numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
|
||||
final boolean bgraSwizzle = format == GL12.GL_BGRA && (internalFormat == GL11.GL_RGBA || internalFormat == GL11.GL_RGBA8);
|
||||
final int targetSdlFormat = bgraSwizzle
|
||||
? SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM
|
||||
: resourceManager.mapTextureFormat(internalFormat);
|
||||
if (!canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
target, internalFormat, targetSdlFormat, width, height, 1, numLevels)) {
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
if (bgraSwizzle) {
|
||||
resourceManager.createTextureWithSdlFormat(glId, target, SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, internalFormat, width, height, 1, numLevels);
|
||||
} else {
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, height, 1, numLevels);
|
||||
}
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
|
||||
}
|
||||
|
||||
textureOps.uploadTextureRegion(st, glId, resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId), pixels, 0, 0, width, height, level, format, type);
|
||||
@@ -1255,18 +1358,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, level, internalFormat, width, height, depth);
|
||||
return;
|
||||
}
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
if (glId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
final int numLevels = 1;
|
||||
final int targetSdlFormat = resourceManager.mapTextureFormat(internalFormat);
|
||||
if (!canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
target, internalFormat, targetSdlFormat, width, height, depth, numLevels)) {
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, height, depth, numLevels);
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
ensureLevel0Storage(st, glId, target, internalFormat, width, height, depth, GL11.GL_NONE, false);
|
||||
}
|
||||
if (pixels == null) return;
|
||||
final long texHandle = resourceManager.getTextureHandle(glId);
|
||||
@@ -1288,18 +1388,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, level, internalFormat, width, 1, 1);
|
||||
return;
|
||||
}
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
if (glId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
final int numLevels = 1;
|
||||
final int targetSdlFormat = resourceManager.mapTextureFormat(internalFormat);
|
||||
if (!canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
target, internalFormat, targetSdlFormat, width, 1, 1, numLevels)) {
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, 1, 1, numLevels);
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
ensureLevel0Storage(st, glId, target, internalFormat, width, 1, 1, GL11.GL_NONE, false);
|
||||
}
|
||||
if (pixels == null) return;
|
||||
textureOps.uploadTextureRegion(st, glId, resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
@@ -1307,18 +1404,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, level, internalFormat, width, height, 1);
|
||||
return;
|
||||
}
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
if (glId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(glId);
|
||||
final int numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
|
||||
final int targetSdlFormat = resourceManager.mapTextureFormat(internalFormat);
|
||||
if (!canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId), target, internalFormat, targetSdlFormat, width, height, 1, numLevels)) {
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, height, 1, numLevels);
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
|
||||
}
|
||||
|
||||
if (st.boundPixelUnpackBuffer == 0) return;
|
||||
@@ -1335,7 +1429,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState st = s();
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
final int glId = boundTextureFor(st, target);
|
||||
if (glId == 0 || pixels == null) return;
|
||||
textureOps.uploadTextureRegion(st, glId, resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId), pixels, xoffset, yoffset, width, height, level, format, type);
|
||||
}
|
||||
@@ -1345,7 +1439,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {
|
||||
final ContextState st = s();
|
||||
if (st.boundPixelUnpackBuffer == 0) return;
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
final int glId = boundTextureFor(st, target);
|
||||
if (glId == 0) return;
|
||||
final ByteBuffer staging = resourceManager.getPboStaging(st.boundPixelUnpackBuffer);
|
||||
if (staging == null) return;
|
||||
@@ -1359,24 +1453,29 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void copyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
final ContextState cs = s();
|
||||
final int destGlId = cs.boundTextures[cs.activeTextureUnit];
|
||||
textureOps.copyTexSubImageImpl(cs,destGlId, level, xoffset, yoffset, x, y, width, height);
|
||||
textureOps.copyTexSubImageImpl(cs, boundTextureFor(cs, target), level, xoffset, yoffset, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
|
||||
final ContextState cs = s();
|
||||
final int destGlId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final int destGlId = boundTextureFor(cs, target);
|
||||
if (destGlId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
ensureLevel0Storage(cs, destGlId, target, internalFormat, width, height, 1, GL11.GL_NONE, true);
|
||||
}
|
||||
|
||||
textureOps.copyTexSubImageImpl(cs, destGlId, level, 0, 0, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override public void texParameteri(int target, int pname, int param) {
|
||||
final ContextState cs = s();
|
||||
textureOps.texParameteri(cs, pname, param, cs.boundTextures[cs.activeTextureUnit]);
|
||||
textureOps.texParameteri(cs, pname, param, boundTextureFor(cs, target));
|
||||
}
|
||||
|
||||
@Override public void texParameterf(int target, int pname, float param) {
|
||||
final ContextState cs = s();
|
||||
textureOps.texParameterf(cs, pname, param, cs.boundTextures[cs.activeTextureUnit]);
|
||||
textureOps.texParameterf(cs, pname, param, boundTextureFor(cs, target));
|
||||
}
|
||||
|
||||
@Override public void texParameteriv(int target, int pname, IntBuffer params) {
|
||||
@@ -1387,22 +1486,31 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public int getTexParameteri(int target, int pname) {
|
||||
final ContextState cs = s();
|
||||
return getTextureParameteri(cs.boundTextures[cs.activeTextureUnit], target, pname);
|
||||
return getTextureParameteri(boundTextureFor(cs, target), target, pname);
|
||||
}
|
||||
@Override public float getTexParameterf(int target, int pname) {
|
||||
final ContextState cs = s();
|
||||
return getTextureParameterf(cs.boundTextures[cs.activeTextureUnit], target, pname);
|
||||
return getTextureParameterf(boundTextureFor(cs, target), target, pname);
|
||||
}
|
||||
@Override public int getTexLevelParameteri(int target, int level, int pname) {
|
||||
final ContextState cs = s();
|
||||
if (isProxyTarget(target)) {
|
||||
final ContextState.ProxyTextureState proxy = cs.proxyTexture;
|
||||
if (proxy.target != target) return 0;
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> proxyLevelSize(proxy.width, level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> proxyLayersInHeight(target) ? proxy.height : proxyLevelSize(proxy.height, level);
|
||||
case GL12.GL_TEXTURE_DEPTH -> proxyLayersInDepth(target) ? proxy.depth : proxyLevelSize(proxy.depth, level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> proxy.internalFormat;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
final int glId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null) return 0;
|
||||
final int mipW = Math.max(1, meta.width() >> level);
|
||||
final int mipH = Math.max(1, meta.height() >> level);
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> mipW;
|
||||
case GL11.GL_TEXTURE_HEIGHT -> mipH;
|
||||
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
final int stored = meta.glFormat();
|
||||
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
|
||||
@@ -1411,8 +1519,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
};
|
||||
}
|
||||
@Override public void generateMipmap(int target) {
|
||||
final ContextState st = s();
|
||||
generateTextureMipmap(st.boundTextures[st.activeTextureUnit]);
|
||||
generateTextureMipmap(boundTextureFor(s(), target));
|
||||
}
|
||||
@Override public void pixelStorei(int pname, int param) {
|
||||
final ContextState.PixelStoreState ps = s().pixelStore;
|
||||
@@ -1682,12 +1789,20 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
|
||||
@Override public void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
if (pixels == null || width <= 0 || height <= 0) return;
|
||||
final ContextState cs = s();
|
||||
final boolean fromFbo0 = cs.boundReadFboId == 0;
|
||||
final long texHandle;
|
||||
final int srcSdlFormat;
|
||||
if (cs.boundReadFboId == 0) {
|
||||
final int srcWidth;
|
||||
final int srcHeight;
|
||||
final int srcY;
|
||||
if (fromFbo0) {
|
||||
texHandle = frameManager.getFbo0Texture();
|
||||
srcSdlFormat = frameManager.getSwapchainFormat();
|
||||
srcWidth = frameManager.getFbo0Width();
|
||||
srcHeight = frameManager.getFbo0Height();
|
||||
srcY = srcHeight - y - height;
|
||||
} else {
|
||||
final FboState fbo = resourceManager.getFbo(cs.boundReadFboId);
|
||||
if (fbo == null) return;
|
||||
@@ -1695,10 +1810,58 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
texHandle = resourceManager.getTextureHandle(colorGlId);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
|
||||
srcSdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
srcWidth = meta != null ? meta.width() : fbo.width;
|
||||
srcHeight = meta != null ? meta.height() : fbo.height;
|
||||
srcY = y;
|
||||
}
|
||||
if (texHandle == 0 || srcWidth <= 0 || srcHeight <= 0) return;
|
||||
|
||||
final long clip = CopyRectClip.clipCopyRect(x, srcY, 0, 0, width, height, srcWidth, srcHeight, width, height);
|
||||
if (clip == CopyRectClip.EMPTY) return;
|
||||
final int sx = CopyRectClip.srcX(clip);
|
||||
final int sy = CopyRectClip.srcY(clip);
|
||||
final int w = CopyRectClip.width(clip);
|
||||
final int h = CopyRectClip.height(clip);
|
||||
final int colOffset = CopyRectClip.dstX(clip, x, 0);
|
||||
final int dy = CopyRectClip.dstY(clip, srcY, 0);
|
||||
final int rowOffset = fromFbo0 ? (height - h - dy) : dy;
|
||||
|
||||
final int basePos = pixels.position();
|
||||
final int baseLimit = pixels.limit();
|
||||
final int stride = width * 4;
|
||||
if (baseLimit - basePos < height * stride) return;
|
||||
try {
|
||||
if (w == width) {
|
||||
final int start = basePos + rowOffset * stride;
|
||||
pixels.position(0);
|
||||
pixels.limit(start + h * stride);
|
||||
pixels.position(start);
|
||||
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, pixels);
|
||||
pixels.position(0);
|
||||
pixels.limit(baseLimit);
|
||||
pixels.position(start);
|
||||
PixelOps.postProcessReadback(pixels, width, h, format, srcSdlFormat, fromFbo0);
|
||||
} else {
|
||||
final ByteBuffer staging = MemoryUtil.memAlloc(w * h * 4);
|
||||
try {
|
||||
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, staging);
|
||||
staging.rewind();
|
||||
PixelOps.postProcessReadback(staging, w, h, format, srcSdlFormat, fromFbo0);
|
||||
for (int row = 0; row < h; row++) {
|
||||
staging.position(0);
|
||||
staging.limit((row + 1) * w * 4);
|
||||
staging.position(row * w * 4);
|
||||
pixels.position(basePos + (rowOffset + row) * stride + colOffset * 4);
|
||||
pixels.put(staging);
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(staging);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pixels.limit(baseLimit);
|
||||
pixels.position(basePos);
|
||||
}
|
||||
textureOps.readbackTexture(texHandle, x, y, width, height, 0, pixels);
|
||||
pixels.rewind();
|
||||
PixelOps.postProcessReadback(pixels, width, height, format, srcSdlFormat, cs.boundReadFboId == 0);
|
||||
}
|
||||
@Override public void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
|
||||
readPixels(x, y, width, height, format, type, MemoryUtil.memByteBuffer(pixels));
|
||||
@@ -1708,7 +1871,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState cs = s();
|
||||
final int glId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final int glId = boundTextureFor(cs, target);
|
||||
final long texHandle = resourceManager.getTextureHandle(glId);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null) return;
|
||||
@@ -1723,7 +1886,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final ContextState cs = s();
|
||||
final int pbo = cs.boundPixelPackBuffer;
|
||||
if (pbo == 0) return;
|
||||
final int glId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final int glId = boundTextureFor(cs, target);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null) return;
|
||||
final ByteBuffer tmp = MemoryUtil.memAlloc(meta.width() * meta.height() * 4);
|
||||
@@ -2265,6 +2428,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL30.GL_PIXEL_UNPACK_BUFFER -> st.boundPixelUnpackBuffer = buffer;
|
||||
case GL30.GL_PIXEL_PACK_BUFFER -> st.boundPixelPackBuffer = buffer;
|
||||
case GL40.GL_DRAW_INDIRECT_BUFFER -> st.boundIndirectBuffer = buffer;
|
||||
case GL43.GL_DISPATCH_INDIRECT_BUFFER -> st.boundDispatchIndirectBuffer = buffer;
|
||||
case GL31.GL_COPY_READ_BUFFER -> st.boundCopyReadBuffer = buffer;
|
||||
case GL31.GL_COPY_WRITE_BUFFER -> st.boundCopyWriteBuffer = buffer;
|
||||
case GL43.GL_SHADER_STORAGE_BUFFER -> {
|
||||
@@ -3061,14 +3225,26 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void texStorage1D(int target, int levels, int internalFormat, int width) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, 0, internalFormat, width, 1, 1);
|
||||
return;
|
||||
}
|
||||
texStorageImpl(st, st.boundTextures[st.activeTextureUnit], target, internalFormat, width, 1, 1, levels);
|
||||
}
|
||||
@Override public void texStorage2D(int target, int levels, int internalFormat, int width, int height) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, 0, internalFormat, width, height, 1);
|
||||
return;
|
||||
}
|
||||
texStorageImpl(st, st.boundTextures[st.activeTextureUnit], target, internalFormat, width, height, 1, levels);
|
||||
}
|
||||
@Override public void texStorage3D(int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, 0, internalFormat, width, height, depth);
|
||||
return;
|
||||
}
|
||||
texStorageImpl(st, st.boundTextures[st.activeTextureUnit], target, internalFormat, width, height, depth, levels);
|
||||
}
|
||||
@Override public void textureStorage1D(int texture, int levels, int internalFormat, int width) {
|
||||
@@ -3159,41 +3335,72 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
applyDrawBuffersFromIntBuffer(fbo, bufs, framebuffer == s().boundFboId);
|
||||
}
|
||||
@Override public void blitNamedFramebuffer(int readFramebuffer, int drawFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
|
||||
final FboState srcFbo = resourceManager.getFbo(readFramebuffer);
|
||||
final FboState dstFbo = resourceManager.getFbo(drawFramebuffer);
|
||||
if (srcFbo == null || dstFbo == null) return;
|
||||
final boolean srcIsFbo0 = readFramebuffer == 0;
|
||||
final boolean dstIsFbo0 = drawFramebuffer == 0;
|
||||
final FboState srcFbo = srcIsFbo0 ? null : resourceManager.getFbo(readFramebuffer);
|
||||
final FboState dstFbo = dstIsFbo0 ? null : resourceManager.getFbo(drawFramebuffer);
|
||||
if ((srcFbo == null && !srcIsFbo0) || (dstFbo == null && !dstIsFbo0)) return;
|
||||
|
||||
final boolean isDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
|
||||
long srcTex = 0, dstTex = 0;
|
||||
int dstGlId = 0;
|
||||
if (isDepth) {
|
||||
srcTex = srcFbo.depthTexture;
|
||||
dstTex = dstFbo.depthTexture;
|
||||
dstGlId = dstFbo.depthGlId;
|
||||
} else if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
srcTex = srcFbo.colorTextures[srcFbo.readBufferIndex];
|
||||
dstTex = dstFbo.colorTextures[0];
|
||||
dstGlId = dstFbo.colorGlIds[0];
|
||||
final boolean anyFbo0 = srcIsFbo0 || dstIsFbo0;
|
||||
final int fbo0Height = frameManager.getFbo0Height();
|
||||
if (anyFbo0 && fbo0Height <= 0) return;
|
||||
|
||||
final long colorSrcTex;
|
||||
final long colorDstTex;
|
||||
final int colorSrcGlId;
|
||||
final int colorDstGlId;
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
colorSrcTex = srcIsFbo0 ? frameManager.getFbo0Texture() : srcFbo.colorTextures[srcFbo.readBufferIndex];
|
||||
colorDstTex = dstIsFbo0 ? frameManager.getFbo0Texture() : dstFbo.colorTextures[0];
|
||||
colorSrcGlId = srcIsFbo0 ? 0 : srcFbo.colorGlIds[srcFbo.readBufferIndex];
|
||||
colorDstGlId = dstIsFbo0 ? 0 : dstFbo.colorGlIds[0];
|
||||
} else {
|
||||
colorSrcTex = 0L;
|
||||
colorDstTex = 0L;
|
||||
colorSrcGlId = 0;
|
||||
colorDstGlId = 0;
|
||||
}
|
||||
if (srcTex == 0 || dstTex == 0) return;
|
||||
final boolean blitColor = colorSrcTex != 0 && colorDstTex != 0 && colorSrcTex != colorDstTex;
|
||||
|
||||
final boolean depthRequested = (mask & (GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT)) != 0;
|
||||
final long depthSrcTex = (depthRequested && !anyFbo0) ? srcFbo.depthTexture : 0L;
|
||||
final long depthDstTex = (depthRequested && !anyFbo0) ? dstFbo.depthTexture : 0L;
|
||||
final boolean blitDepth = depthSrcTex != 0 && depthDstTex != 0 && depthSrcTex != depthDstTex;
|
||||
|
||||
if (!blitColor && !blitDepth) return;
|
||||
|
||||
final ContextState cs = s();
|
||||
fboClearTracker.materializePendingClearForTexture(cs, srcTex);
|
||||
if (!fboClearTracker.discardPendingClearIfFullyCovered(cs, dstTex, dstX0, dstY0, 0, dstX1 - dstX0, dstY1 - dstY0, resourceManager.getTextureMeta(dstGlId))) {
|
||||
fboClearTracker.materializePendingClearForTexture(cs, dstTex);
|
||||
if (dstIsFbo0 || (srcIsFbo0 && (cs.pendingSwapchainClear || cs.pendingSwapchainDepthClear || cs.pendingSwapchainStencilClear))) {
|
||||
frameManager.ensureFbo0RenderPass(frameManager.frame(), cs);
|
||||
}
|
||||
|
||||
if (isDepth) {
|
||||
textureOps.copyTexture(srcTex, srcX0, srcY0, dstTex, dstX0, dstY0, srcX1 - srcX0, srcY1 - srcY0);
|
||||
} else {
|
||||
final ResourceManager.TextureMeta srcMeta = resourceManager.getTextureMeta(srcFbo.colorGlIds[srcFbo.readBufferIndex]);
|
||||
final ResourceManager.TextureMeta dstMeta = resourceManager.getTextureMeta(dstFbo.colorGlIds[0]);
|
||||
if (TextureOps.canCopyInsteadOfBlit(srcMeta, dstMeta, srcX1 - srcX0, srcY1 - srcY0, dstX1 - dstX0, dstY1 - dstY0)) {
|
||||
textureOps.copyTexture(srcTex, srcX0, srcY0, dstTex, dstX0, dstY0, srcX1 - srcX0, srcY1 - srcY0);
|
||||
final int srcW = srcX1 - srcX0;
|
||||
final int srcH = srcY1 - srcY0;
|
||||
final int dstW = dstX1 - dstX0;
|
||||
final int dstH = dstY1 - dstY0;
|
||||
final int srcY = srcIsFbo0 ? (fbo0Height - srcY0 - srcH) : srcY0;
|
||||
final int dstY = dstIsFbo0 ? (fbo0Height - dstY0 - dstH) : dstY0;
|
||||
final int flipMode = (srcIsFbo0 != dstIsFbo0) ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
|
||||
|
||||
if (blitColor) {
|
||||
resolveBlitClears(cs, colorSrcTex, colorDstTex, colorDstGlId, dstX0, dstY, dstW, dstH);
|
||||
final ResourceManager.TextureMeta srcMeta = resourceManager.getTextureMeta(colorSrcGlId);
|
||||
final ResourceManager.TextureMeta dstMeta = resourceManager.getTextureMeta(colorDstGlId);
|
||||
if (flipMode == SDL_FLIP_NONE && TextureOps.canCopyInsteadOfBlit(srcMeta, dstMeta, srcW, srcH, dstW, dstH)) {
|
||||
textureOps.copyTexture(colorSrcTex, srcX0, srcY, colorDstTex, 0, dstX0, dstY, srcW, srcH);
|
||||
} else {
|
||||
textureOps.blitTexture(srcTex, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, dstTex, dstX0, dstY0, dstX1 - dstX0, dstY1 - dstY0, filter);
|
||||
textureOps.blitTexture(colorSrcTex, srcX0, srcY, srcW, srcH, colorDstTex, 0, dstX0, dstY, dstW, dstH, filter, flipMode);
|
||||
}
|
||||
}
|
||||
if (blitDepth) {
|
||||
resolveBlitClears(cs, depthSrcTex, depthDstTex, dstFbo.depthGlId, dstX0, dstY, dstW, dstH);
|
||||
textureOps.copyTexture(depthSrcTex, srcX0, srcY, depthDstTex, 0, dstX0, dstY, srcW, srcH);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveBlitClears(ContextState cs, long srcTex, long dstTex, int dstGlId, int dstX, int dstY, int dstW, int dstH) {
|
||||
fboClearTracker.materializePendingClearForTexture(cs, srcTex);
|
||||
fboClearTracker.resolveDestinationForWrite(cs, dstTex, resourceManager.getTextureMeta(dstGlId), 0, dstX, dstY, 0, dstW, dstH);
|
||||
}
|
||||
@Override public int createBuffers() { return resourceManager.genBuffer(); }
|
||||
@Override public void namedBufferData(int buffer, long size, int usage) {
|
||||
@@ -3273,11 +3480,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public int getTextureLevelParameteri(int texture, int level, int pname) {
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
if (meta == null) return 0;
|
||||
final int mipW = Math.max(1, meta.width() >> level);
|
||||
final int mipH = Math.max(1, meta.height() >> level);
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> mipW;
|
||||
case GL11.GL_TEXTURE_HEIGHT -> mipH;
|
||||
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
final int stored = meta.glFormat();
|
||||
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
|
||||
@@ -3289,7 +3494,6 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public int getInteger(int pname) {
|
||||
final ContextState cs = s();
|
||||
return switch (pname) {
|
||||
case GL11.GL_MAX_TEXTURE_SIZE -> 16384;
|
||||
case GL20.GL_MAX_TEXTURE_IMAGE_UNITS -> 17;
|
||||
case GL20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS -> 17;
|
||||
case GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS -> 32;
|
||||
@@ -3347,12 +3551,18 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return switch (pname) {
|
||||
case GL20.GL_MAX_VERTEX_UNIFORM_COMPONENTS, GL20.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS -> MAX_UNIFORM_COMPONENTS_PER_STAGE;
|
||||
case GL30.GL_MAX_ARRAY_TEXTURE_LAYERS -> MAX_ARRAY_TEXTURE_LAYERS;
|
||||
case GL11.GL_MAX_TEXTURE_SIZE -> MAX_TEXTURE_SIZE;
|
||||
case GL12.GL_MAX_3D_TEXTURE_SIZE -> MAX_3D_TEXTURE_SIZE;
|
||||
case GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE -> MAX_CUBE_MAP_TEXTURE_SIZE;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
static final int MAX_UNIFORM_COMPONENTS_PER_STAGE = 4096 / Float.BYTES;
|
||||
static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
|
||||
static final int MAX_TEXTURE_SIZE = 16384;
|
||||
static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
|
||||
static final int MAX_3D_TEXTURE_SIZE = 2048;
|
||||
|
||||
private static int unknownGetInteger(int pname) {
|
||||
synchronized (UNKNOWN_GET_INTEGER_SEEN) {
|
||||
@@ -3472,9 +3682,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return;
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
if (meta == null || level >= meta.levels()) return;
|
||||
final int mipW = Math.max(1, meta.width() >> level);
|
||||
final int mipH = Math.max(1, meta.height() >> level);
|
||||
final int mipD = Math.max(1, meta.depth() >> level);
|
||||
final int mipW = PixelOps.mipLevelSize(meta.width(), level);
|
||||
final int mipH = PixelOps.mipLevelSize(meta.height(), level);
|
||||
final int mipD = PixelOps.mipLevelSize(meta.depth(), level);
|
||||
final int bpp = PixelOps.sdlFormatTexelBytes(meta.sdlFormat());
|
||||
if (mipW <= 0 || mipH <= 0 || bpp <= 0) return;
|
||||
|
||||
@@ -3531,9 +3741,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (srcTex == 0 || dstTex == 0) return;
|
||||
final ContextState copySt = s();
|
||||
fboClearTracker.materializePendingClearForTexture(copySt, srcTex);
|
||||
if (dstZ != 0 || !fboClearTracker.discardPendingClearIfFullyCovered(copySt, dstTex, dstX, dstY, dstLevel, srcWidth, srcHeight, resourceManager.getTextureMeta(dstName))) {
|
||||
fboClearTracker.materializePendingClearForTexture(copySt, dstTex);
|
||||
}
|
||||
fboClearTracker.resolveDestinationForWrite(copySt, dstTex, resourceManager.getTextureMeta(dstName), dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight);
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
if (cp == 0) return;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
@@ -3678,6 +3886,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
pipelineApplier.ensureRenderPass(st);
|
||||
if (!frameManager.isRenderPassActive()) return;
|
||||
if (!pipelineApplier.applyPipelineAndState(st)) return;
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysInstanced", mode, count, 0, first, 0);
|
||||
SDL_DrawGPUPrimitives(frameManager.getRenderPass(), count, primcount, first, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ public final class Device {
|
||||
}
|
||||
vsyncMode = candidate;
|
||||
LOG.info("SDL present mode {} (preferred={}), refresh {}Hz, {}, video={}, framesInFlight={}, supported: vsync={} immediate={} mailbox={}",
|
||||
candidate, preferred, getDisplayRefreshRateHz(),
|
||||
candidate, preferred, RenderBackend.hzFromPeriod(getDisplayRefreshPeriodNanos()),
|
||||
(SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0 ? "fullscreen" : "windowed",
|
||||
SDL_GetCurrentVideoDriver(), FRAMES_IN_FLIGHT,
|
||||
supportsVSyncMode(VSyncMode.ON), supportsVSyncMode(VSyncMode.OFF), supportsVSyncMode(VSyncMode.MAILBOX));
|
||||
@@ -376,13 +376,13 @@ public final class Device {
|
||||
};
|
||||
}
|
||||
|
||||
public int getDisplayRefreshRateHz() {
|
||||
public long getDisplayRefreshPeriodNanos() {
|
||||
final long window = Display.getWindow();
|
||||
final int displayId = window == 0 ? SDL_GetPrimaryDisplay() : SDL_GetDisplayForWindow(window);
|
||||
if (displayId == 0) return 0;
|
||||
if (displayId == 0) return 0L;
|
||||
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());
|
||||
if (mode == null) return 0L;
|
||||
return RenderBackend.periodFromRational(mode.refresh_rate_numerator(), mode.refresh_rate_denominator(), mode.refresh_rate());
|
||||
}
|
||||
|
||||
public int getSwapchainTextureFormat() {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import com.mitchej123.lwjgl.MemoryStack;
|
||||
import org.taumc.celeritas.lwjgl.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
@@ -54,6 +54,28 @@ public final class ContextState {
|
||||
|
||||
public final PixelStoreState pixelStore = new PixelStoreState();
|
||||
|
||||
public static final class ProxyTextureState {
|
||||
public int target;
|
||||
public int width;
|
||||
public int height;
|
||||
public int depth;
|
||||
public int internalFormat;
|
||||
|
||||
public void accept(int target, int width, int height, int depth, int internalFormat) {
|
||||
this.target = target;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.depth = depth;
|
||||
this.internalFormat = internalFormat;
|
||||
}
|
||||
|
||||
public void reject(int target) {
|
||||
accept(target, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public final ProxyTextureState proxyTexture = new ProxyTextureState();
|
||||
|
||||
public static final class VAOState {
|
||||
public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS];
|
||||
public int attribEnabledMask;
|
||||
@@ -211,6 +233,7 @@ public final class ContextState {
|
||||
|
||||
public int boundArrayBuffer;
|
||||
public int boundIndirectBuffer;
|
||||
public int boundDispatchIndirectBuffer;
|
||||
public int boundUniformBuffer;
|
||||
public int boundCopyReadBuffer;
|
||||
public int boundCopyWriteBuffer;
|
||||
@@ -226,6 +249,7 @@ public final class ContextState {
|
||||
case GL30.GL_PIXEL_UNPACK_BUFFER -> boundPixelUnpackBuffer;
|
||||
case GL30.GL_PIXEL_PACK_BUFFER -> boundPixelPackBuffer;
|
||||
case GL40.GL_DRAW_INDIRECT_BUFFER -> boundIndirectBuffer;
|
||||
case GL43.GL_DISPATCH_INDIRECT_BUFFER -> boundDispatchIndirectBuffer;
|
||||
case GL31.GL_COPY_READ_BUFFER -> boundCopyReadBuffer;
|
||||
case GL31.GL_COPY_WRITE_BUFFER -> boundCopyWriteBuffer;
|
||||
case GL43.GL_SHADER_STORAGE_BUFFER -> boundSSBO;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
|
||||
@@ -43,6 +45,7 @@ public final class FrameManager {
|
||||
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_GPU_WAIT = Tracy.zoneId("sdlGpuWait", Tracy.COLOR_SWAP);
|
||||
private final Device device;
|
||||
private ResourceManager resourceManager;
|
||||
|
||||
@@ -83,8 +86,7 @@ public final class FrameManager {
|
||||
public int batchBreakSamplerThisFrame;
|
||||
public int stateAppliesThisFrame;
|
||||
public long acquireWaitNanosThisFrame;
|
||||
public volatile long lastFrameGateNanos;
|
||||
public volatile long lastFrameGateEndNanos;
|
||||
public long gpuWaitNanosThisFrame;
|
||||
public int midFrameSubmitsThisFrame;
|
||||
public int mipGensThisFrame;
|
||||
public int mipGenSubmitsThisFrame;
|
||||
@@ -466,11 +468,15 @@ 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 (window == null || window == self) return;
|
||||
if (!acquireAffinityViolated(self, window)) return;
|
||||
acquireThreadReported = true;
|
||||
|
||||
final String message = "Swapchain acquired on '" + self.getName() + "', window created on '" + window.getName() + "'";
|
||||
@@ -593,6 +599,7 @@ public final class FrameManager {
|
||||
f.stateAppliesThisFrame = 0;
|
||||
f.acquireWaitNanosThisFrame = 0;
|
||||
f.mipGensThisFrame = 0;
|
||||
f.gpuWaitNanosThisFrame = 0;
|
||||
f.mipGenSubmitsThisFrame = 0;
|
||||
f.uploadFlushSubmitsThisFrame = 0;
|
||||
f.blitsThisFrame = 0;
|
||||
@@ -769,7 +776,10 @@ public final class FrameManager {
|
||||
}
|
||||
|
||||
public int getSwapchainFormat() {
|
||||
return device.getSwapchainTextureFormat();
|
||||
if (device.getClaimedWindow() != 0L) return device.getSwapchainTextureFormat();
|
||||
if (resourceManager == null || !finalTarget.isReady()) return 0;
|
||||
final FboState fbo = resourceManager.getFbo(finalTarget.fboId());
|
||||
return fbo != null ? fbo.getColorFormat() : 0;
|
||||
}
|
||||
|
||||
public boolean isFrameActive() {
|
||||
@@ -869,14 +879,6 @@ public final class FrameManager {
|
||||
registeredFrames.clear();
|
||||
}
|
||||
|
||||
public long lastFrameGateNanos() {
|
||||
return frame().lastFrameGateNanos;
|
||||
}
|
||||
|
||||
public long lastFrameGateEndNanos() {
|
||||
return frame().lastFrameGateEndNanos;
|
||||
}
|
||||
|
||||
private volatile Presenter presenter;
|
||||
|
||||
public void setPresenter(Presenter presenter) {
|
||||
@@ -890,6 +892,7 @@ public final class FrameManager {
|
||||
}
|
||||
|
||||
public void presentFinalTarget() {
|
||||
if (BackendManager.RENDER_BACKEND.isPresentSuppressed()) return;
|
||||
if (!finalTarget.hasContent()) return;
|
||||
present(finalTarget.colorTexture(), finalTarget.width(), finalTarget.height(), SDL_FLIP_NONE, false);
|
||||
}
|
||||
@@ -912,16 +915,13 @@ public final class FrameManager {
|
||||
}
|
||||
|
||||
private final AtomicLong windowAcquireWaitNanos = new AtomicLong();
|
||||
private final AtomicLong windowGpuWaitNanos = 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.acquireWaitNanosThisFrame += windowAcquireWaitNanos.getAndSet(0L);
|
||||
f.gpuWaitNanosThisFrame += windowGpuWaitNanos.getAndSet(0L);
|
||||
f.presentSkipsThisFrame += windowPresentSkips.getAndSet(0);
|
||||
}
|
||||
|
||||
@@ -930,6 +930,11 @@ public final class FrameManager {
|
||||
else f.acquireWaitNanosThisFrame += waitNanos;
|
||||
}
|
||||
|
||||
private void noteGpuWait(FrameState f, long waitNanos) {
|
||||
if (f == windowFrame) windowGpuWaitNanos.addAndGet(waitNanos);
|
||||
else f.gpuWaitNanosThisFrame += waitNanos;
|
||||
}
|
||||
|
||||
private void notePresentSkip(FrameState f) {
|
||||
if (f == windowFrame) windowPresentSkips.incrementAndGet();
|
||||
else f.presentSkipsThisFrame++;
|
||||
@@ -958,13 +963,31 @@ public final class FrameManager {
|
||||
final int w;
|
||||
final int h;
|
||||
final boolean callOk;
|
||||
final boolean swapchainReady;
|
||||
Tracy.beginZone(Z_SDL_GPU_WAIT);
|
||||
try {
|
||||
swapchainReady = SDL_WaitForGPUSwapchain(device.getDevice(), window);
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
final long t1 = System.nanoTime();
|
||||
BackendManager.RENDER_BACKEND.recordGpuWait(t1 - t0);
|
||||
noteGpuWait(f, t1 - t0);
|
||||
|
||||
if (!swapchainReady) {
|
||||
SDL_CancelGPUCommandBuffer(cb);
|
||||
f.swapchainUnavailable = true;
|
||||
notePresentSkip(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
try (var stack = stackPush()) {
|
||||
final PointerBuffer pTexture = stack.pointers(0);
|
||||
final IntBuffer pWidth = stack.ints(0);
|
||||
final IntBuffer pHeight = stack.ints(0);
|
||||
Tracy.beginZone(Z_SDL_ACQUIRE_WAIT);
|
||||
try {
|
||||
callOk = SDL_WaitAndAcquireGPUSwapchainTexture(cb, window, pTexture, pWidth, pHeight);
|
||||
callOk = SDL_AcquireGPUSwapchainTexture(cb, window, pTexture, pWidth, pHeight);
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
@@ -977,9 +1000,6 @@ public final class FrameManager {
|
||||
throw re;
|
||||
}
|
||||
final long tEnd = System.nanoTime();
|
||||
f.lastFrameGateNanos = tEnd - t0;
|
||||
f.lastFrameGateEndNanos = tEnd;
|
||||
noteAcquireWait(f, tEnd - t0);
|
||||
|
||||
if (!callOk || tex == 0) {
|
||||
SDL_CancelGPUCommandBuffer(cb);
|
||||
@@ -988,6 +1008,9 @@ public final class FrameManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
BackendManager.RENDER_BACKEND.recordGate(tEnd - t0, tEnd);
|
||||
noteAcquireWait(f, tEnd - t1);
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final long info = stack.ncalloc(SDL_GPUBlitInfo.ALIGNOF, 1, SDL_GPUBlitInfo.SIZEOF);
|
||||
writeBlitRegion(info + SDL_GPUBlitInfo.SOURCE, srcTexture, srcW, srcH);
|
||||
|
||||
@@ -8,23 +8,34 @@ public final class Presenter {
|
||||
private final FrameManager frameManager;
|
||||
private final Executor windowThreadExecutor;
|
||||
private final Semaphore pending = new Semaphore(1);
|
||||
private final Runnable task;
|
||||
|
||||
private long srcTexture;
|
||||
private int srcW;
|
||||
private int srcH;
|
||||
private int flipMode;
|
||||
|
||||
public Presenter(FrameManager frameManager, Executor windowThreadExecutor) {
|
||||
this.frameManager = frameManager;
|
||||
this.windowThreadExecutor = windowThreadExecutor;
|
||||
this.task = () -> {
|
||||
try {
|
||||
frameManager.presentOnWindowThread(srcTexture, srcW, srcH, flipMode);
|
||||
} finally {
|
||||
pending.release();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void requestPresent(long srcTexture, int srcW, int srcH, int flipMode) {
|
||||
pending.acquireUninterruptibly();
|
||||
this.srcTexture = srcTexture;
|
||||
this.srcW = srcW;
|
||||
this.srcH = srcH;
|
||||
this.flipMode = flipMode;
|
||||
boolean submitted = false;
|
||||
try {
|
||||
windowThreadExecutor.execute(() -> {
|
||||
try {
|
||||
frameManager.presentOnWindowThread(srcTexture, srcW, srcH, flipMode);
|
||||
} finally {
|
||||
pending.release();
|
||||
}
|
||||
});
|
||||
windowThreadExecutor.execute(task);
|
||||
submitted = true;
|
||||
} finally {
|
||||
if (!submitted) pending.release();
|
||||
|
||||
@@ -37,6 +37,7 @@ final class SdlFramePlots {
|
||||
private static final long PLOT_PER_PASS_BLOCK_FLUSHES = Tracy.plotHandle("sdl.perPassBlockFlushes");
|
||||
private static final long PLOT_PER_PASS_BLOCK_BYTES = Tracy.plotHandle("sdl.perPassBlockBytes", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long PLOT_ACQUIRE_WAIT_US = Tracy.plotHandle("sdl.acquireWaitUs");
|
||||
private static final long PLOT_GPU_WAIT_US = Tracy.plotHandle("sdl.gpuWaitUs");
|
||||
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");
|
||||
@@ -97,6 +98,7 @@ final class SdlFramePlots {
|
||||
Tracy.plotInt(PLOT_PER_PASS_BLOCK_FLUSHES, f.uniformBlockFlushesThisFrame[ShaderManager.BLOCK_PER_PASS]);
|
||||
Tracy.plotInt(PLOT_PER_PASS_BLOCK_BYTES, f.uniformBlockBytesThisFrame[ShaderManager.BLOCK_PER_PASS]);
|
||||
Tracy.plotInt(PLOT_ACQUIRE_WAIT_US, f.acquireWaitNanosThisFrame / 1000);
|
||||
Tracy.plotInt(PLOT_GPU_WAIT_US, f.gpuWaitNanosThisFrame / 1000);
|
||||
final long nowNanos = System.nanoTime();
|
||||
if (f.lastEndFrameNanos != 0) Tracy.plotInt(PLOT_PRESENT_TO_PRESENT_US, (nowNanos - f.lastEndFrameNanos) / 1000);
|
||||
f.lastEndFrameNanos = nowNanos;
|
||||
|
||||
@@ -131,7 +131,7 @@ public final class DrawDispatch {
|
||||
}
|
||||
}
|
||||
|
||||
public void drawTriangleFanAsTriangleList(ContextState st, int first, int count) {
|
||||
public boolean drawTriangleFanAsTriangleList(ContextState st, int first, int count) {
|
||||
final int numTriangles = count - 2;
|
||||
final boolean use32bit = (first + count - 1) > 65535;
|
||||
final int bytesPerIndex = use32bit ? 4 : 2;
|
||||
@@ -180,13 +180,14 @@ public final class DrawDispatch {
|
||||
|
||||
setPrimitiveTypeForDraw(st, SDL_GPU_PRIMITIVETYPE_TRIANGLELIST);
|
||||
pipelineApplier.ensureRenderPass(st);
|
||||
if (!frameManager.isRenderPassActive()) return;
|
||||
if (!pipelineApplier.applyPipelineAndState(st)) return;
|
||||
if (!frameManager.isRenderPassActive()) return false;
|
||||
if (!pipelineApplier.applyPipelineAndState(st)) return false;
|
||||
|
||||
final int elementSize = use32bit ? SDL_GPU_INDEXELEMENTSIZE_32BIT : SDL_GPU_INDEXELEMENTSIZE_16BIT;
|
||||
final long rp = frameManager.getRenderPass();
|
||||
bindIndexBufferIfChanged(st, rp, st.fanIndexBuffer, elementSize, uploadOffset);
|
||||
SDL_DrawGPUIndexedPrimitives(rp, numTriangles * 3, 1, 0, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private long createFanIndexBuffer(int capacity) {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FFPUniformBlock;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class FFPTrace {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
private static final String PREFIX = "[FFPTrace] ";
|
||||
private static final int MAX_KEYS = 512;
|
||||
|
||||
private final ResourceManager resourceManager;
|
||||
private final LongOpenHashSet seenKeys = new LongOpenHashSet();
|
||||
private final StringBuilder sb = new StringBuilder(1024);
|
||||
private int drawsThisFrame;
|
||||
private boolean capReported;
|
||||
|
||||
public FFPTrace(ResourceManager resourceManager) {
|
||||
this.resourceManager = resourceManager;
|
||||
}
|
||||
|
||||
public synchronized void trace(ContextState st, String kind, int mode, int count, int indexType, int first, int baseVertex) {
|
||||
drawsThisFrame++;
|
||||
final long key = key(st);
|
||||
if (seenKeys.contains(key)) return;
|
||||
if (seenKeys.size() >= MAX_KEYS) {
|
||||
if (!capReported) {
|
||||
capReported = true;
|
||||
LOG.info("{}key cap {} reached; no further distinct draw states will be logged", PREFIX, MAX_KEYS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
seenKeys.add(key);
|
||||
LOG.info(format(st, kind, mode, count, indexType, first, baseVertex));
|
||||
}
|
||||
|
||||
public synchronized void frameEnd(FrameManager.FrameState f) {
|
||||
LOG.info("{}frame {} end: draws={} dropped={}", PREFIX, f.frameNumber, drawsThisFrame, f.droppedDrawsThisFrame);
|
||||
drawsThisFrame = 0;
|
||||
}
|
||||
|
||||
private long key(ContextState st) {
|
||||
final PipelineCache p = st.pipeline;
|
||||
long h = p.lastKey();
|
||||
h = Hashing.fmix64(h, st.boundFboId);
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(st.boundTextures[0], st.boundTextures[1]));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorEnabled ? 1 : 0, 0));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(Float.floatToRawIntBits(st.viewportX), Float.floatToRawIntBits(st.viewportY)));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(Float.floatToRawIntBits(st.viewportW), Float.floatToRawIntBits(st.viewportH)));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorX, st.scissorY));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorW, st.scissorH));
|
||||
return h;
|
||||
}
|
||||
|
||||
private String format(ContextState st, String kind, int mode, int count, int indexType, int first, int baseVertex) {
|
||||
final PipelineCache p = st.pipeline;
|
||||
final ContextState.VAOState vao = st.currentVao;
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
sb.setLength(0);
|
||||
sb.append(PREFIX);
|
||||
sb.append("kind=").append(kind);
|
||||
sb.append(" mode=0x").append(Integer.toHexString(mode));
|
||||
sb.append(" count=").append(count);
|
||||
if (indexType != 0) sb.append(" indexType=0x").append(Integer.toHexString(indexType));
|
||||
sb.append(" first=").append(first).append(" baseVertex=").append(baseVertex);
|
||||
|
||||
sb.append(" | prog=").append(st.boundProgram);
|
||||
if (prog == null) {
|
||||
sb.append(" progObj=null");
|
||||
} else {
|
||||
sb.append(" ffpUboBinding=").append(prog.externalUboBinding);
|
||||
sb.append(" fragSamplers=").append(prog.fragmentSamplerNames);
|
||||
sb.append(" vsInputs=[");
|
||||
boolean firstName = true;
|
||||
for (int i = 0; i < prog.vertexInputName.length; i++) {
|
||||
final String n = prog.vertexInputName[i];
|
||||
if (n == null) continue;
|
||||
if (!firstName) sb.append(',');
|
||||
firstName = false;
|
||||
sb.append(i).append(':').append(n);
|
||||
}
|
||||
sb.append(']');
|
||||
}
|
||||
|
||||
sb.append(" | fbo=").append(st.boundFboId);
|
||||
sb.append(" vp=(").append(st.viewportX).append(',').append(st.viewportY).append(',')
|
||||
.append(st.viewportW).append(',').append(st.viewportH).append(')');
|
||||
sb.append(" scissor=").append(st.scissorEnabled);
|
||||
if (st.scissorEnabled) {
|
||||
sb.append('(').append(st.scissorX).append(',').append(st.scissorY).append(',')
|
||||
.append(st.scissorW).append(',').append(st.scissorH).append(')');
|
||||
}
|
||||
sb.append(" cull=").append(p.cullEnabled).append("/mode=").append(p.cullFaceMode)
|
||||
.append("/front=").append(p.frontFace).append("/all=").append(p.cullAll);
|
||||
sb.append(" depth=").append(p.depthTestEnabled).append("/func=").append(p.depthCompareOp)
|
||||
.append("/write=").append(p.depthWriteEnabled);
|
||||
sb.append(" blend=").append(p.blendEnabledPerAttachment[0])
|
||||
.append("/src=").append(p.srcColorFactor).append("/dst=").append(p.dstColorFactor)
|
||||
.append("/srcA=").append(p.srcAlphaFactor).append("/dstA=").append(p.dstAlphaFactor)
|
||||
.append("/op=").append(p.colorBlendOp).append('/').append(p.alphaBlendOp);
|
||||
sb.append(" colorMask=0x").append(Integer.toHexString(p.colorWriteMask));
|
||||
sb.append(" primitiveType=").append(p.primitiveType);
|
||||
|
||||
appendTextureUnit(st, 0);
|
||||
appendTextureUnit(st, 1);
|
||||
|
||||
sb.append(" | attribs=[");
|
||||
int enabled = vao.attribEnabledMask;
|
||||
boolean firstAttrib = true;
|
||||
while (enabled != 0) {
|
||||
final int i = Integer.numberOfTrailingZeros(enabled);
|
||||
enabled &= enabled - 1;
|
||||
final int b = vao.attribBinding[i];
|
||||
if (!firstAttrib) sb.append(' ');
|
||||
firstAttrib = false;
|
||||
sb.append(i).append(":size=").append(vao.attribSize[i])
|
||||
.append(",type=0x").append(Integer.toHexString(vao.attribType[i]))
|
||||
.append(",norm=").append(vao.attribNormalized[i])
|
||||
.append(",int=").append(vao.attribIsInteger[i])
|
||||
.append(",stride=").append(vao.attribStride[i])
|
||||
.append(",off=").append(vao.attribOffset[i])
|
||||
.append(",buf=").append(vao.bindingBuffer[b])
|
||||
.append(",bufOff=").append(vao.bindingOffset[b]);
|
||||
}
|
||||
sb.append("] enabledMask=0x").append(Integer.toHexString(vao.attribEnabledMask))
|
||||
.append(" shaderInputMask=0x").append(Integer.toHexString(p.shaderInputMask))
|
||||
.append(" ebo=").append(vao.elementBuffer);
|
||||
|
||||
appendFfpUniforms(st, prog);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void appendTextureUnit(ContextState st, int unit) {
|
||||
final int id = st.boundTextures[unit];
|
||||
sb.append(" | tex").append(unit).append("=").append(id);
|
||||
if (id != 0) {
|
||||
sb.append(" handle=").append(resourceManager.getTextureHandle(id) != 0);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(id);
|
||||
if (meta == null) {
|
||||
sb.append(" meta=null");
|
||||
} else {
|
||||
sb.append(" size=").append(meta.width()).append('x').append(meta.height())
|
||||
.append(" levels=").append(meta.levels())
|
||||
.append(" sdlFormat=").append(meta.sdlFormat())
|
||||
.append(" glFormat=0x").append(Integer.toHexString(meta.glFormat()));
|
||||
}
|
||||
}
|
||||
sb.append(" ffpEnabled=").append(GLStateManager.getTextures().getTextureUnitStates(unit).isEnabled());
|
||||
}
|
||||
|
||||
private void appendFfpUniforms(ContextState st, ShaderManager.ProgramObject prog) {
|
||||
if (prog == null) return;
|
||||
final int binding = prog.externalUboBinding;
|
||||
if (binding < 0 || binding >= ContextState.MAX_INDEXED_BUFFERS) return;
|
||||
final int glId = st.boundUboByIndex[binding];
|
||||
if (glId == 0) {
|
||||
sb.append(" | ffpUbo=unbound");
|
||||
return;
|
||||
}
|
||||
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
|
||||
final ByteBuffer buf = pm != null ? pm.staging : resourceManager.getUboShadow(glId);
|
||||
if (buf == null) {
|
||||
sb.append(" | ffpUbo=buffer").append(glId).append(" noStaging");
|
||||
return;
|
||||
}
|
||||
final int base = st.uboRangeOffset[binding];
|
||||
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(base);
|
||||
appendUboFloat(buf, base, FFPUniformBlock.ALPHA_REF, " u_AlphaRef=");
|
||||
appendUboMatrix(buf, base, FFPUniformBlock.MVP_MATRIX, " u_MVPMatrix=");
|
||||
appendUboMatrix(buf, base, FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, " u_LightmapTextureMatrix=");
|
||||
}
|
||||
|
||||
private static float floatIgnoringLimit(ByteBuffer buf, int at) {
|
||||
return MemoryUtil.memGetFloat(MemoryUtil.memAddress0(buf) + at);
|
||||
}
|
||||
|
||||
private void appendUboFloat(ByteBuffer buf, int base, int memberOffset, String label) {
|
||||
final int at = base + memberOffset;
|
||||
sb.append(label);
|
||||
if (at < 0 || at + 4 > buf.capacity()) {
|
||||
sb.append("oob");
|
||||
return;
|
||||
}
|
||||
sb.append(floatIgnoringLimit(buf, at));
|
||||
}
|
||||
|
||||
private void appendUboMatrix(ByteBuffer buf, int base, int memberOffset, String label) {
|
||||
final int at = base + memberOffset;
|
||||
sb.append(label);
|
||||
if (at < 0 || at + 64 > buf.capacity()) {
|
||||
sb.append("oob");
|
||||
return;
|
||||
}
|
||||
sb.append('[');
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (i > 0) sb.append(',');
|
||||
sb.append(floatIgnoringLimit(buf, at + i * 4));
|
||||
}
|
||||
sb.append(']');
|
||||
}
|
||||
}
|
||||
+14
-11
@@ -337,6 +337,11 @@ public final class PipelineApplier {
|
||||
st.ringChunkUsedBlocks = 0;
|
||||
}
|
||||
|
||||
private int fbo0Height(ContextState st) {
|
||||
final int h = frameManager.getFbo0Height();
|
||||
return h > 0 ? h : (int) (st.viewportY + st.viewportH);
|
||||
}
|
||||
|
||||
public boolean applyPipelineAndState(ContextState st) {
|
||||
return applyPipelineAndState(st, frameManager.frame());
|
||||
}
|
||||
@@ -435,11 +440,14 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
if (st.viewportDirty) {
|
||||
float vpY = st.viewportY;
|
||||
float vpH = st.viewportH;
|
||||
final float vpY;
|
||||
final float vpH;
|
||||
if (renderingToFbo) {
|
||||
vpY = st.viewportY + st.viewportH;
|
||||
vpH = -st.viewportH;
|
||||
} else {
|
||||
vpY = fbo0Height(st) - st.viewportY - st.viewportH;
|
||||
vpH = st.viewportH;
|
||||
}
|
||||
final long addr = st.cachedViewport.address();
|
||||
MemoryAccess.putFloat(addr + SDL_GPUViewport.X, st.viewportX);
|
||||
@@ -455,15 +463,9 @@ public final class PipelineApplier {
|
||||
if (st.scissorDirty) {
|
||||
final long addr = st.cachedScissor.address();
|
||||
final int sx, sy, sw, sh;
|
||||
final int fbHeight;
|
||||
if (st.scissorEnabled) {
|
||||
if (renderingToFbo) {
|
||||
fbHeight = (fboState != null && fboState.height > 0) ? fboState.height : (int)(st.viewportY + st.viewportH);
|
||||
} else {
|
||||
fbHeight = (int)(st.viewportY + st.viewportH);
|
||||
}
|
||||
sx = st.scissorX;
|
||||
sy = fbHeight - st.scissorY - st.scissorH;
|
||||
sy = renderingToFbo ? st.scissorY : (fbo0Height(st) - st.scissorY - st.scissorH);
|
||||
sw = st.scissorW;
|
||||
sh = st.scissorH;
|
||||
MemoryAccess.putInt(addr + SDL_Rect.X, sx);
|
||||
@@ -471,11 +473,12 @@ public final class PipelineApplier {
|
||||
MemoryAccess.putInt(addr + SDL_Rect.W, sw);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.H, sh);
|
||||
if ((sw <= 0 || sh <= 0) && bogusScissorWarned.add(Hashing.packHiLo(sw, sh))) {
|
||||
LOG.warn("applyPipelineAndState: scissor degenerate (x={} y={} w={} h={}) - all fragments clipped; boundProgram={} boundFbo={} fbHeight={} scissorEn={} src=[{},{},{},{}]", sx, sy, sw, sh, st.boundProgram, st.boundFboId, fbHeight, st.scissorEnabled, st.scissorX, st.scissorY, st.scissorW, st.scissorH);
|
||||
LOG.warn("applyPipelineAndState: scissor degenerate (x={} y={} w={} h={}) - all fragments clipped; boundProgram={} boundFbo={} scissorEn={} src=[{},{},{},{}]", sx, sy, sw, sh, st.boundProgram, st.boundFboId, st.scissorEnabled, st.scissorX, st.scissorY, st.scissorW, st.scissorH);
|
||||
}
|
||||
} else {
|
||||
final int rectY = renderingToFbo ? (int) st.viewportY : (int) (fbo0Height(st) - st.viewportY - st.viewportH);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.X, (int) st.viewportX);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.Y, (int) st.viewportY);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.Y, rectY);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.W, (int) st.viewportW);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.H, (int) st.viewportH);
|
||||
}
|
||||
|
||||
@@ -185,6 +185,10 @@ public final class PipelineCache {
|
||||
this.maxAttribs = ContextState.MAX_VERTEX_ATTRIBS;
|
||||
}
|
||||
|
||||
long lastKey() {
|
||||
return lastKey;
|
||||
}
|
||||
|
||||
public void markInputDirty() { inputDirty = true; lastKey = 0L; }
|
||||
|
||||
public boolean markInputDirtyIfLivenessChanged(PipelineStore store, int oldBuffer, int newBuffer) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
public final class CopyRectClip {
|
||||
|
||||
public static final long EMPTY = 0L;
|
||||
|
||||
private static final long FIELD_MASK = 0xFFFFL;
|
||||
|
||||
private CopyRectClip() {}
|
||||
|
||||
public static long clipCopyRect(int srcX, int srcY, int dstX, int dstY, int width, int height,
|
||||
int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
|
||||
int sx = srcX;
|
||||
int sy = srcY;
|
||||
int dx = dstX;
|
||||
int dy = dstY;
|
||||
int w = width;
|
||||
int h = height;
|
||||
if (sx < 0) {
|
||||
w += sx;
|
||||
dx -= sx;
|
||||
sx = 0;
|
||||
}
|
||||
if (sy < 0) {
|
||||
h += sy;
|
||||
dy -= sy;
|
||||
sy = 0;
|
||||
}
|
||||
if (dx < 0) {
|
||||
w += dx;
|
||||
sx -= dx;
|
||||
dx = 0;
|
||||
}
|
||||
if (dy < 0) {
|
||||
h += dy;
|
||||
sy -= dy;
|
||||
dy = 0;
|
||||
}
|
||||
if (srcWidth > 0 && sx + w > srcWidth) w = srcWidth - sx;
|
||||
if (srcHeight > 0 && sy + h > srcHeight) h = srcHeight - sy;
|
||||
if (dstWidth > 0 && dx + w > dstWidth) w = dstWidth - dx;
|
||||
if (dstHeight > 0 && dy + h > dstHeight) h = dstHeight - dy;
|
||||
if (w <= 0 || h <= 0) return EMPTY;
|
||||
if ((sx | sy | w | h) > FIELD_MASK) return EMPTY;
|
||||
return (sx & FIELD_MASK) | ((sy & FIELD_MASK) << 16) | ((w & FIELD_MASK) << 32) | ((h & FIELD_MASK) << 48);
|
||||
}
|
||||
|
||||
public static int srcX(long clip) {
|
||||
return (int) (clip & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int srcY(long clip) {
|
||||
return (int) ((clip >>> 16) & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int width(long clip) {
|
||||
return (int) ((clip >>> 32) & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int height(long clip) {
|
||||
return (int) ((clip >>> 48) & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int dstX(long clip, int origSrcX, int origDstX) {
|
||||
return origDstX + (srcX(clip) - origSrcX);
|
||||
}
|
||||
|
||||
public static int dstY(long clip, int origSrcY, int origDstY) {
|
||||
return origDstY + (srcY(clip) - origSrcY);
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,12 @@ public final class FBOClearTracker {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void resolveDestinationForWrite(ContextState st, long destTex, ResourceManager.TextureMeta destMeta, int level, int dx, int dy, int dz, int w, int h) {
|
||||
if (dz != 0 || !discardPendingClearIfFullyCovered(st, destTex, dx, dy, level, w, h, destMeta)) {
|
||||
materializePendingClearForTexture(st, destTex);
|
||||
}
|
||||
}
|
||||
|
||||
public void materializePendingClearForTexture(ContextState st, long handle) {
|
||||
if (handle == 0) return;
|
||||
final boolean depthPending = st.pendingDepthTextures.remove(handle);
|
||||
|
||||
@@ -13,6 +13,12 @@ import static org.lwjgl.sdl.SDLGPU.*;
|
||||
public final class PixelOps {
|
||||
private PixelOps() {}
|
||||
|
||||
public static int mipLevelSize(int base, int level) {
|
||||
if (base <= 0 || level < 0) return 0;
|
||||
if (level >= Integer.SIZE - 1) return 1;
|
||||
return Math.max(1, base >> level);
|
||||
}
|
||||
|
||||
public static int defaultMipLevels(int width, int height) {
|
||||
final int max = Math.max(1, Math.max(width, height));
|
||||
return Math.max(1, 32 - Integer.numberOfLeadingZeros(max));
|
||||
|
||||
+18
-4
@@ -2208,7 +2208,24 @@ public final class ResourceManager {
|
||||
return mapTextureFormat(glFormat, preferredD24, preferredD24S8);
|
||||
}
|
||||
|
||||
public boolean isMappableTextureFormat(int glFormat) {
|
||||
return lookupTextureFormat(glFormat, preferredD24, preferredD24S8) != SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||
}
|
||||
|
||||
public static int mapTextureFormat(int glFormat, int preferredD24, int preferredD24S8) {
|
||||
final int mapped = lookupTextureFormat(glFormat, preferredD24, preferredD24S8);
|
||||
if (mapped != SDL_GPU_TEXTUREFORMAT_INVALID) return mapped;
|
||||
synchronized (UNMAPPED_TEXTURE_FORMAT_SEEN) {
|
||||
if (UNMAPPED_TEXTURE_FORMAT_SEEN.add(glFormat)) {
|
||||
LOG.warn("Unmapped GL texture format: 0x{}", Integer.toHexString(glFormat));
|
||||
}
|
||||
}
|
||||
return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
|
||||
private static final IntOpenHashSet UNMAPPED_TEXTURE_FORMAT_SEEN = new IntOpenHashSet();
|
||||
|
||||
private static int lookupTextureFormat(int glFormat, int preferredD24, int preferredD24S8) {
|
||||
return switch (glFormat) {
|
||||
case GL11.GL_RGBA, GL11.GL_RGBA8 -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
case GL11.GL_RGB, GL11.GL_RGB8 -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; // RGB -> RGBA promotion
|
||||
@@ -2262,10 +2279,7 @@ public final class ResourceManager {
|
||||
case GL31.GL_RGBA8_SNORM -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM;
|
||||
case GL31.GL_RGBA16_SNORM -> SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM;
|
||||
case GL31.GL_RGB16_SNORM -> SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM; // RGB SNORM -> RGBA SNORM promotion
|
||||
default -> {
|
||||
LOG.warn("Unmapped GL texture format: 0x{}", Integer.toHexString(glFormat));
|
||||
yield SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
default -> SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import org.lwjgl.system.MemoryUtil;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
|
||||
|
||||
public final class TextureOps {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
@@ -113,6 +115,11 @@ public final class TextureOps {
|
||||
final long destTex = resourceManager.getTextureHandle(destGlId);
|
||||
if (destTex == 0) return;
|
||||
|
||||
if (st.boundReadFboId == 0) {
|
||||
copyFromDefaultFramebuffer(st, destGlId, destTex, level, xoffset, yoffset, x, y, width, height);
|
||||
return;
|
||||
}
|
||||
|
||||
final FboState fbo = resourceManager.getFbo(st.boundReadFboId);
|
||||
if (fbo == null) return;
|
||||
|
||||
@@ -121,35 +128,81 @@ public final class TextureOps {
|
||||
final long srcTex = isDepthDest ? fbo.depthTexture : fbo.colorTextures[fbo.readBufferIndex];
|
||||
if (srcTex == 0) return;
|
||||
|
||||
if (isDepthDest) {
|
||||
copyTexture(srcTex, x, y, destTex, xoffset, yoffset, width, height);
|
||||
return;
|
||||
}
|
||||
|
||||
final int srcGlId = fbo.colorGlIds[fbo.readBufferIndex];
|
||||
final int srcGlId = isDepthDest ? fbo.depthGlId : fbo.colorGlIds[fbo.readBufferIndex];
|
||||
final ResourceManager.TextureMeta srcMeta = srcGlId != 0 ? resourceManager.getTextureMeta(srcGlId) : null;
|
||||
if (srcMeta != null && destMeta != null && srcMeta.sdlFormat() == destMeta.sdlFormat()) {
|
||||
copyTexture(srcTex, x, y, destTex, xoffset, yoffset, width, height);
|
||||
final long clip = CopyRectClip.clipCopyRect(x, y, xoffset, yoffset, width, height, srcMeta != null ? srcMeta.width() : fbo.width, srcMeta != null ? srcMeta.height() : fbo.height, levelWidth(destMeta, level), levelHeight(destMeta, level));
|
||||
if (clip == CopyRectClip.EMPTY) return;
|
||||
final int sx = CopyRectClip.srcX(clip);
|
||||
final int sy = CopyRectClip.srcY(clip);
|
||||
final int dx = CopyRectClip.dstX(clip, x, xoffset);
|
||||
final int dy = CopyRectClip.dstY(clip, y, yoffset);
|
||||
final int w = CopyRectClip.width(clip);
|
||||
final int h = CopyRectClip.height(clip);
|
||||
|
||||
fboClearTracker.materializePendingClearForTexture(st, srcTex);
|
||||
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h);
|
||||
|
||||
if (isDepthDest || (srcMeta != null && destMeta != null && srcMeta.sdlFormat() == destMeta.sdlFormat())) {
|
||||
copyTexture(srcTex, sx, sy, destTex, level, dx, dy, w, h);
|
||||
} else {
|
||||
blitTexture(srcTex, x, y, width, height, destTex, xoffset, yoffset, width, height, GL11.GL_NEAREST);
|
||||
blitTexture(srcTex, sx, sy, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST);
|
||||
}
|
||||
}
|
||||
|
||||
private static int levelWidth(ResourceManager.TextureMeta meta, int level) {
|
||||
return meta != null ? PixelOps.mipLevelSize(meta.width(), level) : 0;
|
||||
}
|
||||
|
||||
private static int levelHeight(ResourceManager.TextureMeta meta, int level) {
|
||||
return meta != null ? PixelOps.mipLevelSize(meta.height(), level) : 0;
|
||||
}
|
||||
|
||||
private void copyFromDefaultFramebuffer(ContextState st, int destGlId, long destTex, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
final ResourceManager.TextureMeta destMeta = resourceManager.getTextureMeta(destGlId);
|
||||
if (destMeta != null && PixelOps.isDepthFormat(destMeta.sdlFormat())) return;
|
||||
|
||||
final long srcTex = frameManager.getFbo0Texture();
|
||||
if (srcTex == 0) return;
|
||||
final int srcFullW = frameManager.getFbo0Width();
|
||||
final int srcFullH = frameManager.getFbo0Height();
|
||||
if (srcFullW <= 0 || srcFullH <= 0) return;
|
||||
|
||||
final long clip = CopyRectClip.clipCopyRect(x, y, xoffset, yoffset, width, height, srcFullW, srcFullH, levelWidth(destMeta, level), levelHeight(destMeta, level));
|
||||
if (clip == CopyRectClip.EMPTY) return;
|
||||
final int sx = CopyRectClip.srcX(clip);
|
||||
final int sy = CopyRectClip.srcY(clip);
|
||||
final int dx = CopyRectClip.dstX(clip, x, xoffset);
|
||||
final int dy = CopyRectClip.dstY(clip, y, yoffset);
|
||||
final int w = CopyRectClip.width(clip);
|
||||
final int h = CopyRectClip.height(clip);
|
||||
|
||||
if (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) {
|
||||
frameManager.ensureFbo0RenderPass(frameManager.frame(), st);
|
||||
}
|
||||
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h);
|
||||
|
||||
blitTexture(srcTex, sx, srcFullH - sy - h, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST, SDL_FLIP_VERTICAL);
|
||||
}
|
||||
|
||||
public static boolean canCopyInsteadOfBlit(ResourceManager.TextureMeta src, ResourceManager.TextureMeta dst, int srcW, int srcH, int dstW, int dstH) {
|
||||
return src != null && dst != null && src.sdlFormat() == dst.sdlFormat() && srcW > 0 && srcH > 0 && srcW == dstW && srcH == dstH;
|
||||
}
|
||||
|
||||
public void copyTexture(long srcTex, int srcX, int srcY, long dstTex, int dstX, int dstY, int w, int h) {
|
||||
public void copyTexture(long srcTex, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
if (cp == 0) return;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final var src = SDL_GPUTextureLocation.calloc(stack).texture(srcTex).x(srcX).y(srcY);
|
||||
final var dst = SDL_GPUTextureLocation.calloc(stack).texture(dstTex).x(dstX).y(dstY);
|
||||
final var dst = SDL_GPUTextureLocation.calloc(stack).texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY);
|
||||
SDL_CopyGPUTextureToTexture(cp, src, dst, w, h, 1, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstX, int dstY, int dstW, int dstH, int glFilter) {
|
||||
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter) {
|
||||
blitTexture(srcTex, srcX, srcY, srcW, srcH, dstTex, dstLevel, dstX, dstY, dstW, dstH, glFilter, SDL_FLIP_NONE);
|
||||
}
|
||||
|
||||
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter, int flipMode) {
|
||||
frameManager.endCopyPassIfActive();
|
||||
frameManager.endRenderPassIfActive();
|
||||
final long cb = frameManager.getCommandBuffer();
|
||||
@@ -158,9 +211,9 @@ public final class TextureOps {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final var info = SDL_GPUBlitInfo.calloc(stack);
|
||||
info.source(s -> s.texture(srcTex).x(srcX).y(srcY).w(srcW).h(srcH));
|
||||
info.destination(d -> d.texture(dstTex).x(dstX).y(dstY).w(dstW).h(dstH));
|
||||
info.destination(d -> d.texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY).w(dstW).h(dstH));
|
||||
info.filter(glFilter == GL11.GL_LINEAR ? SDL_GPU_FILTER_LINEAR : SDL_GPU_FILTER_NEAREST);
|
||||
info.flip_mode(0);
|
||||
info.flip_mode(flipMode);
|
||||
frameManager.noteBlit();
|
||||
SDL_BlitGPUTexture(cb, info);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrays;
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
@@ -599,29 +600,84 @@ public final class ShaderManager {
|
||||
|
||||
private static int patchAttribLocations(ByteBuffer spirv, StageReflection vs, Object2IntOpenHashMap<String> bindings, int[] outVecSize, int[] outBaseType, String[] outName, Object2IntOpenHashMap<String> resolvedOut) {
|
||||
final IntBuffer vsBuf = spirv.asIntBuffer();
|
||||
int mask = 0;
|
||||
if (outName != null) Arrays.fill(outName, null);
|
||||
for (VsInput vi : vs.vsInputs()) {
|
||||
int finalLoc = vi.originalLocation();
|
||||
final int desired = bindings.getInt(vi.name());
|
||||
if (desired != -1 && desired != vi.originalLocation()) {
|
||||
if (vsBuf.get(vi.binaryOffset()) != vi.originalLocation()) {
|
||||
LOG.error("applyAttribLocations: sanity check failed for '{}' (expected {}, got {})", vi.name(), vi.originalLocation(), vsBuf.get(vi.binaryOffset()));
|
||||
} else {
|
||||
vsBuf.put(vi.binaryOffset(), desired);
|
||||
finalLoc = desired;
|
||||
final List<VsInput> inputs = vs.vsInputs();
|
||||
final int count = inputs.size();
|
||||
final int[] finalLoc = new int[count];
|
||||
int taken = 0;
|
||||
|
||||
for (final IntIterator it = bindings.values().iterator(); it.hasNext(); ) {
|
||||
final int bound = it.nextInt();
|
||||
if (bound >= 0 && bound < ContextState.MAX_VERTEX_ATTRIBS) taken |= 1 << bound;
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
finalLoc[i] = bindings.getInt(inputs.get(i).name());
|
||||
}
|
||||
|
||||
int movers = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (finalLoc[i] != -1) continue;
|
||||
final int original = inputs.get(i).originalLocation();
|
||||
if (original >= 0 && original < ContextState.MAX_VERTEX_ATTRIBS && (taken & (1 << original)) == 0) {
|
||||
finalLoc[i] = original;
|
||||
taken |= 1 << original;
|
||||
} else {
|
||||
movers++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int n = 0; n < movers; n++) {
|
||||
int pick = -1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (finalLoc[i] == -1 && (pick == -1 || inputs.get(i).originalLocation() < inputs.get(pick).originalLocation())) {
|
||||
pick = i;
|
||||
}
|
||||
}
|
||||
if (resolvedOut != null) resolvedOut.put(vi.name(), finalLoc);
|
||||
if (finalLoc < 0 || finalLoc >= 16) continue;
|
||||
mask |= (1 << finalLoc);
|
||||
outVecSize[finalLoc] = vi.vecSize();
|
||||
outBaseType[finalLoc] = vi.baseType();
|
||||
if (outName != null) outName[finalLoc] = vi.name();
|
||||
final int free = Integer.numberOfTrailingZeros(~taken);
|
||||
if (free >= ContextState.MAX_VERTEX_ATTRIBS) {
|
||||
LOG.error("applyAttribLocations: no free vertex attribute slot for '{}'; leaving it at {}", inputs.get(pick).name(), inputs.get(pick).originalLocation());
|
||||
finalLoc[pick] = inputs.get(pick).originalLocation();
|
||||
} else {
|
||||
finalLoc[pick] = free;
|
||||
taken |= 1 << free;
|
||||
}
|
||||
}
|
||||
|
||||
int mask = 0;
|
||||
int seen = 0;
|
||||
if (outName != null) Arrays.fill(outName, null);
|
||||
for (int i = 0; i < count; i++) {
|
||||
final VsInput vi = inputs.get(i);
|
||||
int loc = finalLoc[i];
|
||||
if (loc != vi.originalLocation()) {
|
||||
if (vsBuf.get(vi.binaryOffset()) != vi.originalLocation()) {
|
||||
LOG.error("applyAttribLocations: sanity check failed for '{}' (expected {}, got {})", vi.name(), vi.originalLocation(), vsBuf.get(vi.binaryOffset()));
|
||||
loc = vi.originalLocation();
|
||||
} else {
|
||||
vsBuf.put(vi.binaryOffset(), loc);
|
||||
}
|
||||
}
|
||||
finalLoc[i] = loc;
|
||||
if (resolvedOut != null) resolvedOut.put(vi.name(), loc);
|
||||
if (loc < 0 || loc >= ContextState.MAX_VERTEX_ATTRIBS) continue;
|
||||
if ((seen & (1 << loc)) != 0) {
|
||||
LOG.error("applyAttribLocations: '{}' and '{}' both resolved to location {}", inputNameAtLocation(inputs, finalLoc, i, loc), vi.name(), loc);
|
||||
}
|
||||
seen |= 1 << loc;
|
||||
mask |= 1 << loc;
|
||||
outVecSize[loc] = vi.vecSize();
|
||||
outBaseType[loc] = vi.baseType();
|
||||
if (outName != null) outName[loc] = vi.name();
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
private static String inputNameAtLocation(List<VsInput> inputs, int[] finalLoc, int limit, int loc) {
|
||||
for (int i = 0; i < limit; i++) {
|
||||
if (finalLoc[i] == loc) return inputs.get(i).name();
|
||||
}
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
public VertexVariant getOrBuildVertexVariant(int program, long key, List<UscaledRetype.Attrib> attribs) {
|
||||
final ProgramObject prog = programObjects.get(program);
|
||||
if (prog == null || !prog.linked || prog.vertexSource.isEmpty()) return null;
|
||||
|
||||
+5
-1
@@ -33,10 +33,14 @@ public final class SplashDispatcher {
|
||||
return (long) w << 32 | (h & 0xffffffffL);
|
||||
}
|
||||
|
||||
static boolean intervalElapsed(long now, long lastBlitNanos, boolean force) {
|
||||
return force || now - lastBlitNanos >= MIN_INTERVAL_NS;
|
||||
}
|
||||
|
||||
public static void tryDispatch(SDLGPURenderBackend backend, OffscreenTarget target, boolean force) {
|
||||
if (!frameReady || target == null) return;
|
||||
final long now = System.nanoTime();
|
||||
if (!force && now - lastBlitNanos < MIN_INTERVAL_NS) return;
|
||||
if (!intervalElapsed(now, lastBlitNanos, force)) return;
|
||||
lastBlitNanos = now;
|
||||
frameReady = false;
|
||||
backend.dispatchSplashBlit(target);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DispatchIndirectBindingTest {
|
||||
|
||||
@AfterEach
|
||||
void resetContextState() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.boundIndirectBuffer = 0;
|
||||
st.boundDispatchIndirectBuffer = 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchIndirectHasItsOwnBindingPoint() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
|
||||
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 11);
|
||||
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 22);
|
||||
|
||||
assertEquals(11, st.getBoundBuffer(GL40.GL_DRAW_INDIRECT_BUFFER));
|
||||
assertEquals(22, st.getBoundBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingDispatchIndirectLeavesTheDrawIndirectSlotAlone() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
|
||||
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 33);
|
||||
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 44);
|
||||
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 0);
|
||||
|
||||
assertEquals(33, st.getBoundBuffer(GL40.GL_DRAW_INDIRECT_BUFFER));
|
||||
assertEquals(0, st.getBoundBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class ProxyTextureLimitsTest {
|
||||
|
||||
@AfterEach
|
||||
void resetContextState() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 0;
|
||||
st.proxyTexture.reject(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void threeDimensionalProxyIsBoundedByTheVolumeLimit() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final int limit = SDLGPURenderBackend.MAX_3D_TEXTURE_SIZE;
|
||||
|
||||
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit, limit, limit, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(limit, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(limit, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
|
||||
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit * 2, limit, limit, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
|
||||
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit, limit, limit * 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyProxyDimensionLimitIsAlsoQueryableAsADeviceLimit() {
|
||||
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL11.GL_MAX_TEXTURE_SIZE));
|
||||
assertEquals(2048, SDLGPURenderBackend.deviceLimit(GL12.GL_MAX_3D_TEXTURE_SIZE));
|
||||
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
|
||||
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
|
||||
assertEquals(2048, SDLGPURenderBackend.proxyDimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneDimensionalArrayLayersUseTheLayerLimitNotTheDimensionLimit() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final int layers = SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS;
|
||||
|
||||
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_RGBA, 512, layers, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(512, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(layers, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_HEIGHT));
|
||||
|
||||
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_RGBA, 512, layers + 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLayerCountDoesNotHalvePerLevel() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
|
||||
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_RGBA, 128, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(128, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(64, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_TEXTURE_HEIGHT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aProbedLevelIsAcceptedOnItsLevelZeroEquivalentSize() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_RGBA, 1024, 512, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(1024, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(512, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_HEIGHT));
|
||||
assertEquals(8192, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aProbedLevelWhoseBaseWouldOverflowTheLimitIsRejected() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 2, GL11.GL_RGBA, SDLGPURenderBackend.MAX_TEXTURE_SIZE, 4, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 2, GL11.GL_TEXTURE_WIDTH));
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 40, GL11.GL_RGBA, 4, 4, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 40, GL11.GL_TEXTURE_WIDTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void levelZeroEquivalentSizeRejectsOverflowAndNonsense() {
|
||||
assertEquals(4, SDLGPURenderBackend.levelZeroEquivalentSize(4, 0));
|
||||
assertEquals(32, SDLGPURenderBackend.levelZeroEquivalentSize(4, 3));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, -1));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(0, 2));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 31));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 30));
|
||||
assertEquals(1 << 30, SDLGPURenderBackend.levelZeroEquivalentSize(1, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTargetsAnswerTheGlParameterDefaults() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final int target = GL11.GL_PROXY_TEXTURE_2D;
|
||||
|
||||
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
assertEquals(GL11.GL_LINEAR, backend.getTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER));
|
||||
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL11.GL_TEXTURE_WRAP_S));
|
||||
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL11.GL_TEXTURE_WRAP_T));
|
||||
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL12.GL_TEXTURE_WRAP_R));
|
||||
assertEquals(0, backend.getTexParameteri(target, GL12.GL_TEXTURE_BASE_LEVEL));
|
||||
assertEquals(1000, backend.getTexParameteri(target, GL12.GL_TEXTURE_MAX_LEVEL));
|
||||
assertEquals(-1000.0f, backend.getTexParameterf(target, GL12.GL_TEXTURE_MIN_LOD));
|
||||
assertEquals(1000.0f, backend.getTexParameterf(target, GL12.GL_TEXTURE_MAX_LOD));
|
||||
assertEquals(0.0f, backend.getTexParameterf(target, GL14.GL_TEXTURE_LOD_BIAS));
|
||||
assertEquals(1.0f, backend.getTexParameterf(target, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyParameterDefaultsIgnoreTheBoundTexturesOwnState() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 8181;
|
||||
|
||||
backend.texParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
assertEquals(GL11.GL_NEAREST, backend.getTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(GL11.GL_PROXY_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTargetsNeverReceiveParameterWrites() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 8282;
|
||||
|
||||
backend.texParameteri(GL11.GL_PROXY_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
backend.texParameterf(GL11.GL_PROXY_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 4.0f);
|
||||
|
||||
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
assertEquals(0.0f, backend.getTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
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.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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 ProxyTextureTargetTest {
|
||||
|
||||
private static final int[] PROXY_TARGETS = {
|
||||
0x8063, 0x8064, 0x8070, 0x851B, 0x8C19, 0x8C1B, 0x84F7, 0x900B, 0x9101, 0x9103
|
||||
};
|
||||
|
||||
private static SDLGPURenderBackend backend;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() {
|
||||
backend = new SDLGPURenderBackend();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void resetContextState() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 0;
|
||||
st.boundTextures[1] = 0;
|
||||
st.proxyTexture.reject(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyProxyTargetIsRecognized() {
|
||||
for (int target : PROXY_TARGETS) {
|
||||
assertTrue(SDLGPURenderBackend.isProxyTarget(target), () -> "0x" + Integer.toHexString(target));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void realTargetsAreNotProxies() {
|
||||
final int[] real = {
|
||||
GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_3D, GL13.GL_TEXTURE_CUBE_MAP,
|
||||
GL30.GL_TEXTURE_1D_ARRAY, GL30.GL_TEXTURE_2D_ARRAY, GL31.GL_TEXTURE_RECTANGLE,
|
||||
GL40.GL_TEXTURE_CUBE_MAP_ARRAY, GL32.GL_TEXTURE_2D_MULTISAMPLE, GL32.GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
|
||||
0
|
||||
};
|
||||
for (int target : real) {
|
||||
assertFalse(SDLGPURenderBackend.isProxyTarget(target), () -> "0x" + Integer.toHexString(target));
|
||||
}
|
||||
}
|
||||
|
||||
static Stream<Arguments> levelSizeCases() {
|
||||
return Stream.of(
|
||||
Arguments.of("level0", 16384, 0, 16384),
|
||||
Arguments.of("level1", 16384, 1, 8192),
|
||||
Arguments.of("level14FloorsAtOne", 16384, 14, 1),
|
||||
Arguments.of("level15FloorsAtOne", 16384, 15, 1),
|
||||
Arguments.of("level64FloorsAtOne", 16384, 64, 1),
|
||||
Arguments.of("baseOneFloorsAtOne", 1, 3, 1),
|
||||
Arguments.of("oddBaseRoundsDown", 6, 1, 3),
|
||||
Arguments.of("rejectedProxyBaseZeroLevel0", 0, 0, 0),
|
||||
Arguments.of("rejectedProxyBaseZeroLevel5", 0, 5, 0),
|
||||
Arguments.of("rejectedProxyNegativeLevel", 16384, -1, 0));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("levelSizeCases")
|
||||
void levelSizeMatchesExpectation(String label, int base, int level, int expected) {
|
||||
assertEquals(expected, SDLGPURenderBackend.proxyLevelSize(base, level), label);
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTexImage2DRecordsDimensionsWithoutTouchingTheBoundTexture() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 1;
|
||||
st.boundTextures[1] = 4242;
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 16384, 16384, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
assertEquals(4242, st.boundTextures[1]);
|
||||
assertEquals(16384, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(16384, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT));
|
||||
assertEquals(2048, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(GL11.GL_RGBA, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_BORDER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oversizedProxyTexImage2DZeroesEveryProxyField() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 16384, 16384, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 32768, 32768, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyArrayTargetIsLimitedByLayerCount() {
|
||||
final int layers = SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS;
|
||||
|
||||
backend.texImage3D(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_RGBA, 64, 64, layers, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(layers, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
|
||||
backend.texImage3D(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_RGBA, 64, 64, layers + 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyStateIsScopedToTheProbedTarget() {
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
assertEquals(256, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_HEIGHT));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unmappableProxyFormatIsRejected() {
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, 0x1BADF00D, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyBindIsInert() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 77;
|
||||
|
||||
for (int target : PROXY_TARGETS) {
|
||||
backend.bindTexture(target, 99);
|
||||
assertEquals(77, st.boundTextures[0], () -> "0x" + Integer.toHexString(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-3
@@ -1,17 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.mitchej123.lwjgl.LWJGLService;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class SDLGPULWJGLServiceGatingTest {
|
||||
|
||||
@Test
|
||||
void anUnengagedSdlDeclinesAndNeverArmsTheFallbackTripwire() {
|
||||
final String key = SystemProperties.KEY_CELERITAS_LWJGL_SERVICE;
|
||||
assertFalse(SDLGPUGate.isEngaged(), "test JVM must not have an engaged SDL GPU device");
|
||||
assertEquals(LWJGLService.PRIORITY_UNAVAILABLE, new SDLGPULWJGLService().getPriority());
|
||||
assertFalse(SDLGPULWJGLService.isOfferedAsAvailable(), "declining must leave fallBackToGL() usable");
|
||||
assertNull(System.getProperty(key), "an unengaged SDL must not bind the celeritas service");
|
||||
|
||||
try {
|
||||
SDLGPULWJGLService.bind();
|
||||
assertEquals(SDLGPULWJGLService.class.getName(), System.getProperty(key));
|
||||
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
SDLGPULWJGLService::create,
|
||||
"constructing the service while SDL GPU is unengaged must fail loudly");
|
||||
} finally {
|
||||
SDLGPULWJGLService.unbind();
|
||||
}
|
||||
|
||||
assertNull(System.getProperty(key));
|
||||
assertFalse(SDLGPULWJGLService.isConstructed(), "declining must leave fallBackToGL() usable");
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -12,22 +12,17 @@ class AcquireAffinityTest {
|
||||
|
||||
@Test
|
||||
void acquiringOnTheWindowThreadIsTheOnlyAcceptedCase() {
|
||||
assertFalse(FrameManager.acquireAffinityViolated(WINDOW, WINDOW, 0));
|
||||
assertFalse(FrameManager.acquireAffinityViolated(WINDOW, WINDOW));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acquiringOnAnyOtherThreadViolatesTheSdlContract() {
|
||||
assertTrue(FrameManager.acquireAffinityViolated(OTHER, WINDOW, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aLaterAcquireFromASecondThreadViolatesEvenWhenTheFirstWasCorrect() {
|
||||
assertTrue(FrameManager.acquireAffinityViolated(WINDOW, WINDOW, 1),
|
||||
"drift after a correct first acquire must still be caught");
|
||||
assertTrue(FrameManager.acquireAffinityViolated(OTHER, WINDOW));
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnknownWindowThreadCannotBeJudged() {
|
||||
assertFalse(FrameManager.acquireAffinityViolated(OTHER, null, 3));
|
||||
assertFalse(FrameManager.acquireAffinityViolated(OTHER, null),
|
||||
"the check runs before the window is claimed; judging a null window would throw at startup");
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import java.util.Deque;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
|
||||
@@ -72,6 +73,21 @@ class PresenterTest {
|
||||
drainer.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestReusesOneRunnableAcrossCalls() {
|
||||
final DeferredExecutor executor = new DeferredExecutor();
|
||||
final Presenter p = presenter(executor);
|
||||
|
||||
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
|
||||
final Runnable first = executor.tasks.peekFirst();
|
||||
executor.runNext();
|
||||
|
||||
p.requestPresent(2L, 10, 10, SDL_FLIP_NONE);
|
||||
final Runnable second = executor.tasks.peekFirst();
|
||||
|
||||
assertSame(first, second, "Presenter must submit the same preallocated Runnable on every request");
|
||||
}
|
||||
|
||||
@Test
|
||||
void anInlineExecutorReleasesTheSlotImmediately() {
|
||||
final Presenter p = new Presenter(new FrameManager(new Device()), Runnable::run);
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlCopyBlitTest {
|
||||
|
||||
private static final int RED = 0xFFFF0000;
|
||||
private static final int GREEN = 0xFF00FF00;
|
||||
private static final int BLUE = 0xFF0000FF;
|
||||
|
||||
private static final int SCISSOR_ROWS = 16;
|
||||
private static final byte SENTINEL_BYTE = 0x7B;
|
||||
private static final int SENTINEL = 0x7B7B7B7B;
|
||||
|
||||
private static FrameManager frameManager;
|
||||
private static ResourceManager resourceManager;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
frameManager = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
|
||||
resourceManager = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
frameManager.destroyFinalTarget();
|
||||
frameManager.finalTarget().create(SDLGPUGate.device(), resourceManager, SIZE, SIZE, resourceManager.mapTextureFormat(GL11.GL_RGBA8));
|
||||
assertEquals(SIZE, frameManager.getFbo0Height());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void dropFinalTarget() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
if (frameManager != null) frameManager.destroyFinalTarget();
|
||||
}
|
||||
|
||||
private static void assertRow(int[] pixels, int y, int expected, String label) {
|
||||
for (int x = 0; x < SIZE; x += 8) {
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, x, y, expected, label + " row " + y);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissorOnTheUserFboClipsTheGlBottomRows() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE, SCISSOR_ROWS);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertRow(pixels, 0, RED, "inside the scissor");
|
||||
assertRow(pixels, SCISSOR_ROWS - 1, RED, "inside the scissor");
|
||||
assertRow(pixels, SCISSOR_ROWS, BLUE, "outside the scissor");
|
||||
assertRow(pixels, SIZE - 1, BLUE, "outside the scissor");
|
||||
}
|
||||
|
||||
private static int createTwoLevelTexture(int level0Argb, int level1Argb) {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 1);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, 0.0f);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, 1.0f);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0f);
|
||||
uploadLevel(0, SIZE, level0Argb);
|
||||
uploadLevel(1, SIZE / 2, level1Argb);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void uploadLevel(int level, int size, int argb) {
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(size * size * 4);
|
||||
try {
|
||||
for (int i = 0; i < size * size; i++) {
|
||||
texels.put(i * 4, (byte) ((argb >> 16) & 0xFF));
|
||||
texels.put(i * 4 + 1, (byte) ((argb >> 8) & 0xFF));
|
||||
texels.put(i * 4 + 2, (byte) (argb & 0xFF));
|
||||
texels.put(i * 4 + 3, (byte) ((argb >>> 24) & 0xFF));
|
||||
}
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, size, size, 0,
|
||||
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
}
|
||||
|
||||
private static int copyGreenIntoLevelOne() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
|
||||
|
||||
final int texture = createTwoLevelTexture(RED, BLUE);
|
||||
GLStateManager.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 1, 0, 0, 0, 0, SIZE / 2, SIZE / 2);
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexSubImage2DWritesTheRequestedMipLevel() {
|
||||
final int texture = copyGreenIntoLevelOne();
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.texturedQuad(texture, 0.5f);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "minified sample of level 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexSubImage2DLeavesTheOtherMipLevelsUntouched() {
|
||||
final int texture = copyGreenIntoLevelOne();
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.texturedQuad(texture, 1.0f);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 4, SIZE / 4, RED, "magnified sample of the level 0 region the copy would land in");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readPixelsCrossingTheTopEdgeReturnsTheInRangeRows() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget(0, SIZE / 2, SIZE, SIZE, SENTINEL_BYTE);
|
||||
assertRow(pixels, 0, GREEN, "first in-range row");
|
||||
assertRow(pixels, SIZE / 2 - 1, GREEN, "last in-range row");
|
||||
assertRow(pixels, SIZE / 2, SENTINEL, "first out-of-range row");
|
||||
assertRow(pixels, SIZE - 1, SENTINEL, "last out-of-range row");
|
||||
}
|
||||
|
||||
private static int createSecondaryFbo() {
|
||||
final int color = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, color);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
final int depth = GLStateManager.glGenRenderbuffers();
|
||||
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depth);
|
||||
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
|
||||
|
||||
final int fbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, color, 0);
|
||||
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depth);
|
||||
return fbo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void blitFramebufferWithColorAndDepthBitsMovesColor() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
|
||||
|
||||
final int destination = createSecondaryFbo();
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, destination);
|
||||
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE,
|
||||
GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT, GL11.GL_NEAREST);
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, destination);
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "color aspect of a color+depth blit");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 4, SIZE - 5, GREEN, "color aspect of a color+depth blit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void blitFramebufferIntoFbo0SurvivesTheFramesFirstFbo0Pass() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
|
||||
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
|
||||
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, 4, 4);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "blit into fbo0 survived the first fbo0 pass");
|
||||
}
|
||||
|
||||
@Test
|
||||
void blitFramebufferFromFbo0ToTheUserFboKeepsGlOrientation() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 0.0f);
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.halfQuad(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
|
||||
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
|
||||
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, 4, GREEN, "blitted fbo0 bottom");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE - 5, RED, "blitted fbo0 top");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2 - 4, GREEN, "blitted fbo0 below the split");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2 + 4, RED, "blitted fbo0 above the split");
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlFbo0ViewportTest {
|
||||
|
||||
private static final int FBO0_W = 64;
|
||||
private static final int FBO0_H = 96;
|
||||
private static final int VIEW = 32;
|
||||
|
||||
private static final int RED = 0xFFFF0000;
|
||||
private static final int GREEN = 0xFF00FF00;
|
||||
private static final int BLUE = 0xFF0000FF;
|
||||
|
||||
private static FrameManager frameManager;
|
||||
private static ResourceManager resourceManager;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
frameManager = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
|
||||
resourceManager = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
frameManager.destroyFinalTarget();
|
||||
frameManager.finalTarget().create(SDLGPUGate.device(), resourceManager, FBO0_W, FBO0_H, resourceManager.mapTextureFormat(GL11.GL_RGBA8));
|
||||
assertEquals(FBO0_H, frameManager.getFbo0Height());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void dropFinalTarget() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
if (frameManager != null) frameManager.destroyFinalTarget();
|
||||
}
|
||||
|
||||
private static void bindFbo0WithSmallViewport() {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glViewport(0, 0, VIEW, VIEW);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private static int createCopyTarget() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, VIEW, VIEW, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void drawTextureOverTarget(int texture) {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.texturedQuad(texture, 1.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialViewportDrawCoversOnlyTheGlViewportRows() {
|
||||
bindFbo0WithSmallViewport();
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, VIEW, VIEW), RED, "inside the viewport");
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, VIEW, VIEW), BLUE, "above the viewport");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexSubImage2DFromAPartialViewportKeepsGlOrientation() {
|
||||
bindFbo0WithSmallViewport();
|
||||
GlsmSdlHeadlessRig.halfQuad(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
|
||||
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
final int copy = createCopyTarget();
|
||||
GLStateManager.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, 0, 0, VIEW, VIEW);
|
||||
|
||||
drawTextureOverTarget(copy);
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, 4, GREEN, "sampled copy bottom");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE - 5, RED, "sampled copy top");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexImage2DAllocatesTheDestinationLevel() {
|
||||
bindFbo0WithSmallViewport();
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
|
||||
final int copy = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, copy);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
assertNull(resourceManager.getTextureMeta(copy), "destination was allocated before the copy");
|
||||
|
||||
GLStateManager.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 0, 0, VIEW, VIEW, 0);
|
||||
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(copy);
|
||||
assertNotNull(meta, "copyTexImage2D did not allocate the destination");
|
||||
assertEquals(VIEW, meta.width());
|
||||
assertEquals(VIEW, meta.height());
|
||||
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final ByteBuffer out = MemoryUtil.memAlloc(VIEW * VIEW * 4);
|
||||
try {
|
||||
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, out);
|
||||
for (int i = 0; i < VIEW * VIEW; i++) {
|
||||
final int idx = i;
|
||||
assertEquals((byte) 0xFF, out.get(i * 4), () -> "R at texel " + idx);
|
||||
assertEquals((byte) 0x00, out.get(i * 4 + 1), () -> "G at texel " + idx);
|
||||
assertEquals((byte) 0x00, out.get(i * 4 + 2), () -> "B at texel " + idx);
|
||||
assertEquals((byte) 0xFF, out.get(i * 4 + 3), () -> "A at texel " + idx);
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlFfpTest {
|
||||
|
||||
private static final int TEXEL = 0xFFFF4020;
|
||||
private static final int LIGHTMAP_SIZE = 16;
|
||||
|
||||
private static int baseTexture;
|
||||
private static int lightmapTexture;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
baseTexture = GlsmSdlHeadlessRig.createSolidTexture(TEXEL);
|
||||
lightmapTexture = createLightmapTexture();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
}
|
||||
|
||||
private static int createLightmapTexture() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, LIGHTMAP_SIZE, LIGHTMAP_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4);
|
||||
try {
|
||||
for (int y = 0; y < LIGHTMAP_SIZE; y++) {
|
||||
for (int x = 0; x < LIGHTMAP_SIZE; x++) {
|
||||
final boolean brightest = (x == 0 && y == 0) || (x == LIGHTMAP_SIZE - 1 && y == LIGHTMAP_SIZE - 1);
|
||||
texels.put((byte) (brightest ? 255 : 0));
|
||||
texels.put((byte) 255);
|
||||
texels.put((byte) (brightest ? 255 : 0));
|
||||
texels.put((byte) 255);
|
||||
}
|
||||
}
|
||||
texels.flip();
|
||||
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, LIGHTMAP_SIZE, LIGHTMAP_SIZE, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void bindBaseTexture() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, baseTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
private static void enableLightmapUnit(boolean vanillaTextureMatrix) {
|
||||
GlsmSdlHeadlessRig.enableLightmapUnit(lightmapTexture, vanillaTextureMatrix);
|
||||
}
|
||||
|
||||
private static void quadUnit0Only() {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, -1.0f, 0.0f, 0.0f, false, (short) 0);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, -1.0f, 1.0f, 0.0f, false, (short) 0);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, 1.0f, 1.0f, 1.0f, false, (short) 0);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, 1.0f, 0.0f, 1.0f, false, (short) 0);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
private static void quadTwoUnits(boolean vanillaBrightness) {
|
||||
final short brightness = (short) (vanillaBrightness ? 240 : 0);
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, -1.0f, 0.0f, 0.0f, true, brightness);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, -1.0f, 1.0f, 0.0f, true, brightness);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, 1.0f, 1.0f, 1.0f, true, brightness);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, 1.0f, 0.0f, 1.0f, true, brightness);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
private static void assertTexelEverywhere(String label) {
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int center = pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
|
||||
final int corner = pixelAt(pixels, SIZE, 2, 2);
|
||||
assertEquals(TEXEL, center, () -> label + ": center " + describe(center));
|
||||
assertEquals(TEXEL, corner, () -> label + ": corner " + describe(corner));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleUnitTexturedQuadShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
quadUnit0Only();
|
||||
assertTexelEverywhere("single unit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoUnitTerrainQuadWithVanillaLightmapMatrixShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
enableLightmapUnit(true);
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
quadTwoUnits(true);
|
||||
assertTexelEverywhere("two units, vanilla lightmap matrix");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoUnitTerrainQuadWithIdentityLightmapMatrixShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
enableLightmapUnit(false);
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
quadTwoUnits(false);
|
||||
assertTexelEverywhere("two units, identity lightmap matrix, zero brightness");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoUnitTerrainQuadThroughDisplayListsShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
enableLightmapUnit(true);
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
|
||||
final int list = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(0.0f, 0.0f, 0.0f);
|
||||
quadTwoUnits(true);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glEndList();
|
||||
|
||||
GLStateManager.glCallList(list);
|
||||
assertTexelEverywhere("two units, glCallList");
|
||||
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
final IntBuffer lists = BufferUtils.createIntBuffer(1);
|
||||
lists.put(0, list);
|
||||
lists.position(0);
|
||||
GLStateManager.glCallLists(lists);
|
||||
assertTexelEverywhere("two units, glCallLists");
|
||||
}
|
||||
|
||||
@Test
|
||||
void litTexturedQuadIsShaded() {
|
||||
bindBaseTexture();
|
||||
enableGuiStandardItemLighting();
|
||||
GLStateManager.glEnable(GL12.GL_RESCALE_NORMAL);
|
||||
quadUnit0Only();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int center = pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
|
||||
final int red = (center >> 16) & 0xFF;
|
||||
assertEquals(255, (center >>> 24) & 0xFF, () -> "lit quad alpha: " + describe(center));
|
||||
assertTrue(red > 100, () -> "lit quad red: " + describe(center));
|
||||
assertTrue(red < ((TEXEL >> 16) & 0xFF), () -> "lit quad is not attenuated: " + describe(center));
|
||||
}
|
||||
|
||||
private static void enableGuiStandardItemLighting() {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glRotatef(-30.0f, 0.0f, 1.0f, 0.0f);
|
||||
GLStateManager.glRotatef(165.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
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);
|
||||
|
||||
final float[] first = normalize(0.2f, 1.0f, -0.7f);
|
||||
final float[] second = normalize(-0.2f, 1.0f, 0.7f);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(first[0], first[1], first[2], 0.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, floats(second[0], second[1], second[2], 0.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.4f, 0.4f, 0.4f, 1.0f));
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
private static float[] normalize(float x, float y, float z) {
|
||||
final float len = (float) Math.sqrt(x * x + y * y + z * z);
|
||||
return new float[]{ x / len, y / len, z / len };
|
||||
}
|
||||
|
||||
private static FloatBuffer floats(float a, float b, float c, float d) {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(4);
|
||||
buf.put(a).put(b).put(c).put(d);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public final class GlsmSdlFrameExtension implements BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
@Override
|
||||
public void beforeEach(ExtensionContext context) {
|
||||
GlsmSdlHeadlessRig.beginFrameAndReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterEach(ExtensionContext context) {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(),
|
||||
() -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
public final class GlsmSdlHeadlessRig {
|
||||
|
||||
public static final int SIZE = 64;
|
||||
|
||||
private static boolean booted;
|
||||
private static int fbo;
|
||||
private static int colorTexture;
|
||||
private static int depthRenderbuffer;
|
||||
|
||||
private GlsmSdlHeadlessRig() {}
|
||||
|
||||
public static void boot() {
|
||||
if (booted) return;
|
||||
SdlTestRig.initSdlVideo();
|
||||
assumeTrue(SDLGPUGate.device().createDevice(), () -> "no SDL GPU device: " + SDLError.SDL_GetError());
|
||||
Reflect.setStatic(SDLGPUGate.class, "engaged", true);
|
||||
|
||||
final RenderBackend backend = BackendManager.RENDER_BACKEND;
|
||||
assumeTrue(backend instanceof SDLGPURenderBackend, () -> "selected backend is " + backend.getName());
|
||||
|
||||
backend.onPostWindowCreate(0L);
|
||||
Reflect.setStaticFinal(GLStateManager.class, "MainThread", Thread.class, Thread.currentThread());
|
||||
|
||||
GLStateManager.initialize(GLSMInitConfig.builder()
|
||||
.displaySize(SIZE, SIZE)
|
||||
.directDrawer(TessellatorStreamingDrawer::drawDirect)
|
||||
.streamingDrawerDestroy(TessellatorStreamingDrawer::destroy)
|
||||
.build());
|
||||
GLStateManager.setRunningSplash(false);
|
||||
GLStateManager.markSplashComplete("glsmSdlHeadlessRig");
|
||||
|
||||
assertTrue(GlsmSdlRedirectAgent.transformedCount() > 0, "redirect agent transformed no classes");
|
||||
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(),
|
||||
() -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
|
||||
|
||||
createTarget();
|
||||
booted = true;
|
||||
}
|
||||
|
||||
private static void createTarget() {
|
||||
beginFrame();
|
||||
colorTexture = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, colorTexture);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
depthRenderbuffer = GLStateManager.glGenRenderbuffers();
|
||||
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthRenderbuffer);
|
||||
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
|
||||
|
||||
fbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, colorTexture, 0);
|
||||
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depthRenderbuffer);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
|
||||
PipelineCache.setSwapchainFormats(new int[]{ colorTargetSdlFormat() });
|
||||
endFrame();
|
||||
}
|
||||
|
||||
private static int colorTargetSdlFormat() {
|
||||
final ResourceManager rm = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
final ResourceManager.TextureMeta meta = rm.getTextureMeta(colorTexture);
|
||||
return meta != null ? meta.sdlFormat() : 0;
|
||||
}
|
||||
|
||||
private static boolean frameOpen;
|
||||
|
||||
public static void beginFrame() {
|
||||
if (frameOpen) return;
|
||||
BackendManager.RENDER_BACKEND.onFrameBegin();
|
||||
frameOpen = true;
|
||||
}
|
||||
|
||||
public static void endFrame() {
|
||||
if (!frameOpen) return;
|
||||
frameOpen = false;
|
||||
BackendManager.RENDER_BACKEND.onFrameEnd();
|
||||
}
|
||||
|
||||
public static void bindTarget() {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
}
|
||||
|
||||
public static void clearTo(float r, float g, float b, float a) {
|
||||
GLStateManager.glClearColor(r, g, b, a);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
public static void beginFrameAndReset() {
|
||||
beginFrame();
|
||||
resetState();
|
||||
bindTarget();
|
||||
clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public static void resetState() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
|
||||
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_FOG);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT0);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT1);
|
||||
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
GLStateManager.glDisable(GL12.GL_RESCALE_NORMAL);
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
}
|
||||
|
||||
public static void terrainRenderState() {
|
||||
GLStateManager.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
|
||||
GLStateManager.glEnable(GL11.GL_FOG);
|
||||
GLStateManager.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR);
|
||||
GLStateManager.glFogf(GL11.GL_FOG_START, 1.0e6f);
|
||||
GLStateManager.glFogf(GL11.GL_FOG_END, 1.0e7f);
|
||||
}
|
||||
|
||||
public static int createSolidTexture(int argb) {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(2 * 2 * 4);
|
||||
try {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
texels.put((byte) ((argb >> 16) & 0xFF));
|
||||
texels.put((byte) ((argb >> 8) & 0xFF));
|
||||
texels.put((byte) (argb & 0xFF));
|
||||
texels.put((byte) ((argb >>> 24) & 0xFF));
|
||||
}
|
||||
texels.flip();
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 2, 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void enableLightmapUnit(int lightmapTexture, boolean vanillaTextureMatrix) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
if (vanillaTextureMatrix) {
|
||||
final float scale = 0.00390625f;
|
||||
GLStateManager.glScalef(scale, scale, scale);
|
||||
GLStateManager.glTranslatef(8.0f, 8.0f, 8.0f);
|
||||
}
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
public static void emitVertex(float x, float y, float u, float v, boolean lightmap, short brightness) {
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glTexCoord2f(u, v);
|
||||
if (lightmap) {
|
||||
GLStateManager.glMultiTexCoord2s(GL13.GL_TEXTURE1, brightness, brightness);
|
||||
}
|
||||
GLStateManager.glVertex3f(x, y, 0.0f);
|
||||
}
|
||||
|
||||
private static void vertex(float x, float y, float r, float g, float b) {
|
||||
GLStateManager.glColor4f(r, g, b, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glVertex3f(x, y, 0.0f);
|
||||
}
|
||||
|
||||
private static void texVertex(float x, float y, float u, float v) {
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glTexCoord2f(u, v);
|
||||
GLStateManager.glVertex3f(x, y, 0.0f);
|
||||
}
|
||||
|
||||
public static void halfQuad(float y0, float y1, float r, float g, float b) {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
vertex(-1.0f, y0, r, g, b);
|
||||
vertex(1.0f, y0, r, g, b);
|
||||
vertex(1.0f, y1, r, g, b);
|
||||
vertex(-1.0f, y1, r, g, b);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
public static void solidQuad(float r, float g, float b) {
|
||||
halfQuad(-1.0f, 1.0f, r, g, b);
|
||||
}
|
||||
|
||||
public static void texturedQuad(int texture, float extent) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
texVertex(-extent, -extent, 0.0f, 0.0f);
|
||||
texVertex(extent, -extent, 1.0f, 0.0f);
|
||||
texVertex(extent, extent, 1.0f, 1.0f);
|
||||
texVertex(-extent, extent, 0.0f, 1.0f);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
public static int[] readTarget() {
|
||||
return readTarget(0, 0, SIZE, SIZE, (byte) 0);
|
||||
}
|
||||
|
||||
public static int[] readTarget(int x, int y, int w, int h) {
|
||||
return readTarget(x, y, w, h, (byte) 0);
|
||||
}
|
||||
|
||||
public static int[] readTarget(int x, int y, int w, int h, byte fill) {
|
||||
endFrame();
|
||||
final ByteBuffer pixels = MemoryUtil.memAlloc(w * h * 4);
|
||||
try {
|
||||
for (int i = 0; i < w * h * 4; i++) pixels.put(i, fill);
|
||||
GLStateManager.glReadPixels(x, y, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
|
||||
final int[] out = new int[w * h];
|
||||
for (int i = 0; i < out.length; i++) {
|
||||
final int r = pixels.get(i * 4) & 0xFF;
|
||||
final int g = pixels.get(i * 4 + 1) & 0xFF;
|
||||
final int b = pixels.get(i * 4 + 2) & 0xFF;
|
||||
final int a = pixels.get(i * 4 + 3) & 0xFF;
|
||||
out[i] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
return out;
|
||||
} finally {
|
||||
MemoryUtil.memFree(pixels);
|
||||
}
|
||||
}
|
||||
|
||||
public static int pixelAt(int[] pixels, int rowStride, int x, int y) {
|
||||
return pixels[y * rowStride + x];
|
||||
}
|
||||
|
||||
public static String describe(int argb) {
|
||||
return String.format("a=%d r=%d g=%d b=%d", (argb >>> 24) & 0xFF, (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
|
||||
}
|
||||
|
||||
public static void assertPixel(int[] pixels, int stride, int x, int y, int expected, String label) {
|
||||
final int got = pixelAt(pixels, stride, x, y);
|
||||
assertEquals(expected, got, () -> label + " at (" + x + "," + y + "): " + describe(got));
|
||||
}
|
||||
|
||||
public static void assertUniform(int[] pixels, int expected, String label) {
|
||||
for (int i = 0; i < pixels.length; i++) {
|
||||
final int got = pixels[i];
|
||||
final int idx = i;
|
||||
assertEquals(expected, got, () -> label + " at index " + idx + ": " + describe(got));
|
||||
}
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlProxyTextureProbeTest {
|
||||
|
||||
private static final int TEXEL = 0xFFFF4020;
|
||||
private static final int LIGHTMAP_SIZE = 16;
|
||||
|
||||
private static final int[] PROXY_TARGETS = {
|
||||
0x8063, 0x8064, 0x8070, 0x851B, 0x8C19, 0x8C1B, 0x84F7, 0x900B, 0x9101, 0x9103
|
||||
};
|
||||
|
||||
private static int baseTexture;
|
||||
private static int lightmapTexture;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
baseTexture = GlsmSdlHeadlessRig.createSolidTexture(TEXEL);
|
||||
lightmapTexture = createLightmapTexture();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
}
|
||||
|
||||
@Test
|
||||
void vanillaMaximumTextureSizeProbeLeavesTheBoundLightmapIntact() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
|
||||
final ResourceManager rm = resourceManager();
|
||||
final long handleBefore = rm.getTextureHandle(lightmapTexture);
|
||||
|
||||
assertEquals(16384, vanillaGetGLMaximumTextureSize());
|
||||
|
||||
final ResourceManager.TextureMeta meta = rm.getTextureMeta(lightmapTexture);
|
||||
assertNotNull(meta, "lightmap meta was dropped by the proxy probe");
|
||||
assertEquals(LIGHTMAP_SIZE, meta.width(), "lightmap width after probe");
|
||||
assertEquals(LIGHTMAP_SIZE, meta.height(), "lightmap height after probe");
|
||||
assertEquals(handleBefore, rm.getTextureHandle(lightmapTexture), "lightmap handle after probe");
|
||||
assertEquals(LIGHTMAP_SIZE, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTargetCallsLeaveTheBoundTextureIntact() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
|
||||
final ResourceManager rm = resourceManager();
|
||||
final long handleBefore = rm.getTextureHandle(lightmapTexture);
|
||||
final ResourceManager.TextureMeta before = rm.getTextureMeta(lightmapTexture);
|
||||
assertNotNull(before, "lightmap meta missing before the proxy calls");
|
||||
|
||||
for (int target : PROXY_TARGETS) {
|
||||
final String where = "0x" + Integer.toHexString(target);
|
||||
GLStateManager.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameterf(target, GL12.GL_TEXTURE_MIN_LOD, 4.0f);
|
||||
GLStateManager.glTexSubImage2D(target, 0, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
GLStateManager.glCopyTexSubImage2D(target, 0, 0, 0, 0, 0, 1, 1);
|
||||
GLStateManager.glCopyTexImage2D(target, 0, GL11.GL_RGBA, 0, 0, 4, 4, 0);
|
||||
GLStateManager.glGenerateMipmap(target);
|
||||
|
||||
final ResourceManager.TextureMeta after = rm.getTextureMeta(lightmapTexture);
|
||||
assertNotNull(after, where);
|
||||
assertEquals(before.width(), after.width(), where);
|
||||
assertEquals(before.height(), after.height(), where);
|
||||
assertEquals(before.levels(), after.levels(), where);
|
||||
assertEquals(before.glFormat(), after.glFormat(), where);
|
||||
assertEquals(handleBefore, rm.getTextureHandle(lightmapTexture), where);
|
||||
}
|
||||
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyProbeDuringTerrainDrawStillShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
enableLightmapUnit();
|
||||
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
assertEquals(16384, vanillaGetGLMaximumTextureSize());
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
quadTwoUnits();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int center = pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
|
||||
final int corner = pixelAt(pixels, SIZE, 2, 2);
|
||||
assertEquals(TEXEL, center, () -> "center " + describe(center));
|
||||
assertEquals(TEXEL, corner, () -> "corner " + describe(corner));
|
||||
}
|
||||
|
||||
private static int vanillaGetGLMaximumTextureSize() {
|
||||
for (int i = 16384; i > 0; i >>= 1) {
|
||||
GLStateManager.glTexImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, i, i, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final int j = GLStateManager.glGetTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
|
||||
if (j != 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static ResourceManager resourceManager() {
|
||||
return Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
}
|
||||
|
||||
private static int createLightmapTexture() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, LIGHTMAP_SIZE, LIGHTMAP_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4);
|
||||
try {
|
||||
for (int i = 0; i < LIGHTMAP_SIZE * LIGHTMAP_SIZE; i++) {
|
||||
texels.put((byte) 255).put((byte) 255).put((byte) 255).put((byte) 255);
|
||||
}
|
||||
texels.flip();
|
||||
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, LIGHTMAP_SIZE, LIGHTMAP_SIZE, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void bindBaseTexture() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, baseTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
private static void enableLightmapUnit() {
|
||||
GlsmSdlHeadlessRig.enableLightmapUnit(lightmapTexture, true);
|
||||
}
|
||||
|
||||
private static void quadTwoUnits() {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, -1.0f, 0.0f, 0.0f, true, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, -1.0f, 1.0f, 0.0f, true, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, 1.0f, 1.0f, 1.0f, true, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, 1.0f, 0.0f, 1.0f, true, (short) 240);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
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.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlVanillaTextureTest {
|
||||
|
||||
private static final int GL_TEXTURE_MAX_ANISOTROPY_EXT = 34046;
|
||||
|
||||
private static final int ATLAS_SIZE = 64;
|
||||
private static final int ATLAS_MIPMAP_LEVELS = 4;
|
||||
private static final int[] LEVEL_COLORS = {
|
||||
0xFFFF4020,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFF20C080,
|
||||
0xFFFFFFFF,
|
||||
};
|
||||
|
||||
private static final int LIGHTMAP_SIZE = 16;
|
||||
private static final int CLEAR_COLOR = 0xFF000000;
|
||||
|
||||
private static int atlasTexture;
|
||||
private static int lightmapTexture;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
atlasTexture = createMipmappedAtlas();
|
||||
lightmapTexture = allocateDynamicLightmap();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
}
|
||||
|
||||
private static int createMipmappedAtlas() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
allocateTextureImpl(id, ATLAS_MIPMAP_LEVELS, ATLAS_SIZE, ATLAS_SIZE, 1.0f);
|
||||
for (int level = 0; level <= ATLAS_MIPMAP_LEVELS; level++) {
|
||||
uploadTextureSub(level, LEVEL_COLORS[level], ATLAS_SIZE >> level, ATLAS_SIZE >> level, true);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private static int allocateDynamicLightmap() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
allocateTextureImpl(id, 0, LIGHTMAP_SIZE, LIGHTMAP_SIZE, 1.0f);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void allocateTextureImpl(int id, int maxLevel, int width, int height, float aniso) {
|
||||
GLStateManager.glDeleteTextures(id);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
|
||||
|
||||
if (maxLevel > 0) {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, maxLevel);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, 0.0f);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, (float) maxLevel);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0f);
|
||||
}
|
||||
|
||||
for (int level = 0; level <= maxLevel; level++) {
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA, width >> level, height >> level, 0,
|
||||
GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, (IntBuffer) null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadTextureSub(int level, int argb, int width, int height, boolean mipmapped) {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
|
||||
mipmapped ? GL11.GL_NEAREST_MIPMAP_LINEAR : GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
|
||||
|
||||
final IntBuffer data = BufferUtils.createIntBuffer(width * height);
|
||||
for (int i = 0; i < width * height; i++) {
|
||||
data.put(i, argb);
|
||||
}
|
||||
data.position(0);
|
||||
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, level, 0, 0, width, height,
|
||||
GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, data);
|
||||
}
|
||||
|
||||
private static void uploadLightmapFrame(int argb) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
uploadTextureSub(0, argb, LIGHTMAP_SIZE, LIGHTMAP_SIZE, false);
|
||||
}
|
||||
|
||||
private static void bindAtlasUnit0() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, atlasTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
private static void enableLightmap() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
final float scale = 0.00390625f;
|
||||
GLStateManager.glScalef(scale, scale, scale);
|
||||
GLStateManager.glTranslatef(8.0f, 8.0f, 8.0f);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
private static void quad(float extent, boolean lightmap, boolean counterClockwise) {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
if (counterClockwise) {
|
||||
GlsmSdlHeadlessRig.emitVertex(-extent, -extent, 0.0f, 0.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(extent, -extent, 1.0f, 0.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(extent, extent, 1.0f, 1.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(-extent, extent, 0.0f, 1.0f, lightmap, (short) 240);
|
||||
} else {
|
||||
GlsmSdlHeadlessRig.emitVertex(-extent, extent, 0.0f, 1.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(extent, extent, 1.0f, 1.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(extent, -extent, 1.0f, 0.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(-extent, -extent, 0.0f, 0.0f, lightmap, (short) 240);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
private static int center() {
|
||||
return pixelAt(GlsmSdlHeadlessRig.readTarget(), SIZE, SIZE / 2, SIZE / 2);
|
||||
}
|
||||
|
||||
private static void assertColorNear(int expected, int actual, int tolerance, String label) {
|
||||
assertEquals(255, (actual >>> 24) & 0xFF, () -> label + ": alpha " + describe(actual));
|
||||
for (int shift = 0; shift <= 16; shift += 8) {
|
||||
final int e = (expected >> shift) & 0xFF;
|
||||
final int a = (actual >> shift) & 0xFF;
|
||||
assertTrue(Math.abs(e - a) <= tolerance,
|
||||
() -> label + ": expected " + describe(expected) + " got " + describe(actual));
|
||||
}
|
||||
}
|
||||
|
||||
private static int modulate(int a, int b) {
|
||||
int out = 0xFF000000;
|
||||
for (int shift = 0; shift <= 16; shift += 8) {
|
||||
final int product = Math.round((((a >> shift) & 0xFF) / 255.0f) * (((b >> shift) & 0xFF) / 255.0f) * 255.0f);
|
||||
out |= product << shift;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Test
|
||||
void mipmappedAtlasMagnifiedShowsLevelZero() {
|
||||
bindAtlasUnit0();
|
||||
quad(1.0f, false, true);
|
||||
|
||||
final int actual = center();
|
||||
assertColorNear(LEVEL_COLORS[0], actual, 1, "magnified level 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mipmappedAtlasMinifiedShowsALowerLevel() {
|
||||
bindAtlasUnit0();
|
||||
quad(0.125f, false, true);
|
||||
|
||||
final int actual = center();
|
||||
assertEquals(255, (actual >>> 24) & 0xFF, () -> "minified alpha: " + describe(actual));
|
||||
assertNotEquals(CLEAR_COLOR, actual, () -> "minified is the clear color: " + describe(actual));
|
||||
assertNotEquals(LEVEL_COLORS[0], actual, () -> "minified is level 0: " + describe(actual));
|
||||
assertColorNear(LEVEL_COLORS[3], actual, 1, "minified");
|
||||
}
|
||||
|
||||
@Test
|
||||
void perFrameDynamicLightmapModulatesTheAtlas() {
|
||||
final int[] frames = { 0xFFFFFFFF, 0xFFFF8040, 0xFF40FF80 };
|
||||
for (int frame = 0; frame < frames.length; frame++) {
|
||||
if (frame > 0) GlsmSdlHeadlessRig.beginFrameAndReset();
|
||||
|
||||
uploadLightmapFrame(frames[frame]);
|
||||
bindAtlasUnit0();
|
||||
enableLightmap();
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
quad(1.0f, true, true);
|
||||
|
||||
final int expected = modulate(LEVEL_COLORS[0], frames[frame]);
|
||||
final int actual = center();
|
||||
assertColorNear(expected, actual, 2, "dynamic lightmap frame " + frame);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushAttribWrappedCulledDrawKeepsFrontFace() {
|
||||
uploadLightmapFrame(0xFFFFFFFF);
|
||||
bindAtlasUnit0();
|
||||
enableLightmap();
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
quad(1.0f, true, true);
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
final int actual = center();
|
||||
assertColorNear(LEVEL_COLORS[0], actual, 2, "front-face winding kept");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushAttribWrappedCulledDrawDropsBackFace() {
|
||||
uploadLightmapFrame(0xFFFFFFFF);
|
||||
bindAtlasUnit0();
|
||||
enableLightmap();
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
quad(1.0f, true, false);
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
final int actual = center();
|
||||
assertEquals(CLEAR_COLOR, actual, () -> "back-face winding was not culled: " + describe(actual));
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayListWithBindingsOutsideShowsTheModulatedTexel() {
|
||||
final int list = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
quad(1.0f, true, true);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
uploadLightmapFrame(0xFFFF8040);
|
||||
bindAtlasUnit0();
|
||||
enableLightmap();
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(0.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glCallList(list);
|
||||
GLStateManager.glPopMatrix();
|
||||
|
||||
final int expected = modulate(LEVEL_COLORS[0], 0xFFFF8040);
|
||||
final int actual = center();
|
||||
assertColorNear(expected, actual, 2, "display list with bindings outside");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class FFPTraceTest {
|
||||
|
||||
@Test
|
||||
void uboFloatReadIgnoresATruncatedLimit() {
|
||||
final ByteBuffer buf = MemoryUtil.memAlloc(64);
|
||||
try {
|
||||
for (int i = 0; i < 16; i++) buf.putFloat(i * 4, i + 0.5f);
|
||||
buf.position(0).limit(4);
|
||||
assertEquals(0.5f, readUboFloat(buf, 0));
|
||||
assertEquals(12.5f, readUboFloat(buf, 48));
|
||||
assertEquals(15.5f, readUboFloat(buf, 60));
|
||||
assertEquals(4, buf.limit());
|
||||
} finally {
|
||||
MemoryUtil.memFree(buf);
|
||||
}
|
||||
}
|
||||
|
||||
private static float readUboFloat(ByteBuffer buf, int at) {
|
||||
return Reflect.invokeStatic(FFPTrace.class, "floatIgnoringLimit", new Class<?>[] { ByteBuffer.class, int.class }, buf, at);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class CopyRectClipTest {
|
||||
|
||||
static Stream<Arguments> cases() {
|
||||
return Stream.of(
|
||||
Arguments.of("insideBothImages", 5, 9, 3, 2, 7, 4, 64, 64, 64, 64, false, 5, 9, 3, 2, 7, 4),
|
||||
Arguments.of("negativeSourceOrigin", -3, -2, 4, 6, 10, 8, 64, 96, 64, 64, false, 0, 0, 7, 8, 7, 6),
|
||||
Arguments.of("negativeDestinationOrigin", 10, 10, -4, -6, 20, 20, 64, 64, 64, 64, false, 14, 16, 0, 0, 16, 14),
|
||||
Arguments.of("farSourceEdge", 60, 60, 0, 0, 10, 10, 64, 64, 64, 64, false, 60, 60, 0, 0, 4, 4),
|
||||
Arguments.of("farDestinationEdge", 0, 0, 28, 30, 16, 16, 64, 64, 32, 32, false, 0, 0, 28, 30, 4, 2),
|
||||
Arguments.of("oversizeBothImages", 61, 93, 62, 61, 10, 9, 64, 96, 64, 64, false, 61, 93, 62, 61, 2, 3),
|
||||
Arguments.of("oversizeNearFarEdge", 60, 93, 61, 62, 10, 9, 64, 96, 64, 64, false, 60, 93, 61, 62, 3, 2),
|
||||
Arguments.of("pastSourceEdge", 64, 0, 0, 0, 8, 8, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("beforeSourceOrigin", -20, 0, 0, 0, 8, 8, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("pastDestinationEdge", 0, 0, 32, 0, 8, 8, 64, 64, 32, 32, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("zeroWidth", 0, 0, 0, 0, 0, 8, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("zeroHeight", 0, 0, 0, 0, 8, 0, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("negativeWidth", 0, 0, 0, 0, -4, 8, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("unknownDestinationExtents", 0, 0, 200, 200, 8, 8, 64, 64, 0, 0, false, 0, 0, 200, 200, 8, 8),
|
||||
Arguments.of("unknownSourceExtents", 200, 200, 0, 0, 8, 8, 0, 0, 64, 64, false, 200, 200, 0, 0, 8, 8),
|
||||
Arguments.of("mipLevelSizedDestination", 0, 0, 0, 0, 64, 64, 64, 64, 32, 32, false, 0, 0, 0, 0, 32, 32));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("cases")
|
||||
void clipsAsExpected(String label, int srcX, int srcY, int dstX, int dstY, int width, int height,
|
||||
int srcW, int srcH, int dstW, int dstH, boolean expectEmpty,
|
||||
int expectedSrcX, int expectedSrcY, int expectedDstX, int expectedDstY, int expectedW, int expectedH) {
|
||||
final long clip = CopyRectClip.clipCopyRect(srcX, srcY, dstX, dstY, width, height, srcW, srcH, dstW, dstH);
|
||||
if (expectEmpty) {
|
||||
assertEquals(CopyRectClip.EMPTY, clip, label);
|
||||
return;
|
||||
}
|
||||
assertEquals(expectedSrcX, CopyRectClip.srcX(clip), label + " srcX");
|
||||
assertEquals(expectedSrcY, CopyRectClip.srcY(clip), label + " srcY");
|
||||
assertEquals(expectedDstX, CopyRectClip.dstX(clip, srcX, dstX), label + " dstX");
|
||||
assertEquals(expectedDstY, CopyRectClip.dstY(clip, srcY, dstY), label + " dstY");
|
||||
assertEquals(expectedW, CopyRectClip.width(clip), label + " width");
|
||||
assertEquals(expectedH, CopyRectClip.height(clip), label + " height");
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
@Timeout(120)
|
||||
class CopyTexSubImageFromFbo0GpuTest {
|
||||
|
||||
private static final int SIZE = 64;
|
||||
private static final int TALL = 96;
|
||||
private static final int ROW_BYTES = SIZE * 4;
|
||||
|
||||
private static Device device;
|
||||
private static FrameManager frameManager;
|
||||
private static ResourceManager resourceManager;
|
||||
private static TextureOps textureOps;
|
||||
private static int nextGlId = 1000;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
final SdlTestRig rig = SdlTestRig.acquireRealDevice();
|
||||
device = rig.device;
|
||||
frameManager = rig.frameManager;
|
||||
resourceManager = rig.resourceManager;
|
||||
textureOps = new TextureOps(device, frameManager, resourceManager, new FBOClearTracker(frameManager, resourceManager, null));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
if (frameManager != null && frameManager.isFrameActive()) frameManager.endFrame();
|
||||
if (frameManager != null) frameManager.destroyFinalTarget();
|
||||
SdlTestRig.releaseRealDevice();
|
||||
}
|
||||
|
||||
private static byte srcRed(int col) {
|
||||
return (byte) (16 + col * 3);
|
||||
}
|
||||
|
||||
private static byte srcGreen(int row) {
|
||||
return (byte) (7 + row * 3);
|
||||
}
|
||||
|
||||
private static ByteBuffer fbo0Pattern(int height) {
|
||||
final ByteBuffer buf = MemoryUtil.memAlloc(SIZE * height * 4);
|
||||
for (int row = 0; row < height; row++) {
|
||||
for (int col = 0; col < SIZE; col++) {
|
||||
final int i = row * ROW_BYTES + col * 4;
|
||||
buf.put(i, srcRed(col));
|
||||
buf.put(i + 1, srcGreen(row));
|
||||
buf.put(i + 2, (byte) 0x40);
|
||||
buf.put(i + 3, (byte) 0xFF);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static final byte[] SENTINEL = { 0x11, 0x22, 0x33, 0x44 };
|
||||
|
||||
private static ByteBuffer sentinelPattern() {
|
||||
final ByteBuffer buf = MemoryUtil.memAlloc(SIZE * SIZE * 4);
|
||||
for (int i = 0; i < SIZE * SIZE; i++) {
|
||||
for (int c = 0; c < 4; c++) buf.put(i * 4 + c, SENTINEL[c]);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static void upload(long texture, ByteBuffer data, int width, int height) {
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
assertNotEquals(0L, cp, "no copy pass");
|
||||
resourceManager.uploadToTexture(cp, data, texture, 0, 0, width, height, 0);
|
||||
resourceManager.flushBatchedUploads(cp);
|
||||
frameManager.endCopyPassIfActive();
|
||||
}
|
||||
|
||||
private static int createDest() {
|
||||
final int glId = nextGlId++;
|
||||
final long handle = resourceManager.createTexture(glId, GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, SIZE, SIZE, 1, 1);
|
||||
assertNotEquals(0L, handle, "dest texture creation failed");
|
||||
return glId;
|
||||
}
|
||||
|
||||
private static int createSentinelDest() {
|
||||
final int destGlId = createDest();
|
||||
final ByteBuffer sentinel = sentinelPattern();
|
||||
try {
|
||||
upload(resourceManager.getTextureHandle(destGlId), sentinel, SIZE, SIZE);
|
||||
} finally {
|
||||
MemoryUtil.memFree(sentinel);
|
||||
}
|
||||
return destGlId;
|
||||
}
|
||||
|
||||
private static void prepareFbo0(int height) {
|
||||
frameManager.destroyFinalTarget();
|
||||
frameManager.finalTarget().create(device, resourceManager, SIZE, height, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
assertEquals(SIZE, frameManager.getFbo0Width());
|
||||
assertEquals(height, frameManager.getFbo0Height());
|
||||
final ByteBuffer pattern = fbo0Pattern(height);
|
||||
try {
|
||||
upload(frameManager.getFbo0Texture(), pattern, SIZE, height);
|
||||
} finally {
|
||||
MemoryUtil.memFree(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer download(int destGlId) {
|
||||
final ByteBuffer out = MemoryUtil.memAlloc(SIZE * SIZE * 4);
|
||||
textureOps.readbackTexture(resourceManager.getTextureHandle(destGlId), 0, 0, SIZE, SIZE, 0, out);
|
||||
out.rewind();
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void assertPixel(ByteBuffer got, int col, int row, byte r, byte g, byte b, byte a, String what) {
|
||||
final int i = row * ROW_BYTES + col * 4;
|
||||
assertEquals(r, got.get(i), what + " R at col=" + col + " row=" + row);
|
||||
assertEquals(g, got.get(i + 1), what + " G at col=" + col + " row=" + row);
|
||||
assertEquals(b, got.get(i + 2), what + " B at col=" + col + " row=" + row);
|
||||
assertEquals(a, got.get(i + 3), what + " A at col=" + col + " row=" + row);
|
||||
}
|
||||
|
||||
private static void assertCopiedRegion(ByteBuffer got, int fbo0Height, int dx, int dy, int sx, int sy, int w, int h, String what) {
|
||||
for (int row = 0; row < SIZE; row++) {
|
||||
for (int col = 0; col < SIZE; col++) {
|
||||
final boolean inside = col >= dx && col < dx + w && row >= dy && row < dy + h;
|
||||
if (inside) {
|
||||
final int srcCol = sx + (col - dx);
|
||||
final int srcStorageRow = fbo0Height - 1 - (sy + (row - dy));
|
||||
assertPixel(got, col, row, srcRed(srcCol), srcGreen(srcStorageRow), (byte) 0x40, (byte) 0xFF, what);
|
||||
} else {
|
||||
assertPixel(got, col, row, SENTINEL[0], SENTINEL[1], SENTINEL[2], SENTINEL[3], "outside " + what);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fullCopyFromDefaultFramebufferIsFlippedIntoTheTexture() {
|
||||
frameManager.beginFrame();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
prepareFbo0(SIZE);
|
||||
|
||||
final int destGlId = createSentinelDest();
|
||||
|
||||
final ContextState st = new ContextState();
|
||||
st.boundReadFboId = 0;
|
||||
textureOps.copyTexSubImageImpl(st, destGlId, 0, 0, 0, 0, 0, SIZE, SIZE);
|
||||
|
||||
final ByteBuffer got = download(destGlId);
|
||||
try {
|
||||
for (int row = 0; row < SIZE; row++) {
|
||||
for (int col = 0; col < SIZE; col++) {
|
||||
assertPixel(got, col, row, srcRed(col), srcGreen(SIZE - 1 - row), (byte) 0x40, (byte) 0xFF, "full copy");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(got);
|
||||
}
|
||||
frameManager.endFrame();
|
||||
}
|
||||
|
||||
@Test
|
||||
void subRectCopyFromATallerDefaultFramebufferUsesTheFullHeightBasis() {
|
||||
frameManager.beginFrame();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
prepareFbo0(TALL);
|
||||
|
||||
final int destGlId = createSentinelDest();
|
||||
|
||||
final ContextState st = new ContextState();
|
||||
st.boundReadFboId = 0;
|
||||
textureOps.copyTexSubImageImpl(st, destGlId, 0, 5, 9, 3, 2, 7, 4);
|
||||
|
||||
final ByteBuffer got = download(destGlId);
|
||||
try {
|
||||
assertCopiedRegion(got, TALL, 5, 9, 3, 2, 7, 4, "tall sub-rect copy");
|
||||
} finally {
|
||||
MemoryUtil.memFree(got);
|
||||
}
|
||||
frameManager.endFrame();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -51,7 +51,7 @@ class ReadbackSubmitOrderGpuTest {
|
||||
}
|
||||
|
||||
assertEquals(1, f.midFrameSubmitsThisFrame, "the frame command buffer must be submitted before the download buffer, or the readback sees stale contents");
|
||||
assertNotEquals(frameCb, f.commandBuffer, "the frame must continue on a freshly acquired command buffer");
|
||||
assertNotEquals(0L, f.commandBuffer, "the frame must continue on a freshly acquired command buffer");
|
||||
|
||||
fm.endFrame();
|
||||
}
|
||||
|
||||
+70
-3
@@ -5,9 +5,12 @@ import org.lwjgl.util.spvc.Spvc;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class AttribLocationPatchTest {
|
||||
|
||||
@@ -23,10 +26,14 @@ class AttribLocationPatchTest {
|
||||
};
|
||||
|
||||
private static ShaderManager.StageReflection reflectionWithInputs(ByteBuffer spirv) {
|
||||
final ShaderManager.VsInput[] inputs = new ShaderManager.VsInput[NAMES.length];
|
||||
for (int i = 0; i < NAMES.length; i++) {
|
||||
return reflectionWithInputs(spirv, NAMES);
|
||||
}
|
||||
|
||||
private static ShaderManager.StageReflection reflectionWithInputs(ByteBuffer spirv, String[] names) {
|
||||
final ShaderManager.VsInput[] inputs = new ShaderManager.VsInput[names.length];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
spirv.asIntBuffer().put(i, i);
|
||||
inputs[i] = new ShaderManager.VsInput(NAMES[i], i, i, VEC_SIZES[i], BASE_TYPES[i]);
|
||||
inputs[i] = new ShaderManager.VsInput(names[i], i, i, i < VEC_SIZES.length ? VEC_SIZES[i] : 4, i < BASE_TYPES.length ? BASE_TYPES[i] : Spvc.SPVC_BASETYPE_FP32);
|
||||
}
|
||||
final ShaderManager.StageReflection e = ShaderManager.StageReflection.EMPTY;
|
||||
return new ShaderManager.StageReflection(e.counts(), e.samplerNames(), e.extraUniformNames(),
|
||||
@@ -71,6 +78,66 @@ class AttribLocationPatchTest {
|
||||
assertEquals(Spvc.SPVC_BASETYPE_INT32, prog.vertexInputBaseType[5]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnboundInputMovesOffALocationABindingClaimed() {
|
||||
final String[] names = {
|
||||
"a_PosId", "a_Color", "a_TexCoord", "a_LightCoord", "a_RdhFactor",
|
||||
"mc_midTexCoord", "at_tangent", "iris_Normal", "mc_Entity", "at_midBlock"
|
||||
};
|
||||
final ShaderManager.ProgramObject prog = new ShaderManager.ProgramObject();
|
||||
prog.vertexSpirv = ByteBuffer.allocateDirect(names.length * Integer.BYTES).order(ByteOrder.nativeOrder());
|
||||
final ShaderManager.StageReflection vs = reflectionWithInputs(prog.vertexSpirv, names);
|
||||
|
||||
int loc = 0;
|
||||
for (String name : names) {
|
||||
if (!name.equals("a_RdhFactor")) prog.attribLocationBindings.put(name, loc++);
|
||||
}
|
||||
|
||||
ShaderManager.applyAttribLocationsAndInputMask(prog, vs);
|
||||
|
||||
assertEquals(4, prog.resolvedAttribLocations.getInt("mc_midTexCoord"), "the binding is authoritative");
|
||||
assertEquals(9, prog.resolvedAttribLocations.getInt("a_RdhFactor"), "the unbound input takes the lowest free slot");
|
||||
assertEquals(0x3ff, prog.vertexInputMask, "ten inputs at 0..9");
|
||||
|
||||
final Set<Integer> seen = new HashSet<>();
|
||||
for (String name : names) {
|
||||
final int resolved = prog.resolvedAttribLocations.getInt(name);
|
||||
assertTrue(seen.add(resolved), () -> "duplicate location " + resolved + " for " + name);
|
||||
assertEquals(resolved, prog.vertexSpirv.asIntBuffer().get(indexOf(names, name)), () -> "SPIR-V Location decoration for " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void aBoundNameAbsentFromTheShaderKeepsItsLocationReserved() {
|
||||
final String[] bound = {
|
||||
"a_PosId", "a_Color", "a_TexCoord", "a_LightCoord", "mc_midTexCoord",
|
||||
"at_tangent", "iris_Normal", "mc_Entity", "at_midBlock"
|
||||
};
|
||||
final String[] declared = {
|
||||
"a_PosId", "a_Color", "a_TexCoord", "a_LightCoord", "a_RdhFactor",
|
||||
"mc_midTexCoord", "iris_Normal", "mc_Entity", "at_midBlock"
|
||||
};
|
||||
final ShaderManager.ProgramObject prog = new ShaderManager.ProgramObject();
|
||||
prog.vertexSpirv = ByteBuffer.allocateDirect(declared.length * Integer.BYTES).order(ByteOrder.nativeOrder());
|
||||
final ShaderManager.StageReflection vs = reflectionWithInputs(prog.vertexSpirv, declared);
|
||||
|
||||
for (int i = 0; i < bound.length; i++) {
|
||||
prog.attribLocationBindings.put(bound[i], i);
|
||||
}
|
||||
|
||||
ShaderManager.applyAttribLocationsAndInputMask(prog, vs);
|
||||
|
||||
assertEquals(9, prog.resolvedAttribLocations.getInt("a_RdhFactor"), "location 5 stays reserved for the optimized-out at_tangent");
|
||||
assertEquals(0x3df, prog.vertexInputMask, "nine inputs at 0..4 and 6..9");
|
||||
}
|
||||
|
||||
private static int indexOf(String[] names, String name) {
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
if (names[i].equals(name)) return i;
|
||||
}
|
||||
throw new IllegalArgumentException(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unboundInputsKeepTheirOriginalLocation() {
|
||||
final ShaderManager.ProgramObject prog = new ShaderManager.ProgramObject();
|
||||
|
||||
@@ -251,6 +251,7 @@ public class BatchingFontRenderer {
|
||||
int packedLight;
|
||||
float normalX, normalY, normalZ;
|
||||
int blockEntityId;
|
||||
boolean depthTest;
|
||||
}
|
||||
|
||||
// 16-bit EBO index range
|
||||
@@ -516,6 +517,7 @@ public class BatchingFontRenderer {
|
||||
segment.normalY = normal.y;
|
||||
segment.normalZ = normal.z;
|
||||
segment.blockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
segment.depthTest = GLStateManager.getDepthTest().isEnabled();
|
||||
batchSegments.add(segment);
|
||||
batchSealedEnd = end;
|
||||
}
|
||||
@@ -603,6 +605,7 @@ public class BatchingFontRenderer {
|
||||
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
|
||||
GLStateManager.getEffectiveBlendState(deferredBlendStateBefore);
|
||||
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
final boolean depthTestBefore = GLStateManager.getDepthTest().isEnabled();
|
||||
final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
final int prevBlockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
boolean textureChanged = false;
|
||||
@@ -619,6 +622,7 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
try {
|
||||
boolean curDepthTest = depthTestBefore;
|
||||
for (final TextSegment segment : deferredSegments) {
|
||||
final BatchingFontRenderer owner = segment.owner;
|
||||
if (segment.cmdStart == segment.cmdEnd) continue;
|
||||
@@ -626,6 +630,11 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.setModelViewMatrix(segment.modelView);
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
|
||||
|
||||
if (segment.depthTest != curDepthTest) {
|
||||
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
|
||||
curDepthTest = segment.depthTest;
|
||||
}
|
||||
|
||||
final FontDrawCmd[] cmdsData = owner.batchCommands.elements();
|
||||
Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
@@ -637,7 +646,7 @@ public class BatchingFontRenderer {
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
|
||||
GbufferPrograms.endTranslucencyDeclaration(prevTranslucency);
|
||||
|
||||
GLStateManager.glDepthMask(depthMaskBefore);
|
||||
restoreDepth(depthTestBefore, depthMaskBefore);
|
||||
if (isTextureEnabledBefore) {
|
||||
GLStateManager.enableTexture();
|
||||
} else {
|
||||
@@ -685,12 +694,17 @@ public class BatchingFontRenderer {
|
||||
try {
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
boolean curDepthTest = depthTestBefore;
|
||||
for (final TextSegment segment : deferredSegments) {
|
||||
mvpBuf.clear();
|
||||
segment.mvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, mvpBuf);
|
||||
uploadLightmap(segment.owner.lightmapLocation, segment.lightmapActive, segment.lightmapU,
|
||||
segment.lightmapV, segment.lightmapTexture);
|
||||
if (segment.depthTest != curDepthTest) {
|
||||
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
|
||||
curDepthTest = segment.depthTest;
|
||||
}
|
||||
drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,27 @@ import com.google.common.collect.HashMultiset;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ResourceAccessor;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.resources.DefaultResourcePack;
|
||||
import net.minecraft.client.resources.SimpleReloadableResourceManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import cpw.mods.fml.common.Loader;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FontStrategist {
|
||||
@@ -29,42 +32,46 @@ public class FontStrategist {
|
||||
@Getter
|
||||
private static final Font[] availableFonts;
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
private static final boolean isSafeToUseAwtEnvironmentData = checkIsSafeToUseAwtEnvironmentData();
|
||||
|
||||
static {
|
||||
HashMap<String, Font> fontSet = new HashMap<>();
|
||||
|
||||
if (GraphicsEnvironment.isHeadless()) {
|
||||
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Only bundled fonts will be available. This is likely a MacOS issue.");
|
||||
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
|
||||
final Font[] availableFontsDirty;
|
||||
final HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
|
||||
|
||||
if (isSafeToUseAwtEnvironmentData) {
|
||||
availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
|
||||
} else {
|
||||
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
|
||||
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
|
||||
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
|
||||
|
||||
for (Font font : availableFontsDirty) {
|
||||
String fontName = font.getFontName();
|
||||
if (fontSet.containsKey(fontName)) {
|
||||
duplicates.add(fontName);
|
||||
} else {
|
||||
fontSet.put(fontName, font);
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicates.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder(duplicates.size() + " duplicate font(s) found in the list reported by Java: ");
|
||||
for (Iterator<String> iter = duplicates.stream().distinct().iterator(); iter.hasNext(); ) {
|
||||
String dupe = iter.next();
|
||||
sb.append(duplicates.count(dupe)).append("x ").append(dupe);
|
||||
if (iter.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(". Some fonts may be missing from the font selection menu.");
|
||||
LOGGER.warn(sb.toString());
|
||||
}
|
||||
|
||||
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, fontSet.size());
|
||||
LOGGER.warn("System font enumeration is disabled because AWT API is unsafe in this environment. Update lwjgl3ify!");
|
||||
availableFontsDirty = new Font[0];
|
||||
}
|
||||
|
||||
for (Font font : availableFontsDirty) {
|
||||
String fontName = font.getFontName();
|
||||
if (fontSet.containsKey(fontName)) {
|
||||
duplicates.add(fontName);
|
||||
} else {
|
||||
fontSet.put(fontName, font);
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicates.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder(duplicates.size() + " duplicate font(s) found in the list reported by Java: ");
|
||||
for (Iterator<String> iter = duplicates.stream().distinct().iterator(); iter.hasNext(); ) {
|
||||
String dupe = iter.next();
|
||||
sb.append(duplicates.count(dupe)).append("x ").append(dupe);
|
||||
if (iter.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(". Some fonts may be missing from the font selection menu.");
|
||||
LOGGER.warn(sb.toString());
|
||||
}
|
||||
|
||||
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, fontSet.size());
|
||||
|
||||
loadBundledFonts(fontSet);
|
||||
|
||||
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
|
||||
@@ -76,22 +83,46 @@ public class FontStrategist {
|
||||
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
|
||||
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
|
||||
defaultResourcePacks.add(fontResourcePack);
|
||||
|
||||
((ResourceAccessor) mc).angelica$getDefaultResourcePacks().add(fontResourcePack);
|
||||
((SimpleReloadableResourceManager) mc.getResourceManager()).reloadResourcePack(fontResourcePack);
|
||||
}
|
||||
|
||||
private static boolean checkIsSafeToUseAwtEnvironmentData() {
|
||||
if (!GraphicsEnvironment.isHeadless()) return true;
|
||||
|
||||
try (InputStream stream = ClassLoader.getSystemResourceAsStream(
|
||||
"META-INF/lwjgl3ify-forgePatches-version.txt")) {
|
||||
|
||||
if (stream == null) {
|
||||
LOGGER.warn("Failed to identify lwjgl3ify version");
|
||||
return false;
|
||||
}
|
||||
|
||||
String version;
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(stream, StandardCharsets.UTF_8))) {
|
||||
version = reader.readLine();
|
||||
}
|
||||
|
||||
// See https://github.com/GTNewHorizons/RetroFuturaBootstrap/commit/6d39c56ba0f1496b5599a3660d1578df3b28ff17
|
||||
// This change corresponds to RFB 1.0.14, used since lwjgl3ify 3.0.8
|
||||
return new DefaultArtifactVersion(version)
|
||||
.compareTo(new DefaultArtifactVersion("3.0.8")) >= 0;
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to identify lwjgl3ify version", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load .ttf/.otf shipped with the pack so customFontName* can point at a font that isn't installed
|
||||
// on the system. fontfiles/ is SmoothFont's folder, kept so packs built for it work unchanged.
|
||||
private static void loadBundledFonts(HashMap<String, Font> fontSet) {
|
||||
File configDir = Loader.instance().getConfigDir();
|
||||
if (configDir == null) {
|
||||
LOGGER.warn("Loader.instance().getConfigDir() returned null. Bundled fonts will not be loaded.");
|
||||
return;
|
||||
}
|
||||
File parent = configDir.getParentFile();
|
||||
File[] fontDirs = { new File(parent, "fontfiles"), new File(configDir, "angelica/fonts") };
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.isHeadless() ? null : GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
File gameDir = Minecraft.getMinecraft().mcDataDir;
|
||||
File[] fontDirs = { new File(gameDir, "fontfiles"), new File(gameDir, "config/angelica/fonts") };
|
||||
GraphicsEnvironment ge = !isSafeToUseAwtEnvironmentData ? null : GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
int loaded = 0;
|
||||
for (File dir : fontDirs) {
|
||||
File[] files = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".ttf") || name.toLowerCase().endsWith(".otf"));
|
||||
|
||||
-368
@@ -1,368 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.client.rendering;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.mitchej123.glsm.GLStateManagerService;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class AngelicaGLStateManagerService implements GLStateManagerService {
|
||||
|
||||
private IntBuffer intArrayBuffer = BufferUtils.createIntBuffer(16);
|
||||
|
||||
private IntBuffer getIntArrayBuffer(int size) {
|
||||
if (intArrayBuffer.capacity() < size) {
|
||||
intArrayBuffer = BufferUtils.createIntBuffer(size);
|
||||
}
|
||||
intArrayBuffer.clear();
|
||||
return intArrayBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// ===================== QUERY OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGetInteger(int pname) {
|
||||
return GLStateManager.glGetInteger(pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetString(int pname) {
|
||||
return GLStateManager.glGetString(pname);
|
||||
}
|
||||
|
||||
// ===================== FRAMEBUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenFramebuffers() {
|
||||
return GLStateManager.glGenFramebuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(int framebuffer) {
|
||||
GLStateManager.glDeleteFramebuffers(framebuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindFramebuffer(int target, int framebuffer) {
|
||||
GLStateManager.glBindFramebuffer(target, framebuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCheckFramebufferStatus(int target) {
|
||||
return GLStateManager.glCheckFramebufferStatus(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
|
||||
GLStateManager.glFramebufferTexture2D(target, attachment, textarget, texture, level);
|
||||
}
|
||||
|
||||
// ===================== SHADER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glCreateShader(int type) {
|
||||
return GLStateManager.glCreateShader(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompileShader(int shader) {
|
||||
GLStateManager.glCompileShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetShaderi(int shader, int pname) {
|
||||
return GLStateManager.glGetShaderi(shader, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetShaderInfoLog(int shader, int maxLength) {
|
||||
return GLStateManager.glGetShaderInfoLog(shader, maxLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteShader(int shader) {
|
||||
GLStateManager.glDeleteShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCreateProgram() {
|
||||
return GLStateManager.glCreateProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glAttachShader(int program, int shader) {
|
||||
GLStateManager.glAttachShader(program, shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glLinkProgram(int program) {
|
||||
GLStateManager.glLinkProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetProgrami(int program, int pname) {
|
||||
return GLStateManager.glGetProgrami(program, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUseProgram(int program) {
|
||||
GLStateManager.glUseProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteProgram(int program) {
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
}
|
||||
|
||||
// ===================== UNIFORM/ATTRIBUTE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGetUniformLocation(int program, CharSequence name) {
|
||||
return GLStateManager.glGetUniformLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int value) {
|
||||
GLStateManager.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetAttribLocation(int program, CharSequence name) {
|
||||
return GLStateManager.glGetAttribLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindAttribLocation(int program, int index, CharSequence name) {
|
||||
GLStateManager.glBindAttribLocation(program, index, name);
|
||||
}
|
||||
|
||||
// ===================== VAO OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenVertexArrays() {
|
||||
return UniversalVAO.genVertexArrays();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindVertexArray(int array) {
|
||||
GLStateManager.glBindVertexArray(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteVertexArrays(int array) {
|
||||
GLStateManager.glDeleteVertexArrays(array);
|
||||
}
|
||||
|
||||
// ===================== BUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenBuffers() {
|
||||
return GLStateManager.glGenBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindBuffer(int target, int buffer) {
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(int buffer) {
|
||||
GLStateManager.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenTextures() {
|
||||
return GLStateManager.glGenTextures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenTextures(int[] textures) {
|
||||
final IntBuffer buffer = getIntArrayBuffer(textures.length);
|
||||
GLStateManager.glGenTextures(buffer);
|
||||
buffer.get(textures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(int texture) {
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(int[] textures) {
|
||||
final IntBuffer buffer = getIntArrayBuffer(textures.length);
|
||||
buffer.put(textures);
|
||||
buffer.flip();
|
||||
GLStateManager.glDeleteTextures(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetTexLevelParameteri(int target, int level, int pname) {
|
||||
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetTexLevelParameter(int target, int level, int pname) {
|
||||
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
GLStateManager.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glPixelStorei(int pname, int param) {
|
||||
GLStateManager.glPixelStorei(pname, param);
|
||||
}
|
||||
|
||||
// ===================== STATE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void enableCullFace() {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCullFace() {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlend() {
|
||||
GLStateManager.enableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlend() {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableDepthTest() {
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableDepthTest() {
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthFunc(int func) {
|
||||
GLStateManager.glDepthFunc(func);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthMask(boolean flag) {
|
||||
GLStateManager.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
GLStateManager.glColorMask(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLStateManager.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClear(int mask) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
// ===================== MOJANG ADDITIONS =====================
|
||||
|
||||
@Override
|
||||
public void clear(int mask, boolean checkError) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTexture(int texture) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTexture() {
|
||||
return GLStateManager.getActiveTextureUnit() + GL13.GL_TEXTURE0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTextureAccessor() {
|
||||
return GLStateManager.getActiveTextureUnit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBoundTexture(int internalUnit) {
|
||||
return GLStateManager.getBoundTextureForServerState(internalUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveBoundTexture() {
|
||||
return GLStateManager.getBoundTextureForServerState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewportWidth() {
|
||||
return GLStateManager.getViewportState().width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewportHeight() {
|
||||
return GLStateManager.getViewportState().height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDepthStateMask() {
|
||||
return GLStateManager.getDepthState().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlendEnabled() {
|
||||
return GLStateManager.getBlendMode().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoundTexture(int unit, int texture) {
|
||||
final int currentUnit = GLStateManager.getActiveTextureUnit();
|
||||
if (currentUnit != unit) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
}
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
if (currentUnit != unit) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTextureBinding(int unit) {
|
||||
return GLStateManager.getBoundTextureForServerState(unit);
|
||||
}
|
||||
}
|
||||
-243
@@ -1,243 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.client.rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.mitchej123.glsm.RenderSystemService;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class AngelicaRenderSystemService implements RenderSystemService {
|
||||
|
||||
// Shader state (Mojang-specific, tracked here)
|
||||
private final int[] shaderTextures = new int[16];
|
||||
private final float[] shaderColor = {1f, 1f, 1f, 1f};
|
||||
private final float[] shaderFogColor = {0f, 0f, 0f, 1f};
|
||||
private float shaderFogStart = 0f;
|
||||
private float shaderFogEnd = 0f;
|
||||
private int fogShape = 0;
|
||||
private float shaderLineWidth = 1f;
|
||||
private final Matrix4f projectionMatrix = new Matrix4f();
|
||||
private final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTexture(int texture) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
// ===================== STATE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void enableCullFace() {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCullFace() {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlend() {
|
||||
GLStateManager.enableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlend() {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defaultBlendFunc() {
|
||||
GLStateManager.defaultBlendFunc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnknownBlendState() {
|
||||
// No-op: Angelica tracks blend state accurately
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableDepthTest() {
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableDepthTest() {
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthFunc(int depthFunc) {
|
||||
GLStateManager.glDepthFunc(depthFunc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthMask(boolean flag) {
|
||||
GLStateManager.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLStateManager.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int mask, boolean checkError) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
// ===================== UNIFORM OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int value) {
|
||||
GLStateManager.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
|
||||
GLStateManager.glUniformMatrix3(location, transpose, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
|
||||
GLStateManager.glUniformMatrix4(location, transpose, value);
|
||||
}
|
||||
|
||||
// ===================== THREAD ASSERTIONS =====================
|
||||
|
||||
@Override
|
||||
public void assertOnRenderThread() {
|
||||
// No-op: 1.7.10 is single-threaded for rendering
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertOnRenderThreadOrInit() {
|
||||
// No-op: 1.7.10 is single-threaded for rendering
|
||||
}
|
||||
|
||||
// ===================== SHADER STATE =====================
|
||||
|
||||
@Override
|
||||
public void setShaderTexture(int shaderTexture, int textureId) {
|
||||
if (shaderTexture >= 0 && shaderTexture < 16) {
|
||||
shaderTextures[shaderTexture] = textureId;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShaderTexture(int shaderTexture) {
|
||||
return (shaderTexture >= 0 && shaderTexture < 16) ? shaderTextures[shaderTexture] : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderColor(float red, float green, float blue, float alpha) {
|
||||
shaderColor[0] = red;
|
||||
shaderColor[1] = green;
|
||||
shaderColor[2] = blue;
|
||||
shaderColor[3] = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getShaderColor() {
|
||||
return shaderColor.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderLineWidth(float lineWidth) {
|
||||
shaderLineWidth = lineWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderLineWidth() {
|
||||
return shaderLineWidth;
|
||||
}
|
||||
|
||||
// ===================== FOG STATE =====================
|
||||
|
||||
@Override
|
||||
public void setShaderFogColor(float red, float green, float blue, float alpha) {
|
||||
shaderFogColor[0] = red;
|
||||
shaderFogColor[1] = green;
|
||||
shaderFogColor[2] = blue;
|
||||
shaderFogColor[3] = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getShaderFogColor() {
|
||||
return shaderFogColor.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderFogStart(float start) {
|
||||
shaderFogStart = start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderFogStart() {
|
||||
return shaderFogStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderFogEnd(float end) {
|
||||
shaderFogEnd = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderFogEnd() {
|
||||
return shaderFogEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFogShape(int shape) {
|
||||
fogShape = shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFogShape() {
|
||||
return fogShape;
|
||||
}
|
||||
|
||||
// ===================== PROJECTION MATRIX =====================
|
||||
|
||||
@Override
|
||||
public Matrix4f getProjectionMatrix() {
|
||||
return new Matrix4f(projectionMatrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectionMatrixOrth(Matrix4f m) {
|
||||
projectionMatrix.set(m);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
projectionMatrix.get(projectionMatrixBuffer);
|
||||
projectionMatrixBuffer.rewind();
|
||||
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectionMatrixOrigin(Matrix4f m) {
|
||||
setProjectionMatrixOrth(m);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -6,7 +6,9 @@ import org.lwjgl.opengl.GL11;
|
||||
|
||||
public abstract class AutoClosableAbstractTexture extends AbstractTexture implements AutoCloseable {
|
||||
@Override
|
||||
public void close() throws Exception {}
|
||||
public void close() {
|
||||
deleteGlTexture();
|
||||
}
|
||||
|
||||
// TODO: Is this needed?
|
||||
public void bind() {
|
||||
|
||||
Loaded 100 of 230 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user