Compare commits

..
6 Commits
1961 changed files with 33432 additions and 149547 deletions

No files matched your search

@@ -39,7 +39,6 @@ body:
- Java 19
- Java 20
- Java 21
- Java 25
- Other (Please Specify)
validations:
required: true
-14
View File
@@ -1,14 +0,0 @@
# Description of your *PR* and other info
<!-- Please include a summary of the change -->
<!-- Any details that you think are important to review this PR? -->
<!-- Are there any Issues / PRs related to this one? -->
<!-- Add a screenshot or a video demonstration for new features -->
<details open><summary>Template</summary> <!-- OPEN is intentional -->
### Before submitting a *pull request* make sure you have:
- [ ] Read the [AI Contribution Policy](https://github.com/GTNewHorizons/Angelica/blob/master/AI_POLICY.md)
- [ ] At least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
</details>
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
: "${REPO:?REPO required}"
: "${TAG:?TAG required}"
if [ -z "${DISCORD_WEBHOOK_URL:-}" ]; then
echo "DISCORD_WEBHOOK_URL not set, skipping"
exit 0
fi
if ! release_json=$(gh release view "$TAG" --repo "$REPO" --json body,url,publishedAt 2>&1); then
echo "Could not read release $TAG in $REPO; skipping:"
printf '%s\n' "$release_json"
exit 0
fi
body=$(jq -r '.body // ""' <<<"$release_json")
url=$(jq -r '.url' <<<"$release_json")
ts=$(jq -r '.publishedAt // ""' <<<"$release_json")
[ -n "$ts" ] || ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
payload=$(jq -n \
--arg title "$TAG" \
--arg url "$url" \
--arg desc "$body" \
--arg ts "$ts" \
'{
embeds: [{
title: ("Angelica " + $title + " released"),
url: $url,
description: (if ($desc | length) > 3900
then $desc[0:3900] + "\n\n...[full changelog](" + $url + ")"
else $desc end),
color: 3066993,
footer: { text: "GitHub Releases" },
timestamp: $ts
}]
}')
curl -fsS -X POST -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL"
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
# Builds the libTracyClient shared library for the tracy-client module.
set -euo pipefail
TRACY_VERSION="${TRACY_VERSION:-v0.13.1}"
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
TRACY_REPO="https://github.com/wolfpld/tracy"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
case "$(uname -s)" in
Darwin) PLAT_OS="macos" ;;
Linux) PLAT_OS="linux" ;;
*) PLAT_OS="windows" ;;
esac
case "$(uname -m)" in
arm64|aarch64) PLAT_ARCH="arm64" ;;
*) PLAT_ARCH="x64" ;;
esac
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$PLAT_OS-$PLAT_ARCH}"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
if [[ -n "${TRACY_SRC:-}" ]]; then
SRC_DIR="$WORK_DIR/tracy"
git clone --shared --no-checkout "$TRACY_SRC" "$SRC_DIR"
git -C "$SRC_DIR" checkout --detach "$TRACY_VERSION"
else
SRC_DIR="$WORK_DIR/tracy"
git clone --depth 1 --branch "$TRACY_VERSION" "$TRACY_REPO" "$SRC_DIR"
fi
BUILD_DIR="$WORK_DIR/build"
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
cmake -S "$SRC_DIR" -B "$BUILD_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DTRACY_STATIC=OFF \
-DTRACY_ENABLE=ON \
-DTRACY_ON_DEMAND=ON \
-DTRACY_DELAYED_INIT=ON \
-DTRACY_MANUAL_LIFETIME=ON \
-DTRACY_NO_SAMPLING=ON \
-DTRACY_NO_SYSTEM_TRACING=ON \
-DTRACY_NO_CRASH_HANDLER=ON \
-DTRACY_LTO=OFF
cmake --build "$BUILD_DIR" --config Release --parallel
mkdir -p "$OUT_DIR"
for NAME in libTracyClient.so libTracyClient.dylib TracyClient.dll; do
LIB="$(find "$BUILD_DIR" -name "$NAME" | head -1)"
if [[ -n "$LIB" ]]; then
cp -L "$LIB" "$OUT_DIR/$NAME"
cp "$SRC_DIR/LICENSE" "$OUT_DIR/LICENSE"
mkdir -p "$RES_DIR"
cp -L "$LIB" "$RES_DIR/$NAME"
cp "$SRC_DIR/LICENSE" "$RES_DIR/LICENSE"
echo "built: $OUT_DIR/$NAME ($TRACY_VERSION), staged: $RES_DIR/$NAME"
exit 0
fi
done
echo "error: built library not found under $BUILD_DIR" >&2
exit 1
-92
View File
@@ -1,92 +0,0 @@
name: Build Tracy natives
on:
push:
tags:
- "tracy-v*"
workflow_dispatch:
inputs:
tracy-version:
description: Tracy git tag to build
required: false
default: v0.13.1
publish:
description: Publish angelica-tracy.jar to the GTNH maven
type: boolean
required: false
default: false
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-22.04
platform: linux-x64
- os: windows-2025
platform: windows-x64
- os: macos-15-intel
platform: macos-x64
- os: macos-15
platform: macos-arm64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Build libTracyClient
shell: bash
env:
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
OUT_DIR: dist/tracy-natives
run: |
export TRACY_VERSION="${RAW_VERSION#tracy-}"
bash .github/scripts/build-tracy-natives.sh
- uses: actions/upload-artifact@v4
with:
name: tracy-natives-${{ matrix.platform }}
path: dist/tracy-natives/*
if-no-files-found: error
assemble:
needs: build
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- name: Check tracy version matches the version catalog
shell: bash
env:
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
run: |
ver="${RAW_VERSION#tracy-}"
grep -q "^tracy = \"${ver#v}\"$" gradle/libs.versions.toml \
|| { echo "libs.versions.toml tracy does not match $ver" >&2; exit 1; }
- uses: actions/download-artifact@v5
with:
pattern: tracy-natives-*
path: staging
- name: Stage natives into tracy-client resources
shell: bash
run: |
for d in staging/tracy-natives-*; do
dest="tracy-client/src/main/resources/natives/tracy/${d#staging/tracy-natives-}"
mkdir -p "$dest"
cp "$d"/* "$dest/"
done
find tracy-client/src/main/resources/natives -type f
- name: Build angelica-tracy.jar
run: ./gradlew :tracy-client:distJar
- uses: actions/upload-artifact@v4
with:
name: angelica-tracy
path: tracy-client/build/libs/angelica-tracy-*.jar
if-no-files-found: error
- name: Publish to GTNH maven
if: github.ref_type == 'tag' || inputs.publish
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew -PpublishTracy=true :tracy-client:publish
+1 -20
View File
@@ -3,7 +3,7 @@ name: Release tagged build
on:
push:
tags: [ '*', '!tracy-v*' ]
tags: [ '*' ]
permissions:
contents: write
@@ -12,22 +12,3 @@ jobs:
release-tags:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master
secrets: inherit
notify-discord:
needs: [release-tags]
runs-on: ubuntu-24.04
if: >-
${{ !endsWith(github.ref_name, '-snapshot')
&& !contains(github.event.head_commit.message, '[snapshot]')
&& !endsWith(github.ref_name, '-pre') }}
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Announce release to Discord
shell: bash
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/announce-release.sh
+4 -8
View File
@@ -1,11 +1,10 @@
.gradle
.kotlin/
.settings
/.idea/
/.vs/
/.vscode/
run/
build/
/run/
/build/
/eclipse/
.classpath
.project
@@ -13,7 +12,6 @@ build/
/config/
/crash-reports/
/logs/
tmp/
options.txt
/saves/
usernamecache.json
@@ -38,7 +36,5 @@ addon.local.gradle
addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
local/
tracy-client/src/main/resources/natives/
dist/tracy-natives/
*.tracy
/*.csv
!gl_functions.csv
-55
View File
@@ -1,55 +0,0 @@
# AI Usage Policy
This project has strict rules for AI usage:
* **The human-in-the-loop must fully understand all code.** If you
can't explain what your changes do and how they interact with the
greater system without the aid of AI tools, do not contribute
those changes.
* **Issues and discussions can use AI assistance but must have a full
human-in-the-loop.** This means that any content generated with AI
must have been reviewed *and edited* by a human before submission.
The author must do their research and trim this down.
* **No AI-generated media is allowed (textures, videos, audio, etc.).**
Text and code are the only acceptable AI-generated content, per the
other rules in this policy.
* **Commit messages, PR descriptions must read in your own voice.**
No AI-authored or AI co-authored commits, including Co-authored-by AI tags.
* **Only contribute fixes for bugs you have personally hit in-game.**
If you haven’t experienced or reproduced a bug firsthand, and don’t
already know the relevant code well, don’t throw AI at it and forward it to us.
Patches written for problems the author never actually experienced themselves
tend to fix the wrong thing and/or produce another.
* **The voice and intent of written content must be yours.**
For documentation and especially quests, the tone and the point you're trying to make
has to come from you, not an AI. Use AI to help with phrasing or cleanup if you like,
but if the substance and voice aren't yours, don't contribute it.
* **Bad AI use may be blocked.** People who produce bad contributions
that are clearly AI (slop) may be blocked from making future contributions.
We love to help junior developers learn and grow, but if you're interested
in that, don't use AI and we'll help you. We're sorry that bad AI
users have ruined this for you.
## There are Humans Here
Please remember that this project is maintained by humans.
Every discussion, issue, and pull request is read and reviewed by
humans (and sometimes machines, too). It is a boundary point at which
people interact with each other and work is done. It is rude and
disrespectful to approach this boundary with low-effort, unqualified
work, since it puts the burden of validation on the maintainer.
In a perfect world, AI would produce high-quality, accurate work
every time. But today, that reality depends on the user of the AI.
And today, many users of AI do not use it well. So, until that
changes, we need to have strict rules to protect maintainers.
Based on https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
-17
View File
@@ -1,17 +0,0 @@
# Angelica dev commands
# Build with headless display
build:
xvfb-run --server-args="-screen 0 1366x768x24" ./gradlew build
# Run tests with headless display
test:
xvfb-run --server-args="-screen 0 1366x768x24" ./gradlew test
# Build jar only (no tests)
jar:
./gradlew jar
# Clean build directory
clean:
./gradlew clean
+13 -30
View File
@@ -7,50 +7,40 @@
* It's a cute angle -mitchej123
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
## **Beta Quality - Here there be Dragons**
* Shaders - Beta
* Complementary + Euphoria Patches is the primary compatibility target
* Sildurs, Seus, and any other shaders might work
## Core Profile
Targets GL 3.3+ core profile
* FFP emulation via generated GLSL shaders
* Display list emulation
* Software matrix stacks and state management through GLSM
* GL_QUADS → GL_TRIANGLES conversion
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
## **Beta Quality - Here there be Dragons**
* Shaders sneak preview - Please don't report issues yet, but please do submit fixes
* Complementary & BSL mostly function, but have some bugs
* Sildurs, Seus, and any other shader probably doesn't work
# Required Dependencies
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.14
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.4.2
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
# Known (temporary) Incompatibilities
* Forge Relocation - Currently non Thread Safe; safe if not in use
* ProjectRed Frames - Currently non Thread Safe; safe if not in use
* ProjectRed Frames - Currently non Thread Safe; safe if not in use'
# Potential Incompatibilities
* EndlessIDs - Compat commits have been made, might work, might not - not fully tested
# Known Incompatibilities
* FalseTweaks - Not currently planned (Looking for volunteers) [Might work if you effectively disable most of the mod]
* SmoothFont - Not currently planned (Looking for volunteers)
* RPLE - Not currently planned (Looking for volunteers)
* LiteLoader - Install [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
# Permanent Incompatibilities
* OptiFine [Disabled, won't fix]
* FastCraft [Disabled, won't fix]
* BetterFPS [Disabled, won't fix]
* OptimizationsAndTweaks [Disabled, won't fix]
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - use 1.2.2+
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 0.4.5 or above
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
* SmoothFont - Equivalent functionality added
* ClientFixer - Use a resource pack like [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
# Credits
* ShadersMod
@@ -65,16 +55,16 @@ Targets GL 3.3+ core profile
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
* Backported code under the LGPL
* Sodium / Celeritas
* Sodium
* JellySquid & the CaffeineMC team, for making Sodium in the first place
* Embeddedt for Celeritas used under the LGPL 3.0
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
* Reese's Sodium Options
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
* Neodymium
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
* MCPatcherForge
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
* [Forge version](https://github.com/mist475/MCPatcherForge): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
* [Forge version](https://bitbucket.org/prupe/mcpatcher/src/master/): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
* HUDCaching
* Created by [Moulberry](https://github.com/Moulberry/MCHUDCaching), backported from [Patcher](https://github.com/Sk1erLLC/Patcher) mod by Alexdoru under the CC-BY-SA-NC 4.0 license
* Angelica
@@ -92,10 +82,3 @@ Targets GL 3.3+ core profile
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
* FalseTweaks
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
* Beddium
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
+12 -242
View File
@@ -1,136 +1,27 @@
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
minecraft {
extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
extraRunJvmArguments.add("-Dangelica.tracy=true")
extraRunJvmArguments.add("-Dangelica.sdlgpu.enable=true")
extraRunJvmArguments.add("-Dangelica.unmappedGL=fail")
extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=$lwjglDebug")
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")
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=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")
}
tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
}
if (gpuHud) {
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) {
environment("MTL_HUD_ENABLED", "1")
} else {
environment("MANGOHUD", "1")
environment("LD_PRELOAD", "/usr/\$LIB/mangohud/libMangoHud_shim.so")
}
}
}
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")) {
environment("WAYLAND_DISPLAY", "")
environment("XDG_SESSION_TYPE", "x11")
val rgpFrame = project.findProperty("rgpFrame") as String?
val rgpCapture = rgp || project.hasProperty("rgp") || rgpFrame != null
if (rgpCapture) {
environment("MESA_VK_TRACE", "rgp")
if (rgpFrame != null) {
environment("MESA_VK_TRACE_FRAME", rgpFrame)
doFirst { logger.lifecycle("RGP: capturing frame $rgpFrame -> /tmp/java_*.rgp") }
} else {
environment("MESA_VK_TRACE_TRIGGER", "/tmp/angelica_rgp.trigger")
doFirst { logger.lifecycle("RGP: touch /tmp/angelica_rgp.trigger (or press F1) to capture -> /tmp/java_*.rgp") }
}
} else if (renderdoc || project.hasProperty("renderdoc")) {
val renderdocLib = file(project.findProperty("renderdocLib") as String? ?: "${System.getProperty("user.home")}/Downloads/renderdoc_1.44/lib/librenderdoc.so")
if (renderdocLib.isFile) {
environment("LD_PRELOAD", renderdocLib.absolutePath)
doFirst { logger.lifecycle("RENDERDOC: LD_PRELOAD set, press F12 to capture") }
} else {
doFirst { logger.lifecycle("RENDERDOC: enabled but ${renderdocLib.absolutePath} not found; set -PrenderdocLib=<path>") }
}
}
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
}
dependsOn(tasks.extractNatives2)
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
}
tasks.processResources {
val projectVersion = project.version.toString()
inputs.property("version", projectVersion)
inputs.property("version", project.version.toString())
filesMatching("META-INF/rfb-plugin/*") {
expand("version" to projectVersion)
expand("version" to project.version.toString())
}
}
@@ -140,124 +31,3 @@ tasks.register<Copy>("copyDependencies") {
from(configurations.default)
into("testDependencies")
}
val embedOnly: Configuration by configurations
val shadowImplementation: Configuration by configurations
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = embedOnly
outputs.dir(temporaryDir)
}
val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
dependsOn(shadowImplementation)
from(shadowImplementation.map(::zipTree))
exclude("module-info.class")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveFileName = "shadow-stripped.jar"
}
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = files(stripModuleInfoFromShadow.map { it.archiveFile })
outputs.dir(temporaryDir)
}
val glsmTestFixtures: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
}
}
val downgradeGlsmTestFixturesForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = glsmTestFixtures
outputs.dir(temporaryDir)
}
fun downgraded(task: TaskProvider<DowngradeFiles>): FileCollection = files(task.map { it.outputMap.values })
fun Test.configureAngelicaJava8() {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (isMacOsArm64) {
vendor = JvmVendorSpec.AZUL
}
}
dependsOn(tasks.extractNatives2)
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
testLogging { events("passed", "skipped", "failed") }
val swaps = mapOf(
embedOnly to downgradeEmbedOnlyForTest,
shadowImplementation to downgradeShadowImplForTest,
glsmTestFixtures to downgradeGlsmTestFixturesForTest,
)
dependsOn(swaps.values)
for ((original, downgrade) in swaps) {
classpath = classpath.minus(original).plus(downgraded(downgrade))
}
}
tasks.test {
useJUnitPlatform { excludeTags = setOf("gl-core") }
configureAngelicaJava8()
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
}
val downgradeRootMainClasses = tasks.named<DowngradeFiles>("downgradeMainClasses")
val downgradeRootTestClasses = tasks.named<DowngradeFiles>("downgradeTestClasses")
val glCoreTest by tasks.registering(Test::class) {
description = "Runs the gl-core tests in their own JVM with a real GL context."
group = "verification"
useJUnitPlatform { includeTags = setOf("gl-core") }
dependsOn(downgradeRootMainClasses, downgradeRootTestClasses)
val rawMain = sourceSets["main"].output.classesDirs
val rawTest = sourceSets["test"].output.classesDirs
val downgradedTest = downgraded(downgradeRootTestClasses)
setTestClassesDirs(downgradedTest)
classpath = downgradedTest
.plus(downgraded(downgradeRootMainClasses))
.plus(sourceSets["test"].runtimeClasspath.minus(rawMain).minus(rawTest))
configureAngelicaJava8()
mustRunAfter(tasks.test)
}
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")
}
tasks.withType<TaskPublishCurseForge>().configureEach {
uploadArtifacts.forEach { it.addGameVersion("Client") }
}
tasks.withType<TaskModrinthUpload>().configureEach {
setDependsOn(dependsOn.filterNot { it == "build" } + "assemble")
}
-14
View File
@@ -1,14 +0,0 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
}
// Re-export Gradle's generated typed catalog accessors so applied scripts (dependencies.gradle.kts) can use them.
dependencies {
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
implementation(libs.asm.build)
}
-5
View File
@@ -1,5 +0,0 @@
dependencyResolutionManagement {
versionCatalogs {
create("libs") { from(files("../gradle/libs.versions.toml")) }
}
}
@@ -1,49 +0,0 @@
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import java.io.File
private const val LWJGL3_AWARE_DESC = "Lme/eigenraven/lwjgl3ify/api/Lwjgl3Aware;"
/**
* Stamps @Lwjgl3Aware onto every class under [classesDir] - classes that already carry the annotation are left untouched.
*/
fun injectLwjgl3Aware(classesDir: File) {
if (!classesDir.isDirectory) return
classesDir.walkTopDown()
.filter { it.isFile && it.extension == "class" && it.name != "module-info.class" }
.forEach { classFile ->
val reader = ClassReader(classFile.readBytes())
if (hasLwjgl3Aware(reader)) return@forEach
val writer = ClassWriter(reader, 0)
reader.accept(object : ClassVisitor(Opcodes.ASM9, writer) {
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
super.visitAnnotation(LWJGL3_AWARE_DESC, true)?.visitEnd()
}
}, 0)
classFile.writeBytes(writer.toByteArray())
}
}
private fun hasLwjgl3Aware(reader: ClassReader): Boolean {
var found = false
reader.accept(object : ClassVisitor(Opcodes.ASM9) {
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
if (descriptor == LWJGL3_AWARE_DESC) found = true
return null
}
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
return found
}
+122
View File
@@ -0,0 +1,122 @@
/*
* Add your dependencies here. Supported configurations:
* - api("group:name:version:classifier"): if you use the types from this dependency in the public API of this mod
* Available at runtime and compiletime for mods depending on this mod
* - implementation("g:n:v:c"): if you need this for internal implementation details of the mod, but none of it is visible via the public API
* Available at runtime but not compiletime for mods depending on this mod
* - compileOnly("g:n:v:c"): if the mod you're building doesn't need this dependency during runtime at all, e.g. for optional mods
* Not available at all for mods depending on this mod, only visible at compiletime for this mod
* - compileOnlyApi("g:n:v:c"): like compileOnly, but also visible at compiletime for mods depending on this mod
* Available at compiletime but not runtime for mods depending on this mod
* - runtimeOnlyNonPublishable("g:n:v:c"): if you want to include a mod in this mod's runClient/runServer runs, but not publish it as a dependency
* Not available at all for mods depending on this mod, only visible at runtime for this mod
* - devOnlyNonPublishable("g:n:v:c"): a combination of runtimeOnlyNonPublishable and compileOnly for dependencies present at both compiletime and runtime,
* but not published as Maven dependencies - useful for RFG-deobfuscated dependencies or local testing
* - runtimeOnly("g:n:v:c"): if you don't need this at compile time, but want it to be present at runtime
* Available at runtime for mods depending on this mod
* - annotationProcessor("g:n:v:c"): mostly for java compiler plugins, if you know you need this, use it, otherwise don't worry
* - testCONFIG("g:n:v:c") - replace CONFIG by one of the above (except api), same as above but for the test sources instead of main
*
* - shadowImplementation("g:n:v:c"): effectively the same as API, but the dependency is included in your jar under a renamed package name
* Requires you to enable usesShadowedDependencies in gradle.properties
*
* - compile("g:n:v:c"): deprecated, replace with "api" (works like the old "compile") or "implementation" (can be more efficient)
*
* You can exclude transitive dependencies (dependencies of the chosen dependency) by appending { transitive = false } if needed,
* but use this sparingly as it can break using your mod as another mod's dependency if you're not careful.
*
* To depend on obfuscated jars you can use `devOnlyNonPublishable(rfg.deobf("dep:spec:1.2.3"))` to fetch an obfuscated jar from maven,
* or `devOnlyNonPublishable(rfg.deobf(project.files("libs/my-mod-jar.jar")))` to use a file.
*
* Gradle names for some of the configuration can be misleading, compileOnlyApi and runtimeOnly both get published as dependencies in Maven, but compileOnly does not.
* The buildscript adds runtimeOnlyNonPublishable to also have a runtime dependency that's not published.
*
* For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph
*/
configurations {
transformedMod // Mods that can be transformed - used for compiling angelica, but not necessary at runtime
transformedMod.canBeConsumed = false
transformedModCompileOnly
transformedModCompileOnly.canBeConsumed = false
// Add the transformed mod dependencies to the compilation classpaths, but don't publish them in the Maven metadata
compileClasspath.extendsFrom(transformedMod, transformedModCompileOnly)
testCompileClasspath.extendsFrom(transformedMod, transformedModCompileOnly)
// Can be enabled for testing if desired
// runtimeClasspath.extendsFrom(transformedMod)
// testRuntimeClasspath.extendsFrom(transformedMod)
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.5.78")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:0.4.0") { transitive = false }
compileOnly("org.projectlombok:lombok:1.18.22") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.22")
// Use modern models+textures to inject QuadProviders
devOnlyNonPublishable('com.github.GTNewHorizons:TX-Loader:1.7.2:dev')
// Iris Shaders
compileOnly('org.jetbrains:annotations:24.0.1')
api("com.github.GTNewHorizons:GTNHLib:0.5.20:dev")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-4.g6b42bca") {
exclude module: "antlr4" // we only want to shadow the runtime module
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
compileOnly "org.apache.ant:ant:1.8.2"
// Because who doesn't want NEI
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.6.45-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.3.10:dev')
// Notfine Deps
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles:1.0.4:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.6.35:dev") { transitive = false }
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.12.14-GTNH:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.7.5:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.6.4:dev")
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.1:dev") { transitive = false }
compileOnly(rfg.deobf('maven.modrinth:backhand:1.4.1')) { transitive = false }
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { transitive = false }
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { transitive = false }
compileOnly(rfg.deobf("curse.maven:extrautils-225561:2264383"))
compileOnly(rfg.deobf("curse.maven:dynamiclights-227874:2337326"))
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.6:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly("com.github.GTNewHorizons:HoloInventory:2.4.13-GTNH:dev")
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
// Tests
testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation("org.mockito:mockito-core:3.+")
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
devOnlyNonPublishable("com.github.javaparser:javaparser-symbol-solver-core:3.26.2") {
exclude module: "guava"
}
}
-209
View File
@@ -1,209 +0,0 @@
/*
* Angelica's dependency declarations. Supported configurations:
* - api("group:name:version:classifier"): if you use the types from this dependency in the public API of this mod
* Available at runtime and compiletime for mods depending on this mod
* - implementation("g:n:v:c"): if you need this for internal implementation details of the mod, but none of it is visible via the public API
* Available at runtime but not compiletime for mods depending on this mod
* - compileOnly("g:n:v:c"): if the mod you're building doesn't need this dependency during runtime at all, e.g. for optional mods
* Not available at all for mods depending on this mod, only visible at compiletime for this mod
* - runtimeOnlyNonPublishable("g:n:v:c"): if you want to include a mod in this mod's runClient/runServer runs, but not publish it as a dependency
* - devOnlyNonPublishable("g:n:v:c"): a combination of runtimeOnlyNonPublishable and compileOnly, not published as a Maven dependency
* - shadowImplementation("g:n:v:c"): like api, but included in your jar under a renamed package name
* - embedOnly("g:n:v:c"): embedded in the jar without relocation
*
* Versions come from gradle/libs.versions.toml. Mod coordinates stay literal: nearly all carry a
* :dev classifier, which a version catalog cannot express, and curse/modrinth "versions" are file ids.
*
* To depend on obfuscated jars use `rfg.deobf("dep:spec:1.2.3")`.
*/
import com.gtnewhorizons.retrofuturagradle.modutils.ModUtils
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.kotlin.dsl.DependencyHandlerScope
val libs = the<LibrariesForLibs>()
val rfg = (dependencies as ExtensionAware).extensions.getByType<ModUtils.RfgDependencyExtension>()
val api = configurations["api"]
val compileOnly = configurations["compileOnly"]
val annotationProcessor = configurations["annotationProcessor"]
val shadowImplementation = configurations["shadowImplementation"]
val runtimeOnlyNonPublishable = configurations["runtimeOnlyNonPublishable"]
val devOnlyNonPublishable = configurations["devOnlyNonPublishable"]
// Mods that can be transformed - used for compiling angelica, but not necessary at runtime
val transformedMod by configurations.creating { isCanBeConsumed = false }
val transformedModCompileOnly by configurations.creating { isCanBeConsumed = false }
// Embed in JAR without relocation (compile only, shadowed into JAR)
val embedOnly by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
// Add the transformed mod dependencies to the compilation classpaths, but don't publish them in the Maven metadata
configurations["compileClasspath"].extendsFrom(transformedMod, transformedModCompileOnly, embedOnly)
configurations["testCompileClasspath"].extendsFrom(transformedMod, transformedModCompileOnly, embedOnly)
configurations["testRuntimeClasspath"].extendsFrom(embedOnly)
configurations["testRuntimeClasspath"].exclude(group = "com.github.GTNewHorizons", module = "lwjgl3ify")
// Prevents transitive Angelica deps from resolving against this project
configurations.configureEach {
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
}
DependencyHandlerScope.of(dependencies).apply {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-994-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.19-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterLoadingScreen:1.7.9-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterQuesting:3.8.72-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.63:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.32-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:LogisticsPipes:1.5.32-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.55-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.10:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Schematica:1.12.6-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.4.1:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.13:dev")
runtimeOnlyNonPublishable("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.19.30:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.30-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.15-GTNH:dev")
runtimeOnlyNonPublishable("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
// runtimeOnlyNonPublishable(libs.lwjgl3ify)
compileOnly(libs.lombok) { isTransitive = false }
annotationProcessor(libs.lombok)
// Iris Shaders
compileOnly(libs.annotations)
api(libs.gtnhlib) { artifact { classifier = "dev" } }
shadowImplementation(project(":glsm")) { isTransitive = false }
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
shadowImplementation(libs.eventbus)
shadowImplementation(libs.jcpp) // Apache 2.0
shadowImplementation(libs.glsl.transformation.lib) {
exclude(module = "antlr4") // we only want to shadow the runtime module
}
shadowImplementation(libs.antlr4.runtime)
compileOnly(libs.ant)
// 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")
devOnlyNonPublishable("com.github.GTNewHorizons:CodeChickenCore:1.4.16:dev")
// BQ Testing
// devOnlyNonPublishable("com.github.GTNewHorizons:BetterQuesting:3.8.74-GTNH:dev")
// Display List Testing
/*
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-994-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.19-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.61:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.10:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.47-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.4.1:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.13:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.19.30:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.25-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.27-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.15-GTNH:dev")
*/
//devOnlyNonPublishable("com.github.GTNewHorizons:worldedit-gtnh:v0.0.9")
// Notfine Deps
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.21-GTNH:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.36:dev") { isTransitive = false }
compileOnly(rfg.deobf("curse.maven:witchery-69673:2234410"))
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev") { isTransitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.8.19:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.20:dev")
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { isTransitive = false }
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { isTransitive = false }
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { isTransitive = false }
compileOnly(rfg.deobf("curse.maven:extrautils-225561:2264383"))
compileOnly(rfg.deobf("curse.maven:dynamiclights-227874:2337326"))
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.11:dev")
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5663_H261"))
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.6-GTNH:dev")
// For testing alternative splash screens - https://github.com/MalTeeez/ModernSplash/releases
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.5-GTNH:dev")
// Distant Horizons
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { isTransitive = false }
}
afterEvaluate {
val glsmTestFixtures = configurations["glsmTestFixtures"]
val testImplementation = configurations["testImplementation"]
val testCompileOnly = configurations["testCompileOnly"]
val testRuntimeOnly = configurations["testRuntimeOnly"]
DependencyHandlerScope.of(dependencies).apply {
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(libs.mockito.core)
testImplementation(libs.lwjgl2)
testImplementation(libs.junit.platform.engine)
testImplementation(libs.junit.platform.reporting)
testImplementation(libs.junit.platform.launcher)
testImplementation(libs.archunit.junit5)
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
testCompileOnly(testFixtures(project(":glsm")))
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
}
}
+2084
View File
File diff suppressed because it is too large. Load diff
-331
View File
@@ -1,331 +0,0 @@
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
`java-library`
`java-test-fixtures`
`maven-publish`
}
apply(plugin = "xyz.wagyourtail.jvmdowngrader")
version = rootProject.version
val lwjglNatives: String by extra
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
withSourcesJar()
}
// RFG variant attributes: the root Angelica project uses RetroFuturaGradle which expects
// these attributes on dependencies. Without them, Gradle can't resolve the subproject variant.
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
configurations {
listOf("apiElements", "runtimeElements", "testFixturesApiElements", "testFixturesRuntimeElements").forEach { name ->
named(name) {
attributes {
attribute(rfgObfAttr, "mcp")
attribute(rfgTransformedAttr, true)
}
}
}
}
// Compile stubs: MC class signatures needed to resolve GTNHLib's type hierarchy.
// Not included in the published jar - real MC classes are on the classpath at runtime.
sourceSets {
create("stubs") {
java.srcDir("src/stubs/java")
}
}
// Exclude stubs from all output jars
tasks.withType<Jar>().configureEach {
exclude("net/minecraft/**")
}
repositories {
maven {
name = "Mojang"
url = uri("https://libraries.minecraft.net")
}
maven {
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
}
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
}
maven {
name = "taumc"
url = uri("https://maven.taumc.org/releases")
}
maven {
name = "WagYourTail"
url = uri("https://maven.wagyourtail.xyz/releases")
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
}
maven {
name = "Sonatype Snapshots"
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
}
maven {
name = "Maven Central Snapshots"
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
}
mavenLocal()
mavenCentral()
mavenLocal()
}
dependencies {
// MC compile stubs
compileOnly(sourceSets["stubs"].output)
// LWJGL
compileOnly(libs.lwjgl2)
compileOnly(libs.lwjgl2.util)
compileOnly(libs.asm.tree.compile)
// Our Deps
api(libs.gtnhlib) { artifact { classifier = "dev" } }
api(libs.eventbus)
compileOnly(libs.celeritas.common) { isTransitive = false }
api(libs.glsl.transformation.lib) { exclude(module = "antlr4") }
api(libs.antlr4.runtime)
implementation(libs.jcpp)
// shaderc + SPIRV-Cross for the SpirvShaderTranslator (GLSL -> SPIR-V -> GLSL ES).
// TODO: Remove
compileOnly(libs.lwjgl3.shaderc)
compileOnly(libs.lwjgl3.spvc)
testImplementation(libs.lwjgl3.shaderc)
testImplementation(libs.lwjgl3.spvc)
// Host natives so the JNI wrapper can find libshaderc / libspirv-cross / liblwjgl.
testRuntimeOnly(libs.lwjgl3.shaderc) { artifact { classifier = lwjglNatives } }
testRuntimeOnly(libs.lwjgl3.spvc) { artifact { classifier = lwjglNatives } }
testRuntimeOnly(libs.lwjgl3)
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
// @Lwjgl3Aware annotation
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
compileOnly(libs.lombok) { isTransitive = false }
annotationProcessor(libs.lombok)
compileOnly(libs.annotations)
compileOnly(libs.log4j.api)
// Deps (normally from MC) - compile only, but needed at test runtime
compileOnly(libs.guava)
testRuntimeOnly(libs.guava)
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml) { isTransitive = false }
testFixturesCompileOnly(sourceSets["stubs"].output)
testFixturesApi(platform(libs.junit.bom))
testFixturesApi(libs.junit.jupiter.api)
testFixturesApi(libs.lwjgl2)
// Test
testImplementation(sourceSets["stubs"].output)
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(libs.asm.tree.test)
testImplementation(libs.lwjgl2)
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)
}
val depsToDowngrade by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
depsToDowngrade(libs.eventbus)
depsToDowngrade(libs.celeritas.common) { isTransitive = false }
}
tasks.withType<DowngradeFiles>().configureEach {
downgradeTo.set(JavaVersion.VERSION_1_8)
multiReleaseOriginal.set(false)
multiReleaseVersions.set(emptySet())
logLevel.set("FATAL")
}
val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = depsToDowngrade
}
val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["main"].output.classesDirs.plus(sourceSets["stubs"].output.classesDirs)
classpath = sourceSets["main"].compileClasspath
(sourceSets["main"].output.classesDirs.files + sourceSets["stubs"].output.classesDirs.files)
.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
dependsOn(tasks.named("classes"), tasks.named("stubsClasses"))
}
val downgradeTestFixturesClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["testFixtures"].output.classesDirs
classpath = sourceSets["testFixtures"].compileClasspath
sourceSets["testFixtures"].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
dependsOn(tasks.named("testFixturesClasses"))
}
val downgradeTestClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["test"].output.classesDirs
classpath = sourceSets["test"].compileClasspath
sourceSets["test"].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
dependsOn(tasks.named("testClasses"))
}
val GL_TASK_TAGS = mapOf(
"glCompatTest" to "gl-compat",
"glCoreTest" to "gl-core",
"glSharedTest" to "gl-shared",
)
fun Test.configureGlsmJava8() {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (System.getProperty("os.name").lowercase().contains("mac")
&& System.getProperty("os.arch") == "aarch64") {
vendor = JvmVendorSpec.AZUL
}
}
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeTestFixturesClasses, downgradeDepsForTest)
val mainClassesDirs = sourceSets["main"].output.classesDirs
.plus(sourceSets["stubs"].output.classesDirs)
val testClassesDirs = sourceSets["test"].output.classesDirs
val downgradedTest = files(downgradeTestClasses.map { it.outputCollection })
val downgradedMain = files(downgradeMainClasses.map { it.outputCollection })
val downgradedDeps = files(downgradeDepsForTest.map { it.outputCollection })
setTestClassesDirs(downgradedTest)
classpath = downgradedTest
.plus(downgradedMain)
.plus(downgradedDeps)
.plus(files(downgradeTestFixturesClasses.map { it.outputCollection }))
.plus(sourceSets["test"].runtimeClasspath.minus(mainClassesDirs).minus(testClassesDirs)
.minus(sourceSets["testFixtures"].output.classesDirs).minus(depsToDowngrade))
val extractNatives = rootProject.tasks.named("extractNatives2")
dependsOn(extractNatives)
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
}
tasks.test {
useJUnitPlatform {
excludeTags = GL_TASK_TAGS.values.toSet() + "lwjgl3"
}
filter {
excludeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
}
configureGlsmJava8()
}
val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
tasks.register<Test>(taskName) {
description = "Runs the $tag GL tests in their own JVM, so they get their own Display and GL profile."
group = "verification"
useJUnitPlatform {
includeTags = setOf(tag)
}
configureGlsmJava8()
}
}
(listOf(tasks.test) + glTestTasks).zipWithNext { earlier, later ->
later.configure { mustRunAfter(earlier) }
}
tasks.test { finalizedBy(glTestTasks) }
val verifyTestsRan by tasks.registering {
dependsOn(tasks.test, glTestTasks)
}
(listOf(tasks.test) + glTestTasks).forEach { testTask ->
val name = testTask.name
val verify = tasks.register("verify${name.replaceFirstChar { it.uppercase() }}Ran") {
val resultsDir = layout.buildDirectory.dir("test-results/$name")
dependsOn(testTask)
doLast {
val dir = resultsDir.get().asFile
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
check(xmls.isNotEmpty()) {
":glsm:$name produced no TEST-*.xml in $dir - test task likely went NO-SOURCE"
}
}
}
verifyTestsRan.configure { dependsOn(verify) }
}
tasks.check { dependsOn(verifyTestsRan) }
val spirvTest by tasks.registering(Test::class) {
description = "Runs SpirvShaderTranslator* tests against native shaderc + spvc on Java 21."
group = "verification"
useJUnitPlatform()
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(21)
}
testClassesDirs = sourceSets["test"].output.classesDirs
classpath = sourceSets["test"].runtimeClasspath.filter { file ->
// Strip the LWJGL2 jars - they collide with LWJGL3's sealed org.lwjgl package.
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
filter {
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
includeTestsMatching("com.gtnewhorizons.angelica.glsm.ffp.FFPUniformBlockSpirvLayoutTest")
}
// libshaderc.so / libspirv-cross.so are extracted by extractNatives3.
val extractNatives3 = rootProject.tasks.named("extractNatives3")
dependsOn(extractNatives3)
jvmArgs(
"-Djava.library.path=${extractNatives3.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}",
"--enable-native-access=ALL-UNNAMED"
)
testLogging {
showStandardStreams = true
}
}
tasks.named("check") { dependsOn(spirvTest) }
publishing {
publications {
create<MavenPublication>("mavenJava") {
groupId = "com.gtnewhorizons.angelica"
artifactId = "glsm"
version = rootProject.version.toString()
from(components["java"])
}
}
repositories {
if (System.getenv("MAVEN_USER") != null) {
maven {
name = "GTNHMaven"
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
credentials {
username = System.getenv("MAVEN_USER")
password = System.getenv("MAVEN_PASSWORD")
}
}
}
}
}
@@ -1,124 +0,0 @@
package com.gtnewhorizons.angelica.config;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Locale;
public final class SystemProperties {
private static final Logger LOGGER = LogManager.getLogger("SystemProperties");
// Backend selection
public static final String KEY_USE_SDL_GPU = "angelica.sdlgpu.enable";
public static final boolean USE_SDL_GPU = Boolean.getBoolean(KEY_USE_SDL_GPU);
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
// Redirector
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
public static final boolean FFP_WARN_ON_UNSUPPORTED = Boolean.getBoolean("angelica.ffp.warnOnUnsupported");
// SDL-GPU
public static final String KEY_SDL_GPU_DRIVER = "angelica.sdlgpu.driver";
public static final String SDL_GPU_DRIVER = System.getProperty(KEY_SDL_GPU_DRIVER, "");
public static final boolean SDL_GPU_DEBUG = Boolean.getBoolean("angelica.sdlgpu.debug");
private static final SdlAssertionMode ENCODER_ASSERTIONS = parseEnum("angelica.sdlgpu.encoderAssertions", SdlAssertionMode.OFF, SdlAssertionMode.WARN, SdlAssertionMode.class);
public static final boolean SDL_ENCODER_ASSERTIONS = ENCODER_ASSERTIONS != SdlAssertionMode.OFF;
public static final boolean SDL_ENCODER_ASSERTIONS_FATAL = ENCODER_ASSERTIONS == SdlAssertionMode.FATAL;
public static final boolean SDL_VERIFY_STATE_SYNC = Boolean.getBoolean("angelica.sdlgpu.verifyStateSync");
public static final boolean SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK = Boolean.getBoolean("angelica.sdlgpu.verifyPerFrameUniformBlock");
// Tracy
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
public static final boolean TRACY_FINE_ZONES = Boolean.getBoolean("angelica.tracy.fineZones");
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
public static final int TRACY_MAX_SRC_LOCS = Math.max(16, Integer.getInteger("angelica.tracy.maxSrcLocs", 4096));
// Flyby
public static final String FLYBY_ROUTE = System.getProperty("angelica.flyby.route", "");
public static final boolean FLYBY_WAIT_FOR_TRACY = Boolean.getBoolean("angelica.flyby.waitForTracy");
public static final int FLYBY_WARMUP_TICKS = Integer.getInteger("angelica.flyby.warmupTicks", 400);
public static final int FLYBY_LENGTH = Integer.getInteger("angelica.flyby.length", 0); // Blocks for moving routes, ticks for stationary
public static final double FLYBY_SPEED = parseDouble("angelica.flyby.speed"); // Travel speed in blocks per tick
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
// Debug
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
public static final boolean DEBUG_MARKERS = Boolean.getBoolean("angelica.debug.markers");
public static final boolean DEBUG_F3_DETAIL = Boolean.getBoolean("angelica.debug.f3Detail");
public static final boolean ENABLE_TEST_BLOCKS = Boolean.getBoolean("angelica.debug.testBlocks");
public static final boolean DUMP_CLASS = Boolean.getBoolean("angelica.debug.dumpClass");
public static final boolean DUMP_SHADERS = Boolean.getBoolean("angelica.debug.dumpShaders") || isDeobf();
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
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 Path shaderDumpDir(String phase) {
return DUMP_SHADERS ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
}
public enum UnmappedGLMode {
OFF,
WARN,
FAIL,
STRICT;
public boolean detects() {
return this != OFF;
}
public boolean failsUnmapped() {
return this == FAIL || this == STRICT;
}
public boolean failsAwareUnroutable() {
return this == STRICT;
}
}
private enum SdlAssertionMode {
OFF,
WARN,
FATAL
}
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return whenAbsent;
try {
return Enum.valueOf(type, raw.trim().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
LOGGER.warn("Unrecognized value '{}' for -D{}; using {}. Valid values: {}", raw, key, whenInvalid, Arrays.toString(type.getEnumConstants()));
return whenInvalid;
}
}
private static double parseDouble(String key) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return 0.0D;
try {
return Double.parseDouble(raw);
} catch (NumberFormatException e) {
return 0.0D;
}
}
private static boolean isDeobf() {
return Launch.blackboard != null && Boolean.TRUE.equals(Launch.blackboard.get("fml.deobfuscatedEnvironment"));
}
private SystemProperties() {}
}
@@ -1,76 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Locale;
import java.util.function.UnaryOperator;
/** Decides whether instrumentation is worth emitting this frame. */
public final class CaptureGate {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
private static final boolean IS_LINUX = System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("linux");
private static final Path PROC_SELF_MAPS = Paths.get("/proc/self/maps");
private static final String[] CAPTURE_ENV = { "RENDERDOC_CAPTURE_KEYPRESS", "ENABLE_VULKAN_RENDERDOC_CAPTURE", "RENDERDOC_HOOK_EGL" };
private static final String[] CAPTURE_LIBS = { "renderdoc", "glxtrace", "egltrace", "apitrace" };
public static final boolean FORCED = SystemProperties.DEBUG_MARKERS || SystemProperties.LWJGL_DEBUG;
public static final boolean TOOL_ATTACHED = detectCaptureTool();
public static boolean markersThisFrame = true;
private CaptureGate() {}
public static void refresh() {
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, Tracy.isConnected());
}
static boolean gateValue(boolean forced, boolean toolAttached, boolean tracyConnected) {
return forced || toolAttached || tracyConnected;
}
public static boolean enabledAtStartup() {
return FORCED || TOOL_ATTACHED;
}
private static boolean detectCaptureTool() {
if (detectFromEnv(System::getenv)) {
LOGGER.info("Capture tool detected via environment; instrumentation enabled");
return true;
}
if (!IS_LINUX) return false;
try {
if (detectFromMaps(Files.readAllLines(PROC_SELF_MAPS, StandardCharsets.ISO_8859_1))) {
LOGGER.info("Capture tool library detected in process map; instrumentation enabled");
return true;
}
} catch (IOException e) {
LOGGER.warn("Could not read {} for capture tool detection: {}", PROC_SELF_MAPS, e.toString());
}
return false;
}
static boolean detectFromEnv(UnaryOperator<String> env) {
for (String key : CAPTURE_ENV) {
if (env.apply(key) != null) return true;
}
return false;
}
static boolean detectFromMaps(List<String> mapLines) {
for (int i = 0; i < mapLines.size(); i++) {
final String line = mapLines.get(i).toLowerCase(Locale.ROOT);
for (String lib : CAPTURE_LIBS) {
if (line.contains(lib)) return true;
}
}
return false;
}
}
@@ -1,669 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.lwjgl.opengl.GL20;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Compat transformation for mod shaders running under core profile.
*
* <p>Handles:
* <ul>
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
* <li>gl_TexCoord[N] varying array -> per-index in/out declarations</li>
* <li>Texture function renames (texture2D -> texture, etc.)</li>
* <li>Fragment output handling (gl_FragColor -> layout-qualified out declarations)</li>
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
* <li>gl_FrontColor -> local variable in vertex shaders</li>
* <li>shadow2D/shadow2DLod -> texture/textureLod with vec4 wrapping</li>
* <li>Version upgrade to 330 core minimum</li>
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
* </ul>
*/
public class CompatShaderTransformer {
private static final Pattern VERSION_PATTERN = Pattern.compile("#version[ \\t]+(\\d+)(?:[ \\t]+(\\w+))?");
private static final Pattern DEFINE_PATTERN = Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
/** Compat builtins that trigger AST transformation. */
private static final Set<String> COMPAT_BUILTINS = Set.of(
"gl_ModelView", "gl_Projection", "gl_NormalMatrix", "gl_TextureMatrix",
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
"shadow2D", "gl_FrontLightModelProduct",
"gl_LightSource", "gl_FrontMaterial"
);
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
String.join("|", COMPAT_BUILTINS) + "|\\b(?:attribute|varying)\\b"
);
private static final Map<String, String> MATRIX_RENAMES = Map.of(
"gl_ModelViewMatrix", "angelica_ModelViewMatrix",
"gl_ModelViewMatrixInverse", "angelica_ModelViewMatrixInverse",
"gl_ProjectionMatrix", "angelica_ProjectionMatrix",
"gl_ProjectionMatrixInverse", "angelica_ProjectionMatrixInverse",
"gl_NormalMatrix", "angelica_NormalMatrix"
);
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
private static final int CACHE_SIZE = 256;
private record CacheKey(String source, boolean isFragment) {}
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
return size() > CACHE_SIZE;
}
});
private record EsCacheKey(String source, int glShaderType, boolean isFragment) {}
private static final Map<EsCacheKey, String> esCache = Collections.synchronizedMap(
new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry<EsCacheKey, String> eldest) {
return size() > CACHE_SIZE;
}
});
public static void clearCache() {
cache.clear();
esCache.clear();
}
public static void prewarm(String source, int glShaderType, boolean isFragment) {
if (!RenderSystem.isGLES()) return;
final EsCacheKey key = new EsCacheKey(source, glShaderType, isFragment);
if (esCache.containsKey(key)) return;
transform(source, glShaderType, isFragment);
}
/** Transform a mod shader for core profile; under GLES, also rewrites to ES 320. */
public static String transform(String source, boolean isFragment) {
return transform(source, isFragment ? GL20.GL_FRAGMENT_SHADER : GL20.GL_VERTEX_SHADER, isFragment);
}
/** Variant carrying the exact GL shader type so the GLES re-emit dispatches to the right shaderc kind (geometry/tess/compute). */
public static String transform(String source, int glShaderType, boolean isFragment) {
if (!source.isEmpty() && source.charAt(source.length() - 1) == '\0') {
source = source.substring(0, source.length() - 1);
}
final boolean gles = RenderSystem.isGLES();
if (gles) {
final EsCacheKey esKey = new EsCacheKey(source, glShaderType, isFragment);
final String esCached = esCache.get(esKey);
if (esCached != null) {
dumpShader(source, esCached, isFragment, needsTransformation(source));
return esCached;
}
}
final boolean needsTransform = needsTransformation(source);
String result;
if (!needsTransform) {
result = fixupVersion(source);
} else {
final CacheKey key = new CacheKey(source, isFragment);
final String cached = cache.get(key);
if (cached != null) {
result = cached;
} else {
try {
result = transformInternal(source, isFragment);
cache.put(key, result);
} catch (Exception e) {
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
result = fixupVersion(source);
}
}
}
if (gles) {
final String preGles = result;
try {
result = toGLES(result, glShaderType, isFragment);
} catch (RuntimeException e) {
dumpShader(source, preGles, isFragment, needsTransform);
throw e;
}
esCache.put(new EsCacheKey(source, glShaderType, isFragment), result);
}
dumpShader(source, result, isFragment, needsTransform);
return result;
}
public static boolean isCoreShader(String source) {
final Matcher vm = VERSION_PATTERN.matcher(source);
if (vm.find()) {
final int version = Integer.parseInt(vm.group(1));
return version >= 330 && "core".equals(vm.group(2));
}
return false;
}
private static boolean needsTransformation(String source) {
if (isCoreShader(source)) return false;
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
}
private static String transformInternal(String source, boolean isFragment) {
final Matcher versionMatcher = VERSION_PATTERN.matcher(source);
int declaredVersion = 110;
if (versionMatcher.find()) {
declaredVersion = Integer.parseInt(versionMatcher.group(1));
}
final int targetVersion = Math.max(declaredVersion, RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330);
// Pre-parse reserved word renaming - prevents ANTLR parse failures
source = GlslTransformUtils.replaceTexture(source);
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
final Transformer transformer = new Transformer(parsed.full());
injectMatrixUniforms(transformer);
transformFog(transformer, isFragment, source);
// gl_FrontLightModelProduct.sceneColor -> angelica_SceneColor uniform
if (source.contains("gl_FrontLightModelProduct")) {
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
}
// gl_LightSource[i] -> struct + uniform array (both via injectFunction to keep struct before uniform)
if (source.contains("gl_LightSource")) {
transformer.injectFunction("struct angelica_LightSourceParameters {"
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
+ "};");
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
transformer.rename("gl_LightSource", "angelica_LightSource");
}
// gl_FrontMaterial -> struct + uniform (both via injectFunction to keep struct before uniform)
if (source.contains("gl_FrontMaterial")) {
transformer.injectFunction("struct angelica_MaterialParameters {"
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
+ "};");
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
}
// gl_FrontColor (vertex) -> gl_Color (fragment) varying chain
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
if (!isFragment) {
transformer.injectVariable("out vec4 angelica_FrontColor;");
transformer.rename("gl_FrontColor", "angelica_FrontColor");
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
final FFPInputs ffp = FFPInputs.detect(source);
assignVertexInputLocations(transformer, ffp);
transformVertexAttributes(transformer, source, ffp);
} else {
if (source.contains("gl_Color")) {
transformer.injectVariable("in vec4 angelica_FrontColor;");
transformer.rename("gl_Color", "angelica_FrontColor");
}
}
// gl_TexCoord[N] varying array -> per-index in/out declarations
final Set<Integer> texCoordIndices = new HashSet<>();
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
for (Integer i : texCoordIndices) {
final String qualifier = isFragment ? "in" : "out";
transformer.injectVariable(qualifier + " vec4 angelica_TexCoord" + i + ";");
}
// Fragment output handling + alpha test discard
if (isFragment) {
transformFragmentOutputs(transformer);
}
// texture-as-variable collision handling
if (transformer.containsCall("texture") && transformer.hasVariable("texture")) {
transformer.rename("texture", "gtexture");
}
if (transformer.hasVariable("angelica_renamed_texture")) {
transformer.rename("angelica_renamed_texture", "gtexture");
}
transformer.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
transformer.renameAndWrapShadow("shadow2D", "texture");
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
final String versionDirective = "#version " + targetVersion + " core\n";
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsed.pre(), "")).replaceFirst("").trim();
// Preserve #define directives
final StringBuilder defines = new StringBuilder();
final Matcher defineMatcher = DEFINE_PATTERN.matcher(source);
while (defineMatcher.find()) {
defines.append(defineMatcher.group().trim()).append('\n');
}
final String header = versionDirective + (extensions.isEmpty() ? "" : "\n" + extensions) + (defines.isEmpty() ? "" : "\n" + defines);
final StringBuilder result = new StringBuilder();
transformer.mutateTree(tree -> result.append(GlslTransformUtils.getFormattedShader(tree, header)));
// Restore pre-parse renames
String output = GlslTransformUtils.restoreReservedWords(result.toString());
// Core profile: attribute -> in, varying -> out (vertex) / in (fragment)
output = fixupQualifiers(output, isFragment);
return output;
}
/**
* Inject matrix uniforms and rename compat builtins.
*/
private static void injectMatrixUniforms(Transformer transformer) {
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrix;");
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrixInverse;");
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrix;");
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrixInverse;");
transformer.injectVariable("uniform mat3 angelica_NormalMatrix;");
transformer.injectVariable("uniform mat4 angelica_LightmapTextureMatrix;");
transformer.rename(MATRIX_RENAMES);
// Expression replacements
transformer.replaceExpression("gl_ModelViewProjectionMatrix", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix)");
transformer.replaceExpression("gl_TextureMatrix[0]", "mat4(1.0)");
transformer.replaceExpression("gl_TextureMatrix[1]", "angelica_LightmapTextureMatrix");
transformer.replaceExpression(
"gl_TextureMatrix",
"mat4[8](mat4(1.0), angelica_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
}
/**
* Transform fragment outputs for core profile.
*/
private static void transformFragmentOutputs(Transformer transformer) {
if (transformer.containsCall("gl_FragColor")) {
transformer.replaceExpression("gl_FragColor", "gl_FragData[0]");
}
final Set<Integer> found = new HashSet<>();
transformer.renameArray("gl_FragData", "angelica_FragData", found);
for (Integer i : found) {
transformer.injectVariable("layout (location = " + i + ") out vec4 angelica_FragData" + i + ";");
}
// Core profile: GL_ALPHA_TEST is removed - inject runtime discard using GLSM-tracked alpha reference (uploaded by CompatUniformManager).
if (found.contains(0)) {
transformer.injectVariable("uniform float angelica_currentAlphaTest;");
transformer.appendMain("if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;");
}
}
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
if (!isFragment) {
transformer.injectVariable("out float angelica_FogFragCoord;");
transformer.prependMain("angelica_FogFragCoord = 0.0;");
} else {
if (source.contains("gl_FogFragCoord")) {
transformer.injectVariable("in float angelica_FogFragCoord;");
}
}
transformer.rename("gl_Fog", "angelica_Fog");
transformer.injectVariable("uniform float angelica_FogDensity;");
transformer.injectVariable("uniform float angelica_FogStart;");
transformer.injectVariable("uniform float angelica_FogEnd;");
transformer.injectVariable("uniform vec4 angelica_FogColor;");
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
transformer.injectFunction("angelica_FogParameters angelica_Fog;");
transformer.prependMain("angelica_Fog = angelica_FogParameters("
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
}
/**
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
*/
private static void transformVertexAttributes(Transformer transformer, String source, FFPInputs ffp) {
if (ffp.position()) {
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
transformer.rename("gl_Vertex", "angelica_Vertex");
}
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
if (ffp.color()) {
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
transformer.rename("gl_Color", "angelica_Color");
}
if (ffp.uv0()) {
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
}
if (ffp.uv1()) {
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
}
if (ffp.normal()) {
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
transformer.rename("gl_Normal", "angelica_Normal");
}
if (source.contains("ftransform")) {
transformer.replaceExpression("ftransform()", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix * angelica_Vertex)");
}
}
private static final int MAX_VERTEX_ATTRIBS = 16;
private record FFPInputs(boolean position, boolean color, boolean uv0, boolean uv1, boolean normal) {
static FFPInputs detect(String source) {
return new FFPInputs(
source.contains("gl_Vertex") || source.contains("ftransform"),
source.contains("gl_Color"),
source.contains("gl_MultiTexCoord0"),
source.contains("gl_MultiTexCoord1"),
source.contains("gl_Normal"));
}
}
/** Adaptation of Mesa's assign_attribute_or_color_locations */
private static void assignVertexInputLocations(Transformer transformer, FFPInputs ffp) {
int used = 0;
if (ffp.position()) used |= 1 << Usage.POSITION.getAttributeLocation();
if (ffp.color()) used |= 1 << Usage.COLOR.getAttributeLocation();
if (ffp.uv0()) used |= 1 << Usage.PRIMARY_UV.getAttributeLocation();
if (ffp.uv1()) used |= 1 << Usage.SECONDARY_UV.getAttributeLocation();
if (ffp.normal()) used |= 1 << Usage.NORMAL.getAttributeLocation();
record Input(String name, String baseType, String arraySuffix, int slots, int index) {}
final List<Input> toAssign = new ArrayList<>();
int index = 0;
for (TerminalNode storage : transformer.collectStorage()) {
final int tokenType = storage.getSymbol().getType();
if (tokenType != GLSLLexer.ATTRIBUTE && tokenType != GLSLLexer.IN) continue;
ParseTree node = storage.getParent();
while (node != null && !(node instanceof GLSLParser.Single_declarationContext)) {
node = node.getParent();
}
if (!(node instanceof GLSLParser.Single_declarationContext decl)) continue;
if (decl.typeless_declaration() == null) continue;
final GLSLParser.Type_specifierContext typeSpec = decl.fully_specified_type().type_specifier();
final String baseType = GlslTransformUtils.getFormattedShader(typeSpec.type_specifier_nonarray(), "").trim();
final int typeArraySlots = arraySlots(typeSpec.array_specifier());
final String typeArray = arraySuffix(typeSpec.array_specifier());
if (hasLayoutQualifier(decl)) {
final int loc = explicitLocation(decl);
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(decl.typeless_declaration().array_specifier());
if (loc >= 0) used |= slotMask(slots) << loc;
continue;
}
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
declarators.add(decl.typeless_declaration());
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
declarators.addAll(list.typeless_declaration());
}
for (GLSLParser.Typeless_declarationContext d : declarators) {
if (d.IDENTIFIER() == null) continue;
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
toAssign.add(new Input(d.IDENTIFIER().getText(), baseType, arraySuffix, slots, index++));
}
}
toAssign.sort((a, b) -> a.slots() != b.slots() ? b.slots() - a.slots() : a.index() - b.index());
for (Input in : toAssign) {
final int loc = findAvailableSlots(used, in.slots());
if (loc < 0) continue;
used |= slotMask(in.slots()) << loc;
transformer.removeVariable(in.name());
transformer.variable = null;
transformer.injectVariable("layout(location = " + loc + ") in " + in.baseType() + " " + in.name() + in.arraySuffix() + ";");
}
}
private static String arraySuffix(GLSLParser.Array_specifierContext arraySpec) {
return arraySpec != null ? GlslTransformUtils.getFormattedShader(arraySpec, "").trim() : "";
}
private static int arraySlots(GLSLParser.Array_specifierContext arraySpec) {
if (arraySpec == null) return 1;
int product = 1;
for (GLSLParser.DimensionContext dim : arraySpec.dimension()) {
if (dim.constant_expression() == null) continue;
try {
product *= Integer.decode(GlslTransformUtils.getFormattedShader(dim.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {}
}
return product;
}
private static int baseSlots(String type) {
final String dims = type.startsWith("dmat") ? type.substring(4) : type.startsWith("mat") ? type.substring(3) : null;
if (dims != null && !dims.isEmpty() && Character.isDigit(dims.charAt(0))) {
return dims.charAt(0) - '0';
}
return 1;
}
private static int slotMask(int slots) {
return (slots >= 32) ? -1 : (1 << slots) - 1;
}
private static int findAvailableSlots(int used, int needed) {
if (needed <= 0) return -1;
final int mask = slotMask(needed);
for (int i = 0; i <= MAX_VERTEX_ATTRIBS - needed; i++) {
if (((mask << i) & ~used) == (mask << i)) return i;
}
return -1;
}
private static int explicitLocation(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return -1;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
final GLSLParser.Layout_qualifierContext layout = single.layout_qualifier();
if (layout == null) continue;
for (GLSLParser.Layout_qualifier_idContext id : layout.layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() != null && "location".equals(id.IDENTIFIER().getText()) && id.constant_expression() != null) {
try {
return Integer.decode(GlslTransformUtils.getFormattedShader(id.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {
return -1;
}
}
}
}
return -1;
}
private static boolean hasLayoutQualifier(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return false;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
if (single.layout_qualifier() != null) return true;
}
return false;
}
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
final Path dir = SystemProperties.shaderDumpDir("compat");
if (dir == null) return;
final int id = dumpCounter.getAndIncrement();
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
try {
Files.createDirectories(dir);
// Capture caller info for identification
final String caller = identifyCaller();
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
+ "\n// Transformed: " + wasTransformed
+ "\n// Caller: " + caller + "\n\n";
Files.writeString(dir.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
Files.writeString(dir.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
}
}
private static String identifyCaller() {
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
final String cls = frame.getClassName();
if (!cls.startsWith("com.gtnewhorizons.angelica.glsm.")
&& !cls.startsWith("java.")
&& !cls.equals("org.lwjgl.opengl.GL20")) {
return cls + "." + frame.getMethodName() + ":" + frame.getLineNumber();
}
}
return "unknown";
}
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\battribute\\b");
private static final Pattern VARYING_PATTERN = Pattern.compile("\\bvarying\\b");
/** Replace legacy storage qualifiers removed in core profile. Safe on AST-serialized output (no comments). */
public static String fixupQualifiers(String source, boolean isFragment) {
source = ATTRIBUTE_PATTERN.matcher(source).replaceAll("in");
source = VARYING_PATTERN.matcher(source).replaceAll(isFragment ? "in" : "out");
return source;
}
private static final String PRECISION_QUALIFIER = "(?:highp\\s+|mediump\\s+|lowp\\s+)?";
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "(vec[234])\\s+angelica_FrontColor\\b");
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "(vec[234])\\s+angelica_TexCoord(\\d+)\\b");
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "float\\s+angelica_FogFragCoord\\b");
/**
* Generate a passthrough vertex shader for a fragment-only program.
*
* @param fragmentSource the transformed fragment shader source
* @return a complete vertex shader source string
*/
public static String generatePassthroughVertexShader(String fragmentSource) {
final StringBuilder sb = new StringBuilder(512);
sb.append("#version 330 core\n\n");
// Always have position
sb.append("layout(location = 0) in vec4 a_Position;\n");
// Matrix uniforms for position transform
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
final StringBuilder varyings = new StringBuilder();
final StringBuilder assignments = new StringBuilder();
final Matcher colorMatcher = FRAG_IN_COLOR.matcher(fragmentSource);
if (colorMatcher.find()) {
final String colorType = colorMatcher.group(1);
sb.append("layout(location = 1) in ").append(colorType).append(" a_Color;\n");
varyings.append("out ").append(colorType).append(" angelica_FrontColor;\n");
assignments.append(" angelica_FrontColor = a_Color;\n");
}
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
final Map<Integer, String> texCoordTypes = new HashMap<>();
while (texCoordMatcher.find()) {
texCoordTypes.putIfAbsent(Integer.parseInt(texCoordMatcher.group(2)), texCoordMatcher.group(1));
}
for (Map.Entry<Integer, String> e : texCoordTypes.entrySet()) {
final int i = e.getKey();
final String type = e.getValue();
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
final int location = i == 0 ? 2 : 3;
sb.append("layout(location = ").append(location).append(") in ").append(type).append(" a_TexCoord").append(i).append(";\n");
varyings.append("out ").append(type).append(" angelica_TexCoord").append(i).append(";\n");
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
}
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
varyings.append("out float angelica_FogFragCoord;\n");
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
}
sb.append(varyings);
sb.append("\nvoid main() {\n");
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
sb.append(assignments);
sb.append("}\n");
return sb.toString();
}
/** 330-core -> GLSL ES 320 via shaderc + SPIRV-Cross. Throws on shaderc failure. */
public static String toGLES(String source, int glShaderType, boolean isFragment) {
final Matcher m = VERSION_PATTERN.matcher(source);
if (m.find() && "es".equalsIgnoreCase(m.group(2))) return source;
if (glShaderType == 0) throw new IllegalArgumentException("toGLES requires a valid GL shader type");
final String translated = SpirvShaderTranslator.glslToGlslEs(source, glShaderType, "compat");
if (translated == null) {
throw new IllegalStateException("SpirvShaderTranslator failed for shader kind 0x"
+ Integer.toHexString(glShaderType) + "; see preceding shaderc error + run/client/angelica_dumps/compat/");
}
return translated;
}
public static String toGLES(String source, boolean isFragment) {
return toGLES(source, isFragment ? GL20.GL_FRAGMENT_SHADER : GL20.GL_VERTEX_SHADER, isFragment);
}
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
private static String fixupVersion(String source) {
final Matcher m = VERSION_PATTERN.matcher(source);
if (!m.find()) {
return "#version 330 core\n" + source;
}
final int version = Integer.parseInt(m.group(1));
final String profile = m.group(2);
if (version >= 330 && !"compatibility".equals(profile)) {
if ("core".equals(profile)) return source;
return m.replaceFirst("#version " + version + " core");
}
final int targetVersion = Math.max(version, 330);
return m.replaceFirst("#version " + targetVersion + " core");
}
}
@@ -1,452 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Per-program compat uniform location cache and upload manager.
*
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader
* programs.
*/
public class CompatUniformManager {
// Uniform location indices within the cached array (package-private for tests)
static final int LOC_MODELVIEW = 0;
static final int LOC_MODELVIEW_INVERSE = 1;
static final int LOC_PROJECTION = 2;
static final int LOC_PROJECTION_INVERSE = 3;
static final int LOC_NORMAL = 4;
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
static final int LOC_FOG_DENSITY = 6;
static final int LOC_FOG_START = 7;
static final int LOC_FOG_END = 8;
static final int LOC_FOG_COLOR = 9;
static final int LOC_IRIS_MODELVIEW = 10;
static final int LOC_IRIS_PROJECTION = 11;
static final int LOC_IRIS_NORMAL = 12;
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
static final int LOC_IRIS_TEXTURE_MATRIX = 14;
static final int LOC_ALPHA_TEST_REF = 15;
static final int LOC_SCENE_COLOR = 16;
static final int LOC_CLIP_PLANES = 17;
static final int LOC_CLIP_PLANES_ENABLED = 18;
// Light source fields: 12 per light × 2 lights = 24 locations
static final int LIGHT_FIELDS = 12;
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
static final int LOC_LIGHT_BASE = 19;
// Material fields: 5 locations
static final int MF_EMISSION = 0, MF_AMBIENT = 1, MF_DIFFUSE = 2, MF_SPECULAR = 3, MF_SHININESS = 4;
static final int MAT_FIELDS = 5;
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 43
static final int LOC_IRIS_MODELVIEW_INVERSE = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_PROJECTION_INVERSE = LOC_IRIS_MODELVIEW_INVERSE + 1; // 49
static final int LOC_COUNT = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
};
private static final String[] MAT_FIELD_NAMES = {
"emission", "ambient", "diffuse", "specular", "shininess",
};
private static final String[] UNIFORM_NAMES;
static {
UNIFORM_NAMES = new String[LOC_COUNT];
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW_INVERSE] = "iris_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_IRIS_PROJECTION_INVERSE] = "iris_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
UNIFORM_NAMES[LOC_CLIP_PLANES_ENABLED] = "angelica_ClipPlanesEnabled";
for (int li = 0; li < 2; li++) {
for (int fi = 0; fi < LIGHT_FIELDS; fi++) {
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] = "angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
}
}
for (int fi = 0; fi < MAT_FIELDS; fi++) {
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
}
}
/** Per-program cached uniform locations + last-uploaded generations. Maps program ID -> state. */
private static final Int2ObjectOpenHashMap<ProgramUniforms> programUniforms = new Int2ObjectOpenHashMap<>();
private static final class ProgramUniforms {
final int[] locs;
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
ProgramUniforms(int[] locs) { this.locs = locs; }
}
// Reusable NIO buffers for upload
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
private static final Matrix3f normalMatrix = new Matrix3f();
private static final Matrix4f scratchMatrix = new Matrix4f();
private static final Vector3f scratchCol = new Vector3f();
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
private static final FloatBuffer lightmapMatrixBuf;
static {
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
}
private CompatUniformManager() {}
/**
* Normalize each column of a 3x3 matrix in place. Used to strip the residual 1/scale that
* inverse-transpose introduces.
*/
private static void normalizeColumnsInPlace(Matrix3f m) {
m.setColumn(0, m.getColumn(0, scratchCol).normalize());
m.setColumn(1, m.getColumn(1, scratchCol).normalize());
m.setColumn(2, m.getColumn(2, scratchCol).normalize());
}
public static void onLinkProgram(int program) {
int[] locs = new int[LOC_COUNT];
boolean hasAny = false;
for (int i = 0; i < LOC_COUNT; i++) {
locs[i] = RENDER_BACKEND.getUniformLocation(program, UNIFORM_NAMES[i]);
if (locs[i] != -1) hasAny = true;
}
if (hasAny) {
programUniforms.put(program, new ProgramUniforms(locs));
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
/**
* Uploads the emulated uniforms.
*
* @return Whether the program needs emulation (FFP or Iris Program)
*/
public static boolean refreshCompatUniforms(int program) {
final ProgramUniforms pu = programUniforms.get(program);
if (pu == null) return false;
final int[] locs = pu.locs;
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.getMvGeneration();
final int mvLinearGen = GLStateManager.getMvLinearGeneration();
final int projGen = GLStateManager.getProjGeneration();
final int texMatGen = GLStateManager.getTexMatrixGeneration();
final boolean mvChanged = mvGen != pu.lastMvGen;
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
final boolean projChanged = projGen != pu.lastProjGen;
final boolean texMatChanged = texMatGen != pu.lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(locs, mvChanged, mvLinearChanged, projChanged, texMatChanged);
pu.lastMvGen = mvGen;
pu.lastMvLinearGen = mvLinearGen;
pu.lastProjGen = projGen;
pu.lastTexMatGen = texMatGen;
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.getFragmentGeneration();
if (fragGen != pu.lastFragmentGen) {
pu.lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.getLightingGeneration();
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
uploadMaterial(locs);
}
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.getClipPlaneGeneration();
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
return true;
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
if (mvChanged) {
// ModelView
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
mv.get(mat4Buf);
if (locs[LOC_MODELVIEW] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
}
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1 || locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW_INVERSE], false, mat4Buf);
}
}
}
if (mvLinearChanged) {
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
mv.normal(normalMatrix);
if (locs[LOC_NORMAL] != -1) {
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
}
// Column-normalized to unit magnitude
if (locs[LOC_IRIS_NORMAL] != -1) {
normalizeColumnsInPlace(normalMatrix);
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
}
if (projChanged) {
// Projection
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
proj.get(mat4Buf);
if (locs[LOC_PROJECTION] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
}
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1 || locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
if (locs[LOC_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION_INVERSE], false, mat4Buf);
}
}
}
if (texMatChanged) {
// Iris lightmap texture matrix (constant)
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
}
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
}
// iris_TextureMatrix — texture unit 0 matrix (animated by glints, etc.)
if (locs[LOC_IRIS_TEXTURE_MATRIX] != -1) {
GLStateManager.getTextures().getTextureUnitMatrix(0).get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_TEXTURE_MATRIX], false, mat4Buf);
}
}
}
private static void uploadFragmentUniforms(int[] locs) {
// Fog uniforms
FogStateStack fog = GLStateManager.getFogState();
if (locs[LOC_FOG_DENSITY] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
}
if (locs[LOC_FOG_START] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_START], fog.getStart());
}
if (locs[LOC_FOG_END] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_END], fog.getEnd());
}
if (locs[LOC_FOG_COLOR] != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y).put((float) fog.getFogColor().z).put(fog.getFogAlpha());
vec4Buf.flip();
RENDER_BACKEND.uniform4(locs[LOC_FOG_COLOR], vec4Buf);
}
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
if (locs[LOC_ALPHA_TEST_REF] != -1) {
final float ref;
if (!GLStateManager.getAlphaTest().isEnabled() || GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
ref = -1.0f;
} else {
ref = GLStateManager.getAlphaState().getReference();
}
RENDER_BACKEND.uniform1f(locs[LOC_ALPHA_TEST_REF], ref);
}
}
/**
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a Same computation as {@code Uniforms.uploadLightingPreComputed()}.
*/
private static void uploadSceneColor(int[] locs) {
final MaterialState mat = GLStateManager.getFrontMaterial();
final LightModelState lm = GLStateManager.getLightModel();
vec4Buf.clear();
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
.put(mat.diffuse.w);
vec4Buf.flip();
RENDER_BACKEND.uniform4(locs[LOC_SCENE_COLOR], vec4Buf);
}
private static void uploadLightSources(int[] locs) {
// Early exit: if the first light field isn't present, no lighting uniforms exist
if (locs[LOC_LIGHT_BASE] == -1) return;
final LightStateStack[] lights = GLStateManager.getLightDataStates();
for (int li = 0; li < 2; li++) {
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
final LightState light = lights[li];
if (locs[base + LF_AMBIENT] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
if (locs[base + LF_DIFFUSE] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
if (locs[base + LF_SPECULAR] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
if (locs[base + LF_POSITION] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
if (locs[base + LF_HALF_VECTOR] != -1) {
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
float hx, hy, hz;
if (light.position.w == 0.0f) {
final float len = (float) Math.sqrt(light.position.x * light.position.x
+ light.position.y * light.position.y
+ light.position.z * light.position.z);
if (len > 0) {
hx = light.position.x / len;
hy = light.position.y / len;
hz = light.position.z / len + 1.0f;
} else {
hx = 0; hy = 0; hz = 1;
}
} else {
hx = 0; hy = 0; hz = 1;
}
final float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
RENDER_BACKEND.uniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
}
if (locs[base + LF_SPOT_DIRECTION] != -1) RENDER_BACKEND.uniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
if (locs[base + LF_SPOT_EXPONENT] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
if (locs[base + LF_SPOT_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
if (locs[base + LF_SPOT_COS_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
if (locs[base + LF_CONSTANT_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
if (locs[base + LF_LINEAR_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
if (locs[base + LF_QUADRATIC_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
}
}
private static void uploadMaterial(int[] locs) {
// Early exit: if the first material field isn't present, no material uniforms exist
if (locs[LOC_MAT_BASE] == -1) return;
final MaterialState mat = GLStateManager.getFrontMaterial();
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
}
private static void uploadClipPlanes(int[] locs) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
}
public static void onDeleteProgram(int program) {
programUniforms.remove(program);
}
public static boolean hasProgram(int program) {
return programUniforms.containsKey(program);
}
public static int[] getLocations(int program) {
final ProgramUniforms pu = programUniforms.get(program);
return pu == null ? null : pu.locs;
}
}
@@ -1,187 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
/*
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
*
* Original copyright and license:
*
* Copyright 2017 Valve Corporation
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sub license, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Original author: Samuel Pitoiset <[email protected]>
*/
/**
* Bitmap ID allocator for display list IDs.
*
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
*/
public class DisplayListIDAllocator {
private int[] data;
private int numElements; // number of uint32 words allocated
private int numSetElements; // highest word index with any bit set + 1
private int lowestFreeIdx; // optimization: skip fully-allocated words
public DisplayListIDAllocator() {
this(32); // 1024 IDs initially
}
public DisplayListIDAllocator(int initialWords) {
if (initialWords < 1) initialWords = 1;
this.data = new int[initialWords];
this.numElements = initialWords;
this.numSetElements = 0;
this.lowestFreeIdx = 0;
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
data[0] = 1;
numSetElements = 1;
}
public int allocRange(int count) {
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
if (count == 1) return allocSingle();
return allocMultiple(count);
}
private int allocSingle() {
for (int i = lowestFreeIdx; i < numElements; i++) {
if (data[i] == 0xFFFFFFFF) continue;
int bit = Integer.numberOfTrailingZeros(~data[i]);
data[i] |= 1 << bit;
lowestFreeIdx = i;
numSetElements = Math.max(numSetElements, i + 1);
return i * 32 + bit;
}
// No slots available, grow. Record old end, then mark first bit there.
int oldNumElements = numElements;
resize(Math.max(numElements, 1) * 2);
lowestFreeIdx = oldNumElements;
data[oldNumElements] |= 1;
numSetElements = Math.max(numSetElements, oldNumElements + 1);
return oldNumElements * 32;
}
private int allocMultiple(int num) {
int numWords = divRoundUp(num, 32);
int base = findFreeBlock(lowestFreeIdx);
boolean found = false;
while (!found) {
int i;
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
;
if (i - base == numWords) {
found = true;
} else if (i == numElements) {
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
resize(numElements * 2 + numWords);
found = true;
} else {
// Non-zero word at i, skip past it
base = i + 1;
}
}
// Mark bits as used
int fullWords = num / 32;
int remainder = num % 32;
for (int i = base; i < base + fullWords; i++) {
data[i] = 0xFFFFFFFF;
}
if (remainder != 0) {
data[base + fullWords] |= (1 << remainder) - 1;
}
if (lowestFreeIdx == base) {
lowestFreeIdx = base + num / 32;
}
numSetElements = Math.max(numSetElements, base + numWords);
return base * 32;
}
private int findFreeBlock(int start) {
for (int i = start; i < numElements; i++) {
if (data[i] == 0) return i;
}
return numElements;
}
public void free(int id) {
if (id <= 0) return; // ID 0 is reserved
int idx = id / 32;
if (idx >= numElements) return;
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
data[idx] &= ~(1 << (id % 32));
if (numSetElements == idx + 1) {
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
numSetElements--;
}
}
}
public void freeRange(int base, int count) {
if (count <= 0 || base <= 0) return;
int startIdx = base / 32;
if (startIdx >= numElements) return;
int end = base + count;
for (int id = base; id < end; id++) {
int idx = id / 32;
if (idx >= numElements) break;
data[idx] &= ~(1 << (id % 32));
}
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
numSetElements--;
}
}
public boolean isAllocated(int id) {
if (id <= 0) return false;
int idx = id / 32;
if (idx >= numSetElements) return false;
return (data[idx] & (1 << (id % 32))) != 0;
}
private void resize(int newNumElements) {
if (newNumElements <= numElements) return;
int[] newData = new int[newNumElements];
System.arraycopy(data, 0, newData, 0, numElements);
data = newData;
numElements = newNumElements;
}
private static int divRoundUp(int a, int b) {
return (a + b - 1) / b;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,345 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Feature {
private static final int[] supportedAttribs = new int[] { GL11.GL_ACCUM_BUFFER_BIT, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_CURRENT_BIT, GL11.GL_DEPTH_BUFFER_BIT,
GL11.GL_ENABLE_BIT, GL11.GL_EVAL_BIT, GL11.GL_FOG_BIT, GL11.GL_HINT_BIT, GL11.GL_LIGHTING_BIT, GL11.GL_LINE_BIT, GL11.GL_LIST_BIT,
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
static List<IStateStack<?>> maskToFeatures(int mask) {
if(maskToFeaturesMap.containsKey(mask)) {
return maskToFeaturesMap.get(mask);
}
final Set<IStateStack<?>> features = new HashSet<>();
for(int attrib : Feature.supportedAttribs) {
if((mask & attrib) == attrib) {
features.addAll(getFeatures(attrib));
}
}
final List<IStateStack<?>> asList = new ArrayList<>(features);
maskToFeaturesMap.put(mask, asList);
return asList;
}
/**
* Returns only non-BooleanStateStack instances for the given mask.
* These use traditional push/pop without global depth tracking.
*/
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
if (cached != null) {
return cached;
}
final List<IStateStack<?>> all = maskToFeatures(mask);
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
for (int i = 0; i < all.size(); i++) {
final IStateStack<?> stack = all.get(i);
if (!(stack instanceof BooleanStateStack)) {
nonBooleans.add(stack);
}
}
cached = nonBooleans.toArray(new IStateStack<?>[0]);
maskToNonBooleanStacksMap.put(mask, cached);
return cached;
}
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
/**
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
* Used by both GL_ENABLE_BIT and GL_TEXTURE_BIT.
*/
private static void addTextureGenStates(Set<IStateStack<?>> collection) {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
collection.add(GLStateManager.getTextures().getTexGenSStates(i));
collection.add(GLStateManager.getTextures().getTexGenTStates(i));
collection.add(GLStateManager.getTextures().getTexGenRStates(i));
collection.add(GLStateManager.getTextures().getTexGenQStates(i));
}
}
static {
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
GLStateManager.getAlphaTest()
, GLStateManager.getAlphaState()
, GLStateManager.getBlendMode()
, GLStateManager.getBlendState()
, GLStateManager.getColorLogicOpState()
, GLStateManager.getDitherState()
, GLStateManager.getDrawBuffer()
, GLStateManager.getIndexLogicOpState()
, GLStateManager.getLogicOpMode()
, GLStateManager.getColorMask()
, GLStateManager.getClearColor()
));
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
GLStateManager.getColor()
, ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates
// Current color index
// Current raster position
// GL_CURRENT_RASTER_POSITION_VALID flag
// RGBA color associated with current raster position
// Color index associated with current raster position
// Texture coordinates associated with current raster position
// GL_EDGE_FLAG flag
));
attribToFeatures.put(GL11.GL_DEPTH_BUFFER_BIT, ImmutableSet.of(
GLStateManager.getDepthTest()
, GLStateManager.getDepthState()
// Depth buffer clear value
// GL_DEPTH_WRITEMASK enable bit
));
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
GLStateManager.getAlphaTest()
, GLStateManager.getAutoNormalState()
, GLStateManager.getBlendMode()
, GLStateManager.getColorLogicOpState()
, GLStateManager.getColorMaterial()
, GLStateManager.getCullState()
, GLStateManager.getDepthTest()
, GLStateManager.getDitherState()
, GLStateManager.getFogMode()
, GLStateManager.getIndexLogicOpState()
, GLStateManager.getLightStates()[0] // GL_LIGHT0
, GLStateManager.getLightStates()[1] // GL_LIGHT1
, GLStateManager.getLightStates()[2] // GL_LIGHT2
, GLStateManager.getLightStates()[3] // GL_LIGHT3
, GLStateManager.getLightStates()[4] // GL_LIGHT4
, GLStateManager.getLightStates()[5] // GL_LIGHT5
, GLStateManager.getLightStates()[6] // GL_LIGHT6
, GLStateManager.getLightStates()[7] // GL_LIGHT7
, GLStateManager.getLightingState()
, GLStateManager.getLineSmoothState()
, GLStateManager.getLineStippleState()
, GLStateManager.getMap1Color4State()
, GLStateManager.getMap1IndexState()
, GLStateManager.getMap1NormalState()
, GLStateManager.getMap1TextureCoord1State()
, GLStateManager.getMap1TextureCoord2State()
, GLStateManager.getMap1TextureCoord3State()
, GLStateManager.getMap1TextureCoord4State()
, GLStateManager.getMap1Vertex3State()
, GLStateManager.getMap1Vertex4State()
, GLStateManager.getMap2Color4State()
, GLStateManager.getMap2IndexState()
, GLStateManager.getMap2NormalState()
, GLStateManager.getMap2TextureCoord1State()
, GLStateManager.getMap2TextureCoord2State()
, GLStateManager.getMap2TextureCoord3State()
, GLStateManager.getMap2TextureCoord4State()
, GLStateManager.getMap2Vertex3State()
, GLStateManager.getMap2Vertex4State()
, GLStateManager.getMultisampleState()
, GLStateManager.getNormalizeState()
, GLStateManager.getPointSmoothState()
, GLStateManager.getPolygonOffsetPointState()
, GLStateManager.getPolygonOffsetLineState()
, GLStateManager.getPolygonOffsetFillState()
, GLStateManager.getPolygonSmoothState()
, GLStateManager.getPolygonStippleState()
, GLStateManager.getRescaleNormalState()
, GLStateManager.getSampleAlphaToCoverageState()
, GLStateManager.getSampleAlphaToOneState()
, GLStateManager.getSampleCoverageState()
, GLStateManager.getScissorTest()
, GLStateManager.getStencilTest()
));
// Enable bits for the user-definable clipping planes
for(int i = 0; i < GLStateManager.getClipPlaneStates().length; i++) {
enableBits.add(GLStateManager.getClipPlaneStates()[i]);
}
// GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
enableBits.add(GLStateManager.getTextures().getTexture1DStates(i));
enableBits.add(GLStateManager.getTextures().getTextureUnitStates(i));
enableBits.add(GLStateManager.getTextures().getTexture3DStates(i));
}
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
addTextureGenStates(enableBits);
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
// GL_MAP1_x enable bits, where x is a map type
// GL_MAP2_x enable bits, where x is a map type
// 1D grid endpoints and divisions
// 2D grid endpoints and divisions
// GL_AUTO_NORMAL enable bit
));
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
GLStateManager.getFogMode()
, GLStateManager.getFogState()
// ^^ Fog density
// ^^ Linear fog start
// ^^ Linear fog end
// Fog index
// ^^ GL_FOG_MODE value
));
attribToFeatures.put(GL11.GL_HINT_BIT, ImmutableSet.of(
// GL_PERSPECTIVE_CORRECTION_HINT setting
// GL_POINT_SMOOTH_HINT setting
// GL_LINE_SMOOTH_HINT setting
// GL_POLYGON_SMOOTH_HINT setting
// GL_FOG_HINT setting
// GL_GENERATE_MIPMAP_HINT setting
// GL_TEXTURE_COMPRESSION_HINT setting
));
attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of(
GLStateManager.getColorMaterial()
, GLStateManager.getColorMaterialFace()
, GLStateManager.getColorMaterialParameter()
, GLStateManager.getLightModel()
, GLStateManager.getLightingState()
// Enable bit for each light
, GLStateManager.getLightStates()[0] // GL_LIGHT0
, GLStateManager.getLightStates()[1] // GL_LIGHT1
, GLStateManager.getLightStates()[2] // GL_LIGHT2
, GLStateManager.getLightStates()[3] // GL_LIGHT3
, GLStateManager.getLightStates()[4] // GL_LIGHT4
, GLStateManager.getLightStates()[5] // GL_LIGHT5
, GLStateManager.getLightStates()[6] // GL_LIGHT6
, GLStateManager.getLightStates()[7] // GL_LIGHT7
// Ambient, diffuse, and specular intensity for each light
// Direction, position, exponent, and cutoff angle for each light
// Constant, linear, and quadratic attenuation factors for each light
, GLStateManager.getLightDataStates()[0]
, GLStateManager.getLightDataStates()[1]
, GLStateManager.getLightDataStates()[2]
, GLStateManager.getLightDataStates()[3]
, GLStateManager.getLightDataStates()[4]
, GLStateManager.getLightDataStates()[5]
, GLStateManager.getLightDataStates()[6]
, GLStateManager.getLightDataStates()[7]
// Ambient, diffuse, specular, and emissive color for each material
// Ambient, diffuse, and specular color indices for each material
// Specular exponent for each material
, GLStateManager.getFrontMaterial()
, GLStateManager.getBackMaterial()
, GLStateManager.getShadeModelState()
));
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
GLStateManager.getLineSmoothState()
, GLStateManager.getLineStippleState()
, GLStateManager.getLineState()
));
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
// GL_LIST_BASE setting
));
attribToFeatures.put(GL13.GL_MULTISAMPLE_BIT, ImmutableSet.of(
// GL_MULTISAMPLE enable bit
// GL_SAMPLE_ALPHA_TO_COVERAGE flag
// GL_SAMPLE_ALPHA_TO_ONE flag
// GL_SAMPLE_COVERAGE flag
// GL_SAMPLE_COVERAGE_VALUE value
// GL_SAMPLE_COVERAGE_INVERT value
));
attribToFeatures.put(GL11.GL_PIXEL_MODE_BIT, ImmutableSet.of(
// GL_RED_BIAS and GL_RED_SCALE settings
// GL_GREEN_BIAS and GL_GREEN_SCALE values
// GL_BLUE_BIAS and GL_BLUE_SCALE
// GL_ALPHA_BIAS and GL_ALPHA_SCALE
// GL_DEPTH_BIAS and GL_DEPTH_SCALE
// GL_INDEX_OFFSET and GL_INDEX_SHIFT values
// GL_MAP_COLOR and GL_MAP_STENCIL flags
// GL_ZOOM_X and GL_ZOOM_Y factors
// GL_READ_BUFFER setting
));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
GLStateManager.getPointSmoothState()
, GLStateManager.getPointState()
));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
GLStateManager.getCullState()
, GLStateManager.getPolygonSmoothState()
, GLStateManager.getPolygonStippleState()
, GLStateManager.getPolygonOffsetFillState()
, GLStateManager.getPolygonOffsetLineState()
, GLStateManager.getPolygonOffsetPointState()
, GLStateManager.getPolygonState()
));
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
// Polygon stipple pattern
));
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
GLStateManager.getScissorTest()
// Scissor box
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.getStencilTest()
, GLStateManager.getStencilState()
));
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
GLStateManager.getActiveTextureUnitStack()
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
// Enable bits for the four texture coordinates
// Border color for each texture image
// Minification function for each texture image
// Magnification function for each texture image
// Texture coordinates and wrap mode for each texture image
// Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
// GL_TEXTURE_GEN_MODE setting for S, T, R, and Q
// glTexGen plane equations for S, T, R, and Q
// Current texture bindings (for example, GL_TEXTURE_BINDING_2D) - Below
));
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.getTextures().getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.getTextures().getTexEnvState(i));
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
addTextureGenStates(textureAttribs);
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
// Coefficients of the six clipping planes
GLStateManager.getMatrixMode()
, GLStateManager.getNormalizeState()
, GLStateManager.getRescaleNormalState()
));
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
GLStateManager.getViewportState()
));
}
public static Set<IStateStack<?>> getFeatures(int attrib) {
return attribToFeatures.getOrDefault(attrib, Collections.emptySet());
}
}
@@ -1,397 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Software emulation of GL feedback mode
*/
public class FeedbackManager {
private static final int FB_3D = 0x01;
private static final int FB_4D = 0x02;
private static final int FB_COLOR = 0x04;
private static final int FB_TEXTURE = 0x08;
private static int renderMode = GL11.GL_RENDER;
private static FloatBuffer feedbackBuffer;
private static int feedbackBufferSize;
private static int feedbackMask;
private static int feedbackCount;
private static final Matrix4f mvpMatrix = new Matrix4f();
private static final Vector4f clipVec = new Vector4f();
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
@FunctionalInterface
private interface IndexReader {
int read(int i);
}
public static boolean isFeedbackMode() {
return renderMode == GL11.GL_FEEDBACK;
}
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
if (renderMode == GL11.GL_FEEDBACK) {
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
return;
}
feedbackBuffer = buffer;
feedbackBufferSize = buffer.remaining();
feedbackMask = computeMask(type);
feedbackCount = 0;
}
public static int glRenderMode(int mode) {
int result = switch (renderMode) {
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
default -> 0;
};
if (mode == GL11.GL_SELECT) {
GLStateManager.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
}
feedbackCount = 0;
renderMode = mode;
return result;
}
public static void glPassThrough(float token) {
if (renderMode == GL11.GL_FEEDBACK) {
feedbackToken(GL11.GL_PASS_THROUGH_TOKEN);
feedbackToken(token);
}
}
private static void feedbackToken(float token) {
if (feedbackCount < feedbackBufferSize) {
feedbackBuffer.put(feedbackBuffer.position() + feedbackCount, token);
}
feedbackCount++;
}
private static void feedbackVertex(float winX, float winY, float winZ, float winW) {
feedbackToken(winX);
feedbackToken(winY);
if ((feedbackMask & FB_3D) != 0) feedbackToken(winZ);
if ((feedbackMask & FB_4D) != 0) feedbackToken(winW);
if ((feedbackMask & FB_COLOR) != 0) {
feedbackToken(1f);
feedbackToken(1f);
feedbackToken(1f);
feedbackToken(1f);
}
if ((feedbackMask & FB_TEXTURE) != 0) {
feedbackToken(0f);
feedbackToken(0f);
feedbackToken(0f);
feedbackToken(1f);
}
}
private static void transformAndEmitVertex(float x, float y, float z) {
clipVec.set(x, y, z, 1.0f);
mvpMatrix.transform(clipVec);
final float w = clipVec.w;
final float ndcX = clipVec.x / w;
final float ndcY = clipVec.y / w;
final float ndcZ = clipVec.z / w;
final ViewportState vp = GLStateManager.getViewportState();
final float winX = vp.x + vp.width * (ndcX + 1.0f) * 0.5f;
final float winY = vp.y + vp.height * (ndcY + 1.0f) * 0.5f;
final float winZ = (float) ((vp.depthRangeFar - vp.depthRangeNear) * ndcZ + (vp.depthRangeFar + vp.depthRangeNear)) * 0.5f;
final float winW = 1.0f / w;
feedbackVertex(winX, winY, winZ, winW);
}
private static int positionStride() {
final int s = VAOManager.getAttribEffectiveStride(0);
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VAOManager.getAttribOffset(0);
}
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final int stride = positionStride();
final long posOffset = positionOffset();
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) first * stride + posOffset, readbackBuf);
emitAllPrimitives(mode, count, i -> i, stride);
}
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final int stride = positionStride();
final int elementSize = GLTypes.sizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
final ByteBuffer indexBuf = indexReadbackBuf;
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < indexCount; i++) {
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
}
public static void processDrawElements(int mode, ByteBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFF);
}
public static void processDrawElements(int mode, ShortBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFFFF);
}
public static void processDrawElements(int mode, IntBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i));
}
public static void processDrawElements(int mode, int count, int type, ByteBuffer indices) {
final int pos = indices.position();
final IndexReader reader = switch (type) {
case GL11.GL_UNSIGNED_BYTE -> i -> indices.get(pos + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> i -> indices.getShort(pos + i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> i -> indices.getInt(pos + i * 4);
default -> i -> 0;
};
processDrawElementsImpl(mode, count, reader);
}
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final int stride = positionStride();
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
final int idx = reader.read(i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
readVerticesFromVBO(minIdx, maxIdx, stride);
final int base = minIdx;
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
}
private static void computeMVP() {
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
}
private static int computeMask(int type) {
return switch (type) {
case GL11.GL_2D -> 0;
case GL11.GL_3D -> FB_3D;
case GL11.GL_3D_COLOR -> FB_3D | FB_COLOR;
case GL11.GL_3D_COLOR_TEXTURE -> FB_3D | FB_COLOR | FB_TEXTURE;
case GL11.GL_4D_COLOR_TEXTURE -> FB_3D | FB_4D | FB_COLOR | FB_TEXTURE;
default -> 0;
};
}
private static void ensureReadbackCapacity(int bytes) {
if (readbackBuf.capacity() < bytes) {
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
}
private static void ensureIndexReadbackCapacity(int bytes) {
if (indexReadbackBuf.capacity() < bytes) {
indexReadbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
}
private static void readPositionBuffer(long offset, ByteBuffer destination) {
final int positionVbo = VAOManager.getAttribVBO(0);
final int previousVbo = GLStateManager.getBoundVBO();
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, positionVbo);
}
try {
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, offset, destination);
} finally {
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousVbo);
}
}
}
private static void emitVertex(int vertexIndex, int stride) {
final float x = readbackBuf.getFloat(vertexIndex * stride);
final float y = readbackBuf.getFloat(vertexIndex * stride + 4);
final float z = readbackBuf.getFloat(vertexIndex * stride + 8);
transformAndEmitVertex(x, y, z);
}
private static void emitAllPrimitives(int mode, int count, IndexReader reader, int stride) {
switch (mode) {
case GL11.GL_POINTS:
for (int i = 0; i < count; i++) {
feedbackToken(GL11.GL_POINT_TOKEN);
emitVertex(reader.read(i), stride);
}
break;
case GL11.GL_LINES:
for (int i = 0; i + 1 < count; i += 2) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_LINE_STRIP:
for (int i = 0; i + 1 < count; i++) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_LINE_LOOP:
for (int i = 0; i + 1 < count; i++) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
if (count > 2) {
feedbackToken(GL11.GL_LINE_TOKEN);
emitVertex(reader.read(count - 1), stride);
emitVertex(reader.read(0), stride);
}
break;
case GL11.GL_TRIANGLES:
for (int i = 0; i + 2 < count; i += 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_TRIANGLE_STRIP:
for (int i = 0; i + 2 < count; i++) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
if ((i & 1) == 0) {
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
} else {
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i), stride);
}
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_TRIANGLE_FAN:
for (int i = 1; i + 1 < count; i++) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
emitVertex(reader.read(0), stride);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_QUADS:
for (int i = 0; i + 3 < count; i += 4) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(4);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 2), stride);
emitVertex(reader.read(i + 3), stride);
}
break;
case GL11.GL_QUAD_STRIP:
for (int i = 0; i + 3 < count; i += 2) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(4);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 3), stride);
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_POLYGON:
if (count >= 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(count);
for (int i = 0; i < count; i++) {
emitVertex(reader.read(i), stride);
}
}
break;
default:
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
}
}
static void reset() {
renderMode = GL11.GL_RENDER;
feedbackBuffer = null;
feedbackBufferSize = 0;
feedbackMask = 0;
feedbackCount = 0;
}
}
@@ -1,203 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.Color4Stack;
import com.gtnewhorizons.angelica.glsm.stacks.ColorMaskStack;
import com.gtnewhorizons.angelica.glsm.stacks.DepthStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.IntegerStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightModelStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LineStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntStack;
import org.joml.Matrix4fStack;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
class GLContextState {
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
private BooleanStateStack track(BooleanStateStack s) {
allBooleanStates.add(s);
return s;
}
public int programGeneration;
public int drawFramebufferGeneration;
public boolean poppingAttributes;
// modelview matrix changes (any: translation, rotation, scale)
public int mvGeneration;
public int mvLinearGeneration;
// projection matrix changes
public int projGeneration;
// texture matrix changes
public int texMatrixGeneration;
public int lightingGeneration;
// fog + alpha ref + overlay color
public int fragmentGeneration;
// current vertex color
public int colorGeneration;
// clip plane equation changes
public int clipPlaneGeneration;
public float overlayR = 0.0f;
public float overlayG = 0.0f;
public float overlayB = 0.0f;
public float overlayA = 0.0f;
public float shaderColorR = 1.0f;
public float shaderColorG = 1.0f;
public float shaderColorB = 1.0f;
public float shaderColorA = 1.0f;
// Deferred vertex attribute upload flags - set when state changes, flushed before draw
public boolean dirtyColorAttrib = true;
public boolean dirtyNormalAttrib;
public boolean dirtyTexCoordAttrib;
public boolean dirtyLightmapAttrib = true;
public boolean unit23TexCoordSetDuringDraw = false;
public int maxBoundTextureUnit = 0;
public int maxBoundImageUnit = 0;
public final IntStack attribs = new IntArrayList(GLStateManager.MAX_ATTRIB_STACK_DEPTH);
public int attribDepth = 0;
@SuppressWarnings("unchecked")
public final List<IStateStack<?>>[] modifiedAtDepth = new List[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedMvGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedMvLinearGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedProjGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedTexMatGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedLightingGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedFragmentGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedColorGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedNormalGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedTexCoordGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final IntegerStateStack activeTextureUnit = new IntegerStateStack(0);
public int clientActiveTextureUnit = 0;
public final IntegerStateStack shadeModelState = new IntegerStateStack(GL11.GL_SMOOTH);
public final TextureUnitArray textures = new TextureUnitArray();
public final ImageUnitArray imageUnits = new ImageUnitArray();
public final BlendStateStack blendState = new BlendStateStack();
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
public final DepthStateStack depthState = new DepthStateStack();
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
public final FogStateStack fogState = new FogStateStack();
public final BooleanStateStack fogMode = track(new BooleanStateStack(GL11.GL_FOG, false, true));
public final Color4Stack color = new Color4Stack();
public final Color4Stack clearColor = new Color4Stack(new Color4(0.0F, 0.0F, 0.0F, 0.0F));
public final ColorMaskStack colorMask = new ColorMaskStack();
public final IntegerStateStack drawBuffer = new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER);
public final IntegerStateStack logicOpMode = new IntegerStateStack(GL11.GL_COPY);
public final BooleanStateStack cullState = track(new BooleanStateStack(GL11.GL_CULL_FACE));
public final AlphaStateStack alphaState = new AlphaStateStack();
public final BooleanStateStack alphaTest = track(new BooleanStateStack(GL11.GL_ALPHA_TEST, false, true));
public final BooleanStateStack lightingState = track(new BooleanStateStack(GL11.GL_LIGHTING, false, true));
public final BooleanStateStack rescaleNormalState = track(new BooleanStateStack(GL12.GL_RESCALE_NORMAL, false, true));
public final BooleanStateStack normalizeState = track(new BooleanStateStack(GL11.GL_NORMALIZE, false, true));
public final BooleanStateStack ditherState = track(new BooleanStateStack(GL11.GL_DITHER, true));
public final BooleanStateStack stencilTest = track(new BooleanStateStack(GL11.GL_STENCIL_TEST));
public final BooleanStateStack lineSmoothState = track(new BooleanStateStack(GL11.GL_LINE_SMOOTH));
public final BooleanStateStack lineStippleState = track(new BooleanStateStack(GL11.GL_LINE_STIPPLE, false, true));
public final BooleanStateStack pointSmoothState = track(new BooleanStateStack(GL11.GL_POINT_SMOOTH, false, true));
public final BooleanStateStack polygonSmoothState = track(new BooleanStateStack(GL11.GL_POLYGON_SMOOTH));
public final BooleanStateStack polygonStippleState = track(new BooleanStateStack(GL11.GL_POLYGON_STIPPLE, false, true));
public final BooleanStateStack multisampleState = track(new BooleanStateStack(GL13.GL_MULTISAMPLE, true));
public final BooleanStateStack sampleAlphaToCoverageState = track(new BooleanStateStack(GL13.GL_SAMPLE_ALPHA_TO_COVERAGE));
public final BooleanStateStack sampleAlphaToOneState = track(new BooleanStateStack(GL13.GL_SAMPLE_ALPHA_TO_ONE));
public final BooleanStateStack sampleCoverageState = track(new BooleanStateStack(GL13.GL_SAMPLE_COVERAGE));
public final BooleanStateStack colorLogicOpState = track(new BooleanStateStack(GL11.GL_COLOR_LOGIC_OP));
public final BooleanStateStack indexLogicOpState = track(new BooleanStateStack(GL11.GL_INDEX_LOGIC_OP, false, true));
public final BooleanStateStack polygonOffsetPointState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_POINT));
public final BooleanStateStack polygonOffsetLineState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_LINE));
public final BooleanStateStack polygonOffsetFillState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_FILL));
public final LineStateStack lineState = new LineStateStack();
public final PointStateStack pointState = new PointStateStack();
public final PolygonStateStack polygonState = new PolygonStateStack();
public final StencilStateStack stencilState = new StencilStateStack();
public int stencilBitMask = 0xFFFFFFFF;
public final BooleanStateStack autoNormalState = track(new BooleanStateStack(GL11.GL_AUTO_NORMAL, false, true));
public final BooleanStateStack map1Color4State = track(new BooleanStateStack(GL11.GL_MAP1_COLOR_4, false, true));
public final BooleanStateStack map1IndexState = track(new BooleanStateStack(GL11.GL_MAP1_INDEX, false, true));
public final BooleanStateStack map1NormalState = track(new BooleanStateStack(GL11.GL_MAP1_NORMAL, false, true));
public final BooleanStateStack map1TextureCoord1State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_1, false, true));
public final BooleanStateStack map1TextureCoord2State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_2, false, true));
public final BooleanStateStack map1TextureCoord3State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_3, false, true));
public final BooleanStateStack map1TextureCoord4State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_4, false, true));
public final BooleanStateStack map1Vertex3State = track(new BooleanStateStack(GL11.GL_MAP1_VERTEX_3, false, true));
public final BooleanStateStack map1Vertex4State = track(new BooleanStateStack(GL11.GL_MAP1_VERTEX_4, false, true));
public final BooleanStateStack map2Color4State = track(new BooleanStateStack(GL11.GL_MAP2_COLOR_4, false, true));
public final BooleanStateStack map2IndexState = track(new BooleanStateStack(GL11.GL_MAP2_INDEX, false, true));
public final BooleanStateStack map2NormalState = track(new BooleanStateStack(GL11.GL_MAP2_NORMAL, false, true));
public final BooleanStateStack map2TextureCoord1State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_1, false, true));
public final BooleanStateStack map2TextureCoord2State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_2, false, true));
public final BooleanStateStack map2TextureCoord3State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_3, false, true));
public final BooleanStateStack map2TextureCoord4State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_4, false, true));
public final BooleanStateStack map2Vertex3State = track(new BooleanStateStack(GL11.GL_MAP2_VERTEX_3, false, true));
public final BooleanStateStack map2Vertex4State = track(new BooleanStateStack(GL11.GL_MAP2_VERTEX_4, false, true));
public final BooleanStateStack[] clipPlaneStates = new BooleanStateStack[GLStateManager.MAX_CLIP_PLANES];
public final ClipPlaneState clipPlaneState = new ClipPlaneState();
public final FloatBuffer queryScratch = BufferUtils.createFloatBuffer(16);
public final DoubleBuffer queryScratchDouble = BufferUtils.createDoubleBuffer(16);
public final MatrixModeStack matrixMode = new MatrixModeStack();
public final Matrix4fStack modelViewMatrix = new Matrix4fStack(GLStateManager.MAX_MODELVIEW_STACK_DEPTH);
public final Matrix4fStack projectionMatrix = new Matrix4fStack(GLStateManager.MAX_PROJECTION_STACK_DEPTH);
public final BooleanStateStack[] lightStates = new BooleanStateStack[8];
public final LightStateStack[] lightDataStates = new LightStateStack[8];
public final BooleanStateStack colorMaterial = track(new BooleanStateStack(GL11.GL_COLOR_MATERIAL, false, true));
public final IntegerStateStack colorMaterialFace = new IntegerStateStack(GL11.GL_FRONT_AND_BACK);
public final IntegerStateStack colorMaterialParameter = new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE);
public final LightModelStateStack lightModel = new LightModelStateStack();
public final MaterialStateStack frontMaterial = new MaterialStateStack(GL11.GL_FRONT);
public final MaterialStateStack backMaterial = new MaterialStateStack(GL11.GL_BACK);
public final ViewPortStateStack viewportState = new ViewPortStateStack();
public int activeProgram = 0;
public int listBase = 0;
public int boundVBO;
public int boundVAO;
public int boundPixelUnpackBuffer;
public int boundPixelPackBuffer;
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public final boolean[] restoreUnitChanged = new boolean[GLStateManager.MAX_TEXTURE_UNITS];
public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged;
public int drawFramebuffer = 0;
public int readFramebuffer = 0;
public int texGenGeneration;
GLContextState() {
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) modifiedAtDepth[i] = new ArrayList<>();
for (int i = 0; i < lightStates.length; i++) {
lightStates[i] = track(new BooleanStateStack(GL11.GL_LIGHT0 + i, false, true));
}
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
clipPlaneStates[i] = track(new BooleanStateStack(GL11.GL_CLIP_PLANE0 + i));
}
}
void init() {
for (int i = 0; i < lightDataStates.length; i++) {
lightDataStates[i] = new LightStateStack(GL11.GL_LIGHT0 + i);
}
}
}
@@ -1,647 +0,0 @@
/*
* Copyright LWJGL. All rights reserved. Modified by IMS for use in Iris (net.coderbot.iris.gl).
* License terms: https://www.lwjgl.org/license
*/
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.lwjgl.opengl.EXTBlendColor;
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.GL43;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public final class GLDebug {
/**
* Sets up debug callbacks
*
* @return 0 for failure, 1 for success, 2 for restart required.
*/
public static int setupDebugMessageCallback() {
if (Thread.currentThread() != GLStateManager.getMainThread()) {
GLStateManager.LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
return 0;
}
return setupDebugMessageCallbackImpl();
}
public static Throwable filterStackTrace(Throwable throwable, int offset) {
final StackTraceElement[] elems = throwable.getStackTrace();
final StackTraceElement[] filtered = new StackTraceElement[elems.length];
int j = 0;
for (int i = offset; i < elems.length; i++) {
String className = elems[i].getClassName();
if (className == null) {
className = "";
}
filtered[j++] = elems[i];
}
final StackTraceElement[] newElems = new StackTraceElement[j];
System.arraycopy(filtered, 0, newElems, 0, j);
throwable.setStackTrace(newElems);
return throwable;
}
private static String buildStackTrace() {
final StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
final StringBuilder sb = new StringBuilder();
for (StackTraceElement elem : elems) {
sb.append("\n\t").append(elem.toString());
}
return sb.toString();
}
private static void logDebugMessage(int id, String source, String type, String severity, String message) {
final String fullMessage = String.format("[GL] %s %s (0x%X) from %s: %s%s", severity, type, id, source, message, buildStackTrace());
if ("HIGH".equals(severity)) {
GLStateManager.LOGGER.error(fullMessage);
} else if ("MEDIUM".equals(severity)) {
GLStateManager.LOGGER.warn(fullMessage);
} else {
GLStateManager.LOGGER.info(fullMessage);
}
}
private static int setupDebugMessageCallbackImpl() {
return BackendManager.RENDER_BACKEND.setupDebugOutput(
(source, type, id, severity, message) ->
logDebugMessage(id, getDebugSource(source), getDebugType(type), getDebugSeverity(severity), message)
);
}
public static int disableDebugMessages() {
return BackendManager.RENDER_BACKEND.disableDebugOutput();
}
private static String getDebugSource(int source) {
return switch (source) {
case GL43.GL_DEBUG_SOURCE_API -> "API";
case GL43.GL_DEBUG_SOURCE_WINDOW_SYSTEM -> "WINDOW SYSTEM";
case GL43.GL_DEBUG_SOURCE_SHADER_COMPILER -> "SHADER COMPILER";
case GL43.GL_DEBUG_SOURCE_THIRD_PARTY -> "THIRD PARTY";
case GL43.GL_DEBUG_SOURCE_APPLICATION -> "APPLICATION";
case GL43.GL_DEBUG_SOURCE_OTHER -> "OTHER";
default -> String.format("Unknown [0x%X]", source);
};
}
private static String getDebugType(int type) {
return switch (type) {
case GL43.GL_DEBUG_TYPE_ERROR -> "ERROR";
case GL43.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR -> "DEPRECATED BEHAVIOR";
case GL43.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR -> "UNDEFINED BEHAVIOR";
case GL43.GL_DEBUG_TYPE_PORTABILITY -> "PORTABILITY";
case GL43.GL_DEBUG_TYPE_PERFORMANCE -> "PERFORMANCE";
case GL43.GL_DEBUG_TYPE_OTHER -> "OTHER";
case GL43.GL_DEBUG_TYPE_MARKER -> "MARKER";
default -> String.format("Unknown [0x%X]", type);
};
}
private static String getDebugSeverity(int severity) {
return switch (severity) {
case GL43.GL_DEBUG_SEVERITY_NOTIFICATION -> "NOTIFICATION";
case GL43.GL_DEBUG_SEVERITY_HIGH -> "HIGH";
case GL43.GL_DEBUG_SEVERITY_MEDIUM -> "MEDIUM";
case GL43.GL_DEBUG_SEVERITY_LOW -> "LOW";
default -> String.format("Unknown [0x%X]", severity);
};
}
public static String getMatrixModeName(int mode) {
return switch (mode) {
case GL11.GL_MODELVIEW -> "MODELVIEW";
case GL11.GL_PROJECTION -> "PROJECTION";
case GL11.GL_TEXTURE -> "TEXTURE";
case GL11.GL_COLOR -> "COLOR";
default -> String.format("0x%X", mode);
};
}
public static String getCapabilityName(int cap) {
return switch (cap) {
case GL11.GL_ALPHA_TEST -> "ALPHA_TEST";
case GL11.GL_BLEND -> "BLEND";
case GL11.GL_COLOR_MATERIAL -> "COLOR_MATERIAL";
case GL11.GL_CULL_FACE -> "CULL_FACE";
case GL11.GL_DEPTH_TEST -> "DEPTH_TEST";
case GL11.GL_FOG -> "FOG";
case GL11.GL_LIGHTING -> "LIGHTING";
case GL11.GL_LINE_SMOOTH -> "LINE_SMOOTH";
case GL11.GL_NORMALIZE -> "NORMALIZE";
case GL11.GL_POINT_SMOOTH -> "POINT_SMOOTH";
case GL11.GL_POLYGON_OFFSET_FILL -> "POLYGON_OFFSET_FILL";
case GL11.GL_POLYGON_OFFSET_LINE -> "POLYGON_OFFSET_LINE";
case GL11.GL_POLYGON_SMOOTH -> "POLYGON_SMOOTH";
case GL12.GL_RESCALE_NORMAL -> "RESCALE_NORMAL";
case GL11.GL_SCISSOR_TEST -> "SCISSOR_TEST";
case GL11.GL_STENCIL_TEST -> "STENCIL_TEST";
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
case GL11.GL_LIGHT2 -> "LIGHT2";
case GL11.GL_LIGHT3 -> "LIGHT3";
case GL11.GL_LIGHT4 -> "LIGHT4";
case GL11.GL_LIGHT5 -> "LIGHT5";
case GL11.GL_LIGHT6 -> "LIGHT6";
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", cap);
};
}
public static String getComparisonFuncName(int func) {
return switch (func) {
case GL11.GL_NEVER -> "NEVER";
case GL11.GL_LESS -> "LESS";
case GL11.GL_EQUAL -> "EQUAL";
case GL11.GL_LEQUAL -> "LEQUAL";
case GL11.GL_GREATER -> "GREATER";
case GL11.GL_NOTEQUAL -> "NOTEQUAL";
case GL11.GL_GEQUAL -> "GEQUAL";
case GL11.GL_ALWAYS -> "ALWAYS";
default -> String.format("0x%X", func);
};
}
public static String getBlendFactorName(int factor) {
return switch (factor) {
case GL11.GL_ZERO -> "ZERO";
case GL11.GL_ONE -> "ONE";
case GL11.GL_SRC_COLOR -> "SRC_COLOR";
case GL11.GL_ONE_MINUS_SRC_COLOR -> "ONE_MINUS_SRC_COLOR";
case GL11.GL_DST_COLOR -> "DST_COLOR";
case GL11.GL_ONE_MINUS_DST_COLOR -> "ONE_MINUS_DST_COLOR";
case GL11.GL_SRC_ALPHA -> "SRC_ALPHA";
case GL11.GL_ONE_MINUS_SRC_ALPHA -> "ONE_MINUS_SRC_ALPHA";
case GL11.GL_DST_ALPHA -> "DST_ALPHA";
case GL11.GL_ONE_MINUS_DST_ALPHA -> "ONE_MINUS_DST_ALPHA";
case EXTBlendColor.GL_CONSTANT_COLOR_EXT -> "CONSTANT_COLOR";
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_COLOR_EXT -> "ONE_MINUS_CONSTANT_COLOR";
case EXTBlendColor.GL_CONSTANT_ALPHA_EXT -> "CONSTANT_ALPHA";
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_ALPHA_EXT -> "ONE_MINUS_CONSTANT_ALPHA";
case GL11.GL_SRC_ALPHA_SATURATE -> "SRC_ALPHA_SATURATE";
default -> String.format("0x%X", factor);
};
}
public static String getShadeModelName(int mode) {
return switch (mode) {
case GL11.GL_FLAT -> "FLAT";
case GL11.GL_SMOOTH -> "SMOOTH";
default -> String.format("0x%X", mode);
};
}
public static String getTextureTargetName(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
case GL12.GL_TEXTURE_3D -> "TEXTURE_3D";
case GL13.GL_TEXTURE_CUBE_MAP -> "TEXTURE_CUBE_MAP";
default -> String.format("0x%X", target);
};
}
public static String getTexturePnameName(int pname) {
return switch (pname) {
case GL11.GL_TEXTURE_MIN_FILTER -> "TEXTURE_MIN_FILTER";
case GL11.GL_TEXTURE_MAG_FILTER -> "TEXTURE_MAG_FILTER";
case GL11.GL_TEXTURE_WRAP_S -> "TEXTURE_WRAP_S";
case GL11.GL_TEXTURE_WRAP_T -> "TEXTURE_WRAP_T";
case GL12.GL_TEXTURE_WRAP_R -> "TEXTURE_WRAP_R";
case GL12.GL_TEXTURE_MIN_LOD -> "TEXTURE_MIN_LOD";
case GL12.GL_TEXTURE_MAX_LOD -> "TEXTURE_MAX_LOD";
case GL12.GL_TEXTURE_BASE_LEVEL -> "TEXTURE_BASE_LEVEL";
case GL12.GL_TEXTURE_MAX_LEVEL -> "TEXTURE_MAX_LEVEL";
case GL11.GL_TEXTURE_BORDER_COLOR -> "TEXTURE_BORDER_COLOR";
case GL11.GL_TEXTURE_PRIORITY -> "TEXTURE_PRIORITY";
case GL14.GL_TEXTURE_LOD_BIAS -> "TEXTURE_LOD_BIAS";
case GL14.GL_GENERATE_MIPMAP -> "GENERATE_MIPMAP";
default -> String.format("0x%X", pname);
};
}
public static String getTextureFormatName(int format) {
return switch (format) {
case GL11.GL_RED -> "RED";
case GL11.GL_GREEN -> "GREEN";
case GL11.GL_BLUE -> "BLUE";
case GL11.GL_ALPHA -> "ALPHA";
case GL11.GL_RGB -> "RGB";
case GL11.GL_RGBA -> "RGBA";
case GL11.GL_LUMINANCE -> "LUMINANCE";
case GL11.GL_LUMINANCE_ALPHA -> "LUMINANCE_ALPHA";
case GL12.GL_BGR -> "BGR";
case GL12.GL_BGRA -> "BGRA";
case GL11.GL_DEPTH_COMPONENT -> "DEPTH_COMPONENT";
case GL11.GL_STENCIL_INDEX -> "STENCIL_INDEX";
case GL30.GL_DEPTH_STENCIL -> "DEPTH_STENCIL";
// Internal formats
case GL11.GL_RGB8 -> "RGB8";
case GL11.GL_RGBA8 -> "RGBA8";
case GL11.GL_ALPHA8 -> "ALPHA8";
case GL11.GL_LUMINANCE8 -> "LUMINANCE8";
case GL11.GL_LUMINANCE8_ALPHA8 -> "LUMINANCE8_ALPHA8";
default -> String.format("0x%X", format);
};
}
public static String getLightName(int light) {
return switch (light) {
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
case GL11.GL_LIGHT2 -> "LIGHT2";
case GL11.GL_LIGHT3 -> "LIGHT3";
case GL11.GL_LIGHT4 -> "LIGHT4";
case GL11.GL_LIGHT5 -> "LIGHT5";
case GL11.GL_LIGHT6 -> "LIGHT6";
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", light);
};
}
public static String getLightPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_POSITION -> "POSITION";
case GL11.GL_SPOT_DIRECTION -> "SPOT_DIRECTION";
case GL11.GL_SPOT_EXPONENT -> "SPOT_EXPONENT";
case GL11.GL_SPOT_CUTOFF -> "SPOT_CUTOFF";
case GL11.GL_CONSTANT_ATTENUATION -> "CONSTANT_ATTENUATION";
case GL11.GL_LINEAR_ATTENUATION -> "LINEAR_ATTENUATION";
case GL11.GL_QUADRATIC_ATTENUATION -> "QUADRATIC_ATTENUATION";
default -> String.format("0x%X", pname);
};
}
public static String getMaterialPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_EMISSION -> "EMISSION";
case GL11.GL_SHININESS -> "SHININESS";
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
case GL11.GL_COLOR_INDEXES -> "COLOR_INDEXES";
default -> String.format("0x%X", pname);
};
}
public static String getFaceName(int face) {
return switch (face) {
case GL11.GL_FRONT -> "FRONT";
case GL11.GL_BACK -> "BACK";
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
default -> String.format("0x%X", face);
};
}
public static String getColorMaterialModeName(int mode) {
return switch (mode) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_EMISSION -> "EMISSION";
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
default -> String.format("0x%X", mode);
};
}
public static String getFogPnameName(int pname) {
return switch (pname) {
case GL11.GL_FOG_MODE -> "FOG_MODE";
case GL11.GL_FOG_DENSITY -> "FOG_DENSITY";
case GL11.GL_FOG_START -> "FOG_START";
case GL11.GL_FOG_END -> "FOG_END";
case GL11.GL_FOG_INDEX -> "FOG_INDEX";
case GL11.GL_FOG_COLOR -> "FOG_COLOR";
default -> String.format("0x%X", pname);
};
}
public static String getLightModelPnameName(int pname) {
return switch (pname) {
case GL11.GL_LIGHT_MODEL_AMBIENT -> "LIGHT_MODEL_AMBIENT";
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> "LIGHT_MODEL_LOCAL_VIEWER";
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> "LIGHT_MODEL_TWO_SIDE";
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> "LIGHT_MODEL_COLOR_CONTROL";
default -> String.format("0x%X", pname);
};
}
public static String getClearMaskString(int mask) {
StringBuilder sb = new StringBuilder();
boolean first = true;
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
sb.append("COLOR");
first = false;
}
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("DEPTH");
first = false;
}
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("STENCIL");
first = false;
}
if ((mask & GL11.GL_ACCUM_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("ACCUM");
}
return sb.toString();
}
public static String getCullFaceName(int mode) {
return switch (mode) {
case GL11.GL_FRONT -> "FRONT";
case GL11.GL_BACK -> "BACK";
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
default -> String.format("0x%X", mode);
};
}
public static String getLogicOpName(int opcode) {
return switch (opcode) {
case GL11.GL_CLEAR -> "CLEAR";
case GL11.GL_AND -> "AND";
case GL11.GL_AND_REVERSE -> "AND_REVERSE";
case GL11.GL_COPY -> "COPY";
case GL11.GL_AND_INVERTED -> "AND_INVERTED";
case GL11.GL_NOOP -> "NOOP";
case GL11.GL_XOR -> "XOR";
case GL11.GL_OR -> "OR";
case GL11.GL_NOR -> "NOR";
case GL11.GL_EQUIV -> "EQUIV";
case GL11.GL_INVERT -> "INVERT";
case GL11.GL_OR_REVERSE -> "OR_REVERSE";
case GL11.GL_COPY_INVERTED -> "COPY_INVERTED";
case GL11.GL_OR_INVERTED -> "OR_INVERTED";
case GL11.GL_NAND -> "NAND";
case GL11.GL_SET -> "SET";
default -> String.format("0x%X", opcode);
};
}
public static String getFramebufferStatusName(int status) {
return switch (status) {
case GL30.GL_FRAMEBUFFER_COMPLETE -> "COMPLETE";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT -> "INCOMPLETE_ATTACHMENT";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT -> "INCOMPLETE_MISSING_ATTACHMENT";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER -> "INCOMPLETE_DRAW_BUFFER";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER -> "INCOMPLETE_READ_BUFFER";
case GL30.GL_FRAMEBUFFER_UNSUPPORTED -> "UNSUPPORTED";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE -> "INCOMPLETE_MULTISAMPLE";
case 0 -> "NO_FRAMEBUFFER_BOUND";
default -> String.format("UNKNOWN(0x%X)", status);
};
}
public static String getInternalFormatName(int format) {
return switch (format) {
// Basic
case GL11.GL_RGBA -> "RGBA";
case GL11.GL_RGB -> "RGB";
// 8-bit normalized
case GL30.GL_R8 -> "R8";
case GL30.GL_RG8 -> "RG8";
case GL11.GL_RGB8 -> "RGB8";
case GL11.GL_RGBA8 -> "RGBA8";
// 8-bit signed normalized
case 0x8F94 -> "R8_SNORM";
case 0x8F95 -> "RG8_SNORM";
case 0x8F96 -> "RGB8_SNORM";
case 0x8F97 -> "RGBA8_SNORM";
// 16-bit normalized
case GL30.GL_R16 -> "R16";
case GL30.GL_RG16 -> "RG16";
case GL11.GL_RGB16 -> "RGB16";
case GL11.GL_RGBA16 -> "RGBA16";
// 16-bit float
case GL30.GL_R16F -> "R16F";
case GL30.GL_RG16F -> "RG16F";
case GL30.GL_RGB16F -> "RGB16F";
case GL30.GL_RGBA16F -> "RGBA16F";
// 32-bit float
case GL30.GL_R32F -> "R32F";
case GL30.GL_RG32F -> "RG32F";
case GL30.GL_RGB32F -> "RGB32F";
case GL30.GL_RGBA32F -> "RGBA32F";
// Packed
case GL30.GL_R11F_G11F_B10F -> "R11F_G11F_B10F";
case GL11.GL_RGB10_A2 -> "RGB10_A2";
default -> String.format("0x%X", format);
};
}
private static DebugState debugState;
private interface DebugState {
void nameObject(int id, int object, String name);
void pushGroup(String name);
void popGroup();
void debugMessage(String name);
String getObjectLabel(int glProgram, int program);
}
private static class KHRDebugState implements DebugState {
private static final int ID = 0;
private int depth = 0;
private static final int maxDepth = BackendManager.RENDER_BACKEND.getInteger(GL43.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
private final String[] groupStack = new String[maxDepth + 1];
@Override
public void nameObject(int id, int object, String name) {
BackendManager.RENDER_BACKEND.objectLabel(id, object, name);
}
@Override
public void pushGroup(String name) {
if (depth + 1 > maxDepth) {
final StringBuilder sb = new StringBuilder();
sb.append("Debug group stack overflow (maxDepth=").append(maxDepth).append("), current stack:\n");
for (int i = 0; i < depth; i++) {
sb.append(" [").append(i).append("] ").append(groupStack[i]).append('\n');
}
sb.append(" -> ").append(name).append(" (rejected)");
throw new RuntimeException(sb.toString());
}
groupStack[depth] = name;
BackendManager.RENDER_BACKEND.pushDebugGroup(GL43.GL_DEBUG_SOURCE_APPLICATION, ID, name);
depth++;
}
@Override
public void popGroup() {
if (depth - 1 < 0) {
throw new RuntimeException("Stack underflow");
}
BackendManager.RENDER_BACKEND.popDebugGroup();
depth--;
groupStack[depth] = null;
}
@Override
public void debugMessage(String message) {
BackendManager.RENDER_BACKEND.debugMessageInsert(
GL43.GL_DEBUG_SOURCE_APPLICATION,
GL43.GL_DEBUG_TYPE_MARKER,
ID,
GL43.GL_DEBUG_SEVERITY_NOTIFICATION,
message);
}
@Override
public String getObjectLabel(int glProgram, int program) {
if (program == 0) return "";
return BackendManager.RENDER_BACKEND.getObjectLabel(glProgram, program);
}
}
private static class UnsupportedDebugState implements DebugState {
@Override
public void nameObject(int id, int object, String name) {
}
@Override
public void pushGroup(String name) {
}
@Override
public void popGroup() {
}
@Override
public void debugMessage(String name) {
}
@Override
public String getObjectLabel(int glProgram, int program) {
return "";
}
}
public static void initDebugState() {
final GLSMInitConfig cfg = GLStateManager.getInitConfig();
final boolean userRequested = cfg != null && (cfg.isLwjglDebug() || CaptureGate.enabledAtStartup());
if (userRequested && BackendManager.RENDER_BACKEND.supportsDebugOutput()) {
debugState = new KHRDebugState();
} else {
debugState = new UnsupportedDebugState();
}
}
public static void nameObject(int id, int object, String name) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.nameObject(id, object, name);
}
}
/**
* Overload that lets you gate the String concat behind the debugState
*/
public static void pushGroup(String prefix, int value) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.pushGroup(prefix + value);
}
}
public static boolean isActive() {
return debugState instanceof KHRDebugState && CaptureGate.markersThisFrame && Thread.currentThread() == GLStateManager.getMainThread();
}
public static void pushGroup(String group) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.pushGroup(group);
}
}
public static void popGroup() {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.popGroup();
}
}
public static void debugMessage(String message) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.debugMessage(message);
}
}
public static String getObjectLabel(int glProgram, int program) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
return debugState.getObjectLabel(glProgram, program);
}
return "";
}
private static final Scope NOOP_SCOPE = () -> {};
private static final Scope ACTIVE_SCOPE = GLDebug::popGroup;
public static Scope scope(String name) {
if (isActive()) {
debugState.pushGroup(name);
return ACTIVE_SCOPE;
}
return NOOP_SCOPE;
}
@FunctionalInterface
public interface Scope extends AutoCloseable {
@Override void close();
}
private static MethodHandle khrCallbackInvoke;
private static boolean khrCallbackInvokeResolved;
/// Use reflection because of lwjgl3ify
static GLDebugMessageListener adaptDebugCallback(Object callback) {
if (callback == null) return null;
final MethodHandle invoke = resolveKhrCallbackInvoke(callback.getClass());
if (invoke == null) return null;
return (source, type, id, severity, length, message, userParam) -> {
try {
invoke.invoke(callback, source, type, id, severity, length, message, userParam);
} catch (Throwable t) {
GLStateManager.LOGGER.warn("Debug message callback threw", t);
}
};
}
private static synchronized MethodHandle resolveKhrCallbackInvoke(Class<?> callbackClass) {
if (!khrCallbackInvokeResolved) {
khrCallbackInvokeResolved = true;
try {
khrCallbackInvoke = MethodHandles.publicLookup().findVirtual(callbackClass, "invoke", MethodType.methodType(void.class, int.class, int.class, int.class, int.class, int.class, long.class, long.class));
} catch (ReflectiveOperationException e) {
GLStateManager.LOGGER.warn("{} has no public invoke(int,int,int,int,int,long,long); debug messages registered through it will not be delivered.", callbackClass.getName());
}
}
return khrCallbackInvoke;
}
}
@@ -1,34 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
public final class GLESCaps {
private GLESCaps() {}
public static boolean isCapAllowed(int cap, boolean isGLES, boolean hasClipCullDistance) {
if (!isGLES) return true;
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES) {
return hasClipCullDistance;
}
return switch (cap) {
case GL11.GL_LINE_SMOOTH,
GL11.GL_POLYGON_SMOOTH,
GL11.GL_COLOR_LOGIC_OP,
GL11.GL_POLYGON_OFFSET_POINT,
GL11.GL_POLYGON_OFFSET_LINE,
GL13.GL_MULTISAMPLE,
GL13.GL_SAMPLE_ALPHA_TO_ONE,
GL20.GL_VERTEX_PROGRAM_POINT_SIZE,
GL30.GL_FRAMEBUFFER_SRGB,
GL31.GL_PRIMITIVE_RESTART,
GL32.GL_DEPTH_CLAMP -> false;
default -> true;
};
}
}
@@ -1,86 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
public final class GLESFormatRemap {
private GLESFormatRemap() {}
public record Result(int internalFormat, int format, int type) {}
public static Result apply(int internalformat, int format, int type, boolean isGLES) {
internalformat = promoteAlphaFormat(internalformat);
if (isGLES) {
internalformat = remapInternalFormat(internalformat);
if (type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV && (format == GL12.GL_BGRA || format == GL11.GL_RGBA)) {
type = GL11.GL_UNSIGNED_BYTE;
}
if (isGenericPixelType(type)) {
type = typeForInternalFormatES32(internalformat, type);
}
}
return new Result(internalformat, format, type);
}
public static int promoteAlphaFormat(int internalformat) {
return switch (internalformat) {
case GL11.GL_ALPHA4 -> GL11.GL_RGBA4;
case GL11.GL_ALPHA8 -> GL11.GL_RGBA8;
case GL11.GL_ALPHA12 -> GL11.GL_RGBA12;
case GL11.GL_ALPHA16 -> GL11.GL_RGBA16;
default -> internalformat;
};
}
public static int remapInternalFormat(int internalformat) {
return switch (internalformat) {
case GL11.GL_RGB16, GL11.GL_RGB12 -> GL30.GL_RGB16F;
case GL11.GL_RGBA16, GL11.GL_RGBA12 -> GL30.GL_RGBA16F;
case GL30.GL_R16 -> GL30.GL_R16F;
case GL30.GL_RG16 -> GL30.GL_RG16F;
case GL11.GL_RGB10, GL11.GL_R3_G3_B2 -> GL11.GL_RGB8;
default -> internalformat;
};
}
public static int remapPixelType(int format, int type) {
if (type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV && (format == GL12.GL_BGRA || format == GL11.GL_RGBA)) {
return GL11.GL_UNSIGNED_BYTE;
}
return type;
}
public static int typeForInternalFormatES32(int internalformat, int type) {
return switch (internalformat) {
case GL30.GL_R16F, GL30.GL_RG16F, GL30.GL_RGB16F, GL30.GL_RGBA16F -> GL30.GL_HALF_FLOAT;
case GL30.GL_R32F, GL30.GL_RG32F, GL30.GL_RGB32F, GL30.GL_RGBA32F -> GL11.GL_FLOAT;
case GL31.GL_R8_SNORM, GL31.GL_RG8_SNORM, GL31.GL_RGB8_SNORM, GL31.GL_RGBA8_SNORM -> GL11.GL_BYTE;
case GL30.GL_R11F_G11F_B10F, GL30.GL_RGB9_E5 -> GL30.GL_HALF_FLOAT;
case GL11.GL_RGB10_A2 -> GL12.GL_UNSIGNED_INT_2_10_10_10_REV;
case GL30.GL_R8I, GL30.GL_RG8I, GL30.GL_RGB8I, GL30.GL_RGBA8I,
GL30.GL_R16I, GL30.GL_RG16I, GL30.GL_RGB16I, GL30.GL_RGBA16I,
GL30.GL_R32I, GL30.GL_RG32I, GL30.GL_RGB32I, GL30.GL_RGBA32I -> GL11.GL_INT;
case GL30.GL_R8UI, GL30.GL_RG8UI, GL30.GL_RGB8UI, GL30.GL_RGBA8UI,
GL30.GL_R16UI, GL30.GL_RG16UI, GL30.GL_RGB16UI, GL30.GL_RGBA16UI,
GL30.GL_R32UI, GL30.GL_RG32UI, GL30.GL_RGB32UI, GL30.GL_RGBA32UI -> GL11.GL_UNSIGNED_INT;
case GL14.GL_DEPTH_COMPONENT24 -> GL11.GL_UNSIGNED_INT;
case GL30.GL_DEPTH_COMPONENT32F -> GL11.GL_FLOAT;
case GL30.GL_DEPTH24_STENCIL8 -> GL30.GL_UNSIGNED_INT_24_8;
case GL30.GL_DEPTH32F_STENCIL8 -> GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
default -> type;
};
}
public static boolean isGenericPixelType(int type) {
return type == GL11.GL_UNSIGNED_BYTE
|| type == GL11.GL_UNSIGNED_SHORT
|| type == GL11.GL_UNSIGNED_INT
|| type == GL11.GL_BYTE
|| type == GL11.GL_SHORT
|| type == GL11.GL_INT;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,57 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL30;
public final class GLTypes {
private GLTypes() {}
public static int sizeBytes(int glType) {
return switch (glType) {
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT, GL30.GL_HALF_FLOAT -> 2;
case GL11.GL_INT, GL11.GL_UNSIGNED_INT, GL11.GL_FLOAT -> 4;
case GL11.GL_DOUBLE -> 8;
default -> throw new IllegalArgumentException("Unknown GL type: 0x" + Integer.toHexString(glType));
};
}
public static int packedTexelBytes(int glType) {
return switch (glType) {
case GL12.GL_UNSIGNED_BYTE_3_3_2, GL12.GL_UNSIGNED_BYTE_2_3_3_REV -> 1;
case GL12.GL_UNSIGNED_SHORT_5_6_5, GL12.GL_UNSIGNED_SHORT_5_6_5_REV,
GL12.GL_UNSIGNED_SHORT_4_4_4_4, GL12.GL_UNSIGNED_SHORT_4_4_4_4_REV,
GL12.GL_UNSIGNED_SHORT_5_5_5_1, GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV -> 2;
case GL12.GL_UNSIGNED_INT_8_8_8_8, GL12.GL_UNSIGNED_INT_8_8_8_8_REV,
GL12.GL_UNSIGNED_INT_10_10_10_2, GL12.GL_UNSIGNED_INT_2_10_10_10_REV,
GL30.GL_UNSIGNED_INT_24_8, GL30.GL_UNSIGNED_INT_10F_11F_11F_REV,
GL30.GL_UNSIGNED_INT_5_9_9_9_REV -> 4;
case GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV -> 8;
default -> 0;
};
}
public static String name(int glType) {
return switch (glType) {
case GL11.GL_BYTE -> "BYTE";
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
case GL11.GL_SHORT -> "SHORT";
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
case GL11.GL_INT -> "INT";
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
case GL11.GL_FLOAT -> "FLOAT";
case GL11.GL_DOUBLE -> "DOUBLE";
case GL30.GL_HALF_FLOAT -> "HALF_FLOAT";
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
case GL12.GL_UNSIGNED_INT_8_8_8_8_REV -> "UNSIGNED_INT_8_8_8_8_REV";
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
case GL12.GL_UNSIGNED_INT_2_10_10_10_REV -> "UNSIGNED_INT_2_10_10_10_REV";
default -> String.format("0x%X", glType);
};
}
}
@@ -1,200 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLPreParser;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Shared GLSL shader transformation utilities.
*/
public class GlslTransformUtils {
private static final String RENAMED_PREFIX = "angelica_renamed_";
/** Texture function renames */
public static final Map<String, String> TEXTURE_RENAMES = Map.ofEntries(
Map.entry("texture2D", "texture"),
Map.entry("texture3D", "texture"),
Map.entry("texture2DLod", "textureLod"),
Map.entry("texture3DLod", "textureLod"),
Map.entry("texture2DProj", "textureProj"),
Map.entry("texture3DProj", "textureProj"),
Map.entry("texture2DGrad", "textureGrad"),
Map.entry("texture2DGradARB", "textureGrad"),
Map.entry("texture3DGrad", "textureGrad"),
Map.entry("texelFetch2D", "texelFetch"),
Map.entry("texelFetch3D", "texelFetch"),
Map.entry("textureSize2D", "textureSize")
);
private static final Pattern TEXTURE_PATTERN = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
private record ReservedWordRename(Pattern pattern, String replacement) {}
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
// Some reserved words to always rename
0, List.of(
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"),
new ReservedWordRename(Pattern.compile("\\bnew\\b"), RENAMED_PREFIX + "new")
),
400, List.of(
new ReservedWordRename(Pattern.compile("\\bsampler\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
)
);
public static String replaceTexture(String input) {
final Matcher matcher = TEXTURE_PATTERN.matcher(input);
final StringBuilder builder = new StringBuilder();
while (matcher.find()) {
if (matcher.group(1) != null) {
matcher.appendReplacement(builder, RENAMED_PREFIX + "texture");
} else {
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
}
}
matcher.appendTail(builder);
return builder.toString();
}
public static String renameReservedWords(String source, int targetVersion) {
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
// Rename identifiers that become reserved words at or above this GLSL version
if (targetVersion >= entry.getKey()) {
for (var rename : entry.getValue()) {
source = rename.pattern().matcher(source).replaceAll(rename.replacement());
}
}
}
return source;
}
public static String restoreReservedWords(String source) {
source = source.replace(RENAMED_PREFIX + "texture", "texture");
source = source.replace(RENAMED_PREFIX + "sampler", "sampler");
source = source.replace(RENAMED_PREFIX + "sample", "sample");
return source;
}
public record QuietParse(GLSLParser.Translation_unitContext full, GLSLPreParser.Translation_unitContext pre) {}
public static GLSLParser.Translation_unitContext parseFullQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.setBuildParseTree(true);
return parser.translation_unit();
}
public static GLSLPreParser.Translation_unitContext parsePreQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLPreParser preParser = new GLSLPreParser(new CommonTokenStream(lexer, GLSLLexer.DIRECTIVES));
preParser.removeErrorListeners();
preParser.setBuildParseTree(true);
return preParser.translation_unit();
}
public static QuietParse parseBothQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLPreParser preParser = new GLSLPreParser(new BufferedTokenStream(lexer));
preParser.removeErrorListeners();
preParser.setBuildParseTree(true);
final GLSLPreParser.Translation_unitContext pre = preParser.translation_unit();
lexer.reset();
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.setBuildParseTree(true);
return new QuietParse(parser.translation_unit(), pre);
}
public static Set<String> identifiersInDirectiveText(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final BufferedTokenStream tokens = new BufferedTokenStream(lexer);
tokens.fill();
final Set<String> names = new HashSet<>();
for (Token token : tokens.getTokens()) {
switch (token.getType()) {
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
default -> { }
}
}
return names;
}
private static void collectIdentifiers(String text, Set<String> out) {
if (text == null) return;
final int len = text.length();
int i = 0;
while (i < len) {
final char c = text.charAt(i);
if (c != '_' && !Character.isLetter(c)) {
i++;
continue;
}
int end = i + 1;
while (end < len) {
final char n = text.charAt(end);
if (n != '_' && !Character.isLetterOrDigit(n)) break;
end++;
}
out.add(text.substring(i, end));
i = end;
}
}
public static String getFormattedShader(ParseTree tree, String header) {
final StringBuilder sb = new StringBuilder(header + "\n");
final String[] tabHolder = {""};
getFormattedShader(tree, sb, tabHolder);
return sb.toString();
}
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
if (tree instanceof TerminalNode) {
final String text = tree.getText();
if (text.equals("<EOF>")) {
return;
}
if (text.equals("#")) {
stringBuilder.append("\n#");
return;
}
stringBuilder.append(text);
if (text.equals("{")) {
stringBuilder.append(" \n\t");
tabHolder[0] = "\t";
}
if (text.equals("}")) {
if (stringBuilder.length() >= 2) {
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
}
tabHolder[0] = "";
stringBuilder.append(" \n");
} else {
stringBuilder.append(text.equals(";") ? " \n" + tabHolder[0] : " ");
}
} else {
for (int i = 0; i < tree.getChildCount(); ++i) {
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
}
}
}
}
@@ -1,34 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class LTWWorkaround {
private LTWWorkaround() {}
private static ByteBuffer oneTexel;
public static boolean isLtwVersionString(String glVersion) {
return glVersion != null && glVersion.contains("LTW");
}
public static void onTexImage2D(int target, int level, int format, boolean hasPixels) {
if (target != GL11.GL_TEXTURE_2D || level != 0 || format != GL12.GL_BGRA || hasPixels) return;
if (oneTexel == null) {
oneTexel = BufferUtils.createByteBuffer(4);
}
oneTexel.clear();
GLStateManager.forcePixelUnpackState(PixelUnpackState.DEFAULT);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oneTexel);
GLStateManager.restorePixelUnpackBuffer();
GLStateManager.restorePixelUnpackState(PixelUnpackState.DEFAULT);
}
}
@@ -1,410 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
*
* <p>Maintains a single growable element buffer with pre-computed indices mapping
* GL_QUADS vertex order to GL_TRIANGLES: {@code (0,1,3, 1,2,3)} per quad.
* Uses {@code GL_LAST_VERTEX_CONVENTION} winding (Mesa {@code do_quad} LAST path)
* so both emitted triangles share the quad's provoking vertex (v3).
*/
public final class QuadConverter {
public static final int INDEX_TYPE = GL11.GL_UNSIGNED_INT;
private static int eboId;
private static int maxQuads;
/** Scratch EBO for converted quad-element draws (drawQuadElementsAsTriangles). */
private static int scratchEboId;
private static int scratchEboCapacity; // in bytes
private QuadConverter() {}
private static void ensureCapacity(int quadCount) {
if (quadCount <= maxQuads) return;
// Round up to power of 2 for amortized growth
int newMaxQuads = Math.max(256, maxQuads);
while (newMaxQuads < quadCount) {
newMaxQuads *= 2;
}
if (eboId == 0) {
eboId = RENDER_BACKEND.genBuffers();
}
// Generate index data: 6 ints per quad
final ByteBuffer indexData = memAlloc(newMaxQuads * 6 * 4);
long ptr = memAddress0(indexData);
for (int i = 0; i < newMaxQuads; i++) {
final int base = i * 4;
// GL_LAST_VERTEX_CONVENTION: both triangles share v3 as provoking vertex
// Triangle 1: 0, 1, 3
memPutInt(ptr, base);
memPutInt(ptr + 4, base + 1);
memPutInt(ptr + 8, base + 3);
// Triangle 2: 1, 2, 3
memPutInt(ptr + 12, base + 1);
memPutInt(ptr + 16, base + 2);
memPutInt(ptr + 20, base + 3);
ptr += 24;
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
memFree(indexData);
maxQuads = newMaxQuads;
}
/**
* Convert a GL_QUADS glDrawArrays call to indexed GL_TRIANGLES.
* Binds the shared EBO to the current VAO and issues glDrawElements.
*
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
drawSharedQuadEbo(first, vertexCount, 1, false);
}
public static void drawQuadsAsTrianglesInstanced(int first, int vertexCount, int primcount) {
drawSharedQuadEbo(first, vertexCount, primcount, true);
}
private static int misalignedDraws;
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) {
final int quadCount = vertexCount / 4;
if ((first & 3) != 0) {
misalignedDraws++;
if ((misalignedDraws & (misalignedDraws - 1)) == 0) {
GLStateManager.LOGGER.warn("QuadConverter: quad draw with unaligned first={} count={} (#{}) -- using scratch indices", first, vertexCount, misalignedDraws);
}
final ByteBuffer dst = memAlloc(quadCount * 6 * 4);
long ptr = memAddress0(dst);
for (int i = 0; i < quadCount; i++) {
final int base = first + i * 4;
memPutInt(ptr, base);
memPutInt(ptr + 4, base + 1);
memPutInt(ptr + 8, base + 3);
memPutInt(ptr + 12, base + 1);
memPutInt(ptr + 16, base + 2);
memPutInt(ptr + 20, base + 3);
ptr += 24;
}
uploadAndDraw(dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
return;
}
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
}
/**
* Upload converted triangle indices to the scratch EBO and draw.
* Saves/restores the caller's EBO binding so subsequent indexed draws
* (or glGetBufferSubData reads) still see the original EBO.
*
* @param dst off-heap buffer with triangle index data (freed by this method)
* @param triIndexCount number of triangle indices
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
uploadAndDraw(dst, triIndexCount, indexType, bytesPerIndex, 1, false);
}
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth -- allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
memFree(dst);
}
/**
* Convert an indexed GL_QUADS draw to GL_TRIANGLES by reading the source EBO,
* building a triangle index buffer, and issuing the converted draw.
*
* @param indexCount number of quad indices (must be multiple of 4)
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
* @param offset byte offset into the currently bound EBO
*/
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
drawQuadElementsFromEbo(indexCount, type, offset, 1, false);
}
public static void drawQuadElementsAsTrianglesInstanced(int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(indexCount, type, offset, primcount, true);
}
private static void drawQuadElementsFromEbo(int indexCount, int type, long offset, int primcount, boolean instanced) {
if (indexCount == 0) return;
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex = GLTypes.sizeBytes(type);
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
final ByteBuffer dst = memAlloc(triIndexCount * 4);
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
}
/**
* Convert a client-side IntBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Convert a client-side ShortBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final ByteBuffer dst = memAlloc(triIndexCount * 2);
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
}
/**
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
*/
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
if (count == 0) return;
assert count % 4 == 0;
final int quadCount = count / 4;
final int triIndexCount = quadCount * 6;
final long srcAddr = memAddress0(indices) + indices.position();
final ByteBuffer dst = memAlloc(triIndexCount * 4);
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Clean up the shared EBO.
*/
public static void destroy() {
if (eboId != 0) {
GLStateManager.glDeleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
GLStateManager.glDeleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
}
/**
* Read one index at {@code srcAddr + i * sizeof(srcType)} and return its
* zero-extended unsigned value.
*/
private static int readIndex(long srcAddr, int srcType, int i) {
return switch (srcType) {
case GL11.GL_UNSIGNED_BYTE -> memGetByte(srcAddr + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> memGetShort(srcAddr + i * 2L) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> memGetInt(srcAddr + i * 4L);
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(srcType));
};
}
/**
* Write one index at {@code dstAddr + i * sizeof(dstType)}.
*/
private static void writeIndex(long dstAddr, int dstType, int i, int value) {
switch (dstType) {
case GL11.GL_UNSIGNED_SHORT -> memPutShort(dstAddr + i * 2L, (short) value);
case GL11.GL_UNSIGNED_INT -> memPutInt(dstAddr + i * 4L, value);
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(dstType));
}
}
/**
* Read {@code quadCount} quads from {@code srcAddr} and emit 6 triangle indices
* per quad at {@code dstAddr} using {@code GL_LAST_VERTEX_CONVENTION}:
* {@code (a,b,d), (b,c,d)}. v3 is the provoking vertex for both triangles;
* diagonal runs b→d. Matches Mesa's {@code do_quad} LAST path.
*
* <p>Caller allocates dst of size {@code quadCount * 6 * sizeof(dstType)}.
*
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT}
* / {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Widening only.
*/
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount) {
triangulateQuads(srcAddr, srcType, dstAddr, dstType, quadCount, 0);
}
/**
* Same as {@link #triangulateQuads(long, int, long, int, int)} but subtracts
* {@code minVtx} from every emitted index, rebasing to a 0-based buffer in
* one pass.
*/
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount, int minVtx) {
for (int i = 0; i < quadCount; i++) {
final int a = readIndex(srcAddr, srcType, i * 4) - minVtx;
final int b = readIndex(srcAddr, srcType, i * 4 + 1) - minVtx;
final int c = readIndex(srcAddr, srcType, i * 4 + 2) - minVtx;
final int d = readIndex(srcAddr, srcType, i * 4 + 3) - minVtx;
final int o = i * 6;
writeIndex(dstAddr, dstType, o, a);
writeIndex(dstAddr, dstType, o + 1, b);
writeIndex(dstAddr, dstType, o + 2, d);
writeIndex(dstAddr, dstType, o + 3, b);
writeIndex(dstAddr, dstType, o + 4, c);
writeIndex(dstAddr, dstType, o + 5, d);
}
}
/**
* Copy {@code count} indices from {@code srcAddr} to {@code dstAddr}, converting
* from {@code srcType} to {@code dstType}.
*
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Widening only; narrowing is rejected by
* {@link #writeIndex(long, int, int, int) writeIndex}.
*/
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count) {
widenIndices(srcAddr, srcType, dstAddr, dstType, count, 0);
}
/**
* Same as {@link #widenIndices(long, int, long, int, int)} but subtracts
* {@code minVtx} from every written index in one pass.
*/
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count, int minVtx) {
for (int i = 0; i < count; i++) {
writeIndex(dstAddr, dstType, i, readIndex(srcAddr, srcType, i) - minVtx);
}
}
/**
* Scan {@code count} indices at {@code srcAddr} of {@code srcType}. Returns
* {@code (max << 32) | (min & 0xFFFFFFFFL)} or {@code -1L} for an unsupported
* type or empty range.
*/
public static long scanMinMaxIndex(long srcAddr, int srcType, int count) {
if (count <= 0) return -1L;
long min = Long.MAX_VALUE;
long max = Long.MIN_VALUE;
switch (srcType) {
case GL11.GL_UNSIGNED_BYTE -> {
for (int i = 0; i < count; i++) {
final int v = memGetByte(srcAddr + i) & 0xFF;
if (v < min) min = v;
if (v > max) max = v;
}
}
case GL11.GL_UNSIGNED_SHORT -> {
for (int i = 0; i < count; i++) {
final int v = memGetShort(srcAddr + i * 2L) & 0xFFFF;
if (v < min) min = v;
if (v > max) max = v;
}
}
case GL11.GL_UNSIGNED_INT -> {
for (int i = 0; i < count; i++) {
final long v = memGetInt(srcAddr + i * 4L) & 0xFFFFFFFFL;
if (v < min) min = v;
if (v > max) max = v;
}
// readIndex / writeIndex / rebase all route UINT through signed int.
// Reject the > 2^31 slice so the caller bails rather than silently
// emitting negative indices. MC never hits this in practice.
if (max > Integer.MAX_VALUE) return -1L;
}
default -> {
return -1L;
}
}
return (max << 32) | (min & 0xFFFFFFFFL);
}
}
@@ -1,687 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
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 lombok.Getter;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3i;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBShaderImageLoadStore;
import org.lwjgl.opengl.ARBShaderStorageBufferObject;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.NVXGpuMemoryInfo;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* This class is responsible for abstracting calls to OpenGL and asserting that calls are run on the render thread.
*/
public class RenderSystem {
private static DSAAccess dsaState;
private static boolean supportsCompute;
private static boolean supportsImageLoadStore;
private static boolean supportsSSBO;
private static boolean supportsMultiDrawIndirect;
private static boolean supportsBufferStorage;
private static boolean supportsClearTexture;
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
@Getter private static int glMajor;
@Getter private static int glMinor;
@Getter private static boolean coreProfile;
private static volatile boolean isGLES;
private static volatile int glesVersion;
private static volatile boolean glesDetected;
private static volatile boolean hasClipCullDistance;
private static volatile boolean isLTW;
private static volatile boolean ltwDetected;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
private static final Pattern SEMVER_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)\\.*(?<bugfix>\\d*)(.*)");
private RenderSystem() {}
private static boolean rendererInitialized;
public static void initRenderer() {
if (rendererInitialized) return;
rendererInitialized = true;
final boolean wasDetected = glesDetected;
if (isGLES() && !wasDetected) {
GLStateManager.LOGGER.info("OpenGL ES {}.{} detected", glesVersion / 100, (glesVersion / 10) % 10);
}
if (!isGLES) {
try {
if (GLStateManager.vendorIsIntel() && GLStateManager.isWindows()) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("Detected Intel drivers on Windows, disabling DSA.");
} else if (!GLStateManager.getInitConfig().isDSAEnabled()) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("enableDSA is set to false, disabling DSA.");
} else if (GLStateManager.capabilities.OpenGL45) {
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
GLStateManager.LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
try {
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
dsaState = new DSAARB();
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
}
if (dsaState == null) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("{}", isGLES ? "GLES context, using DSAUnsupported (bind-based) fallback." : "No DSA support detected, falling back to legacy OpenGL.");
}
BackendManager.init();
supportsCompute = supportsCompute();
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
supportsImageLoadStore = GLStateManager.capabilities.OpenGL42
|| GLStateManager.capabilities.GL_ARB_shader_image_load_store
|| GLStateManager.capabilities.GL_EXT_shader_image_load_store;
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
supportsMultiDrawIndirect = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_multi_draw_indirect;
supportsBufferStorage = !isGLES && (GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage);
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
// Cache maximum image units
if (supportsImageLoadStore) {
if (GLStateManager.capabilities.OpenGL42) {
maxImageUnits = RENDER_BACKEND.getInteger(GL42.GL_MAX_IMAGE_UNITS);
} else if (GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
maxImageUnits = RENDER_BACKEND.getInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS);
} else {
maxImageUnits = RENDER_BACKEND.getInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
}
} else {
maxImageUnits = 0;
}
// Cache maximum SSBO bindings
if (supportsSSBO) {
if (GLStateManager.capabilities.OpenGL43) {
maxSSBOBindings = RENDER_BACKEND.getInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
} else {
maxSSBOBindings = RENDER_BACKEND.getInteger(ARBShaderStorageBufferObject.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
}
} else {
maxSSBOBindings = 0;
}
// Check for sampler objects support (GL 3.3+ or ARB extension)
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
if (supportsSamplerObjects) {
samplers = new int[RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
}
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
GLStateManager.LOGGER.info("Max GLSL version: {}, GPU Shader4: {}", maxGlslVersion, supportsGpuShader4);
GLStateManager.LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
GLStateManager.LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
if (isGLES) {
GLStateManager.LOGGER.info("GL ES context detected, enabling shader transformer.");
ShaderManager.getInstance().enable();
} else if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
coreProfile = true;
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
} else {
throw new IllegalStateException("Non-core GL context (profile mask 0x" + Integer.toHexString(profileMask)
+ "); FFP emulation requires a core profile and nothing would render. Context creation should have rejected this.");
}
}
final Matcher glVerMatcher = SEMVER_PATTERN.matcher(RENDER_BACKEND.getString(GL11.GL_VERSION));
if (glVerMatcher.matches()) {
glMajor = Integer.parseInt(glVerMatcher.group("major"));
glMinor = Integer.parseInt(glVerMatcher.group("minor"));
}
}
public static void generateMipmaps(int texture, int mipmapTarget) {
dsaState.generateMipmaps(texture, mipmapTarget);
}
public static void bindAttributeLocation(int program, int index, CharSequence name) {
RENDER_BACKEND.bindAttribLocation(program, index, name);
}
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
final GLStateManager.GLESTexImageRemap remap = GLStateManager.remapTexImageForGLES(internalformat, format, type);
internalformat = remap.internalFormat();
format = remap.format();
type = remap.type();
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
if (target == GL11.GL_TEXTURE_2D && level == 0) {
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
}
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
}
public static void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
GLStateManager.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
}
public static void uniform1f(int location, float v0) {
RENDER_BACKEND.uniform1f(location, v0);
}
public static void uniform1i(int location, int v0) {
RENDER_BACKEND.uniform1i(location, v0);
}
public static void uniform2f(int location, float v0, float v1) {
RENDER_BACKEND.uniform2f(location, v0, v1);
}
public static void uniform2i(int location, int v0, int v1) {
RENDER_BACKEND.uniform2i(location, v0, v1);
}
public static void uniform3f(int location, float v0, float v1, float v2) {
RENDER_BACKEND.uniform3f(location, v0, v1, v2);
}
public static void uniform3i(int location, int v0, int v1, int v2) {RENDER_BACKEND.uniform3i(location, v0, v1, v2);}
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
RENDER_BACKEND.uniform4f(location, v0, v1, v2, v3);
}
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
RENDER_BACKEND.uniform4i(location, v0, v1, v2, v3);
}
public static int getAttribLocation(int programId, String name) {
return RENDER_BACKEND.getAttribLocation(programId, name);
}
public static int getUniformLocation(int programId, String name) {
return RENDER_BACKEND.getUniformLocation(programId, name);
}
public static void texParameteriv(int texture, int target, int pname, IntBuffer params) {
dsaState.texParameteriv(texture, target, pname, params);
}
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
}
public static void texParameteri(int texture, int target, int pname, int param) {
dsaState.texParameteri(texture, target, pname, param);
}
public static void texParameterf(int texture, int target, int pname, float param) {
dsaState.texParameterf(texture, target, pname, param);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static String getProgramInfoLog(int program) {
return RENDER_BACKEND.getProgramInfoLog(program, RENDER_BACKEND.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
public static String getShaderInfoLog(int shader) {
return RENDER_BACKEND.getShaderInfoLog(shader, RENDER_BACKEND.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
}
public static void drawBuffers(int framebuffer, IntBuffer buffers) {
dsaState.drawBuffers(framebuffer, buffers);
}
public static void readBuffer(int framebuffer, int buffer) {
dsaState.readBuffer(framebuffer, buffer);
}
public static String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType) {
return RENDER_BACKEND.getActiveUniform(program, index, maxLength, sizeType);
}
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
GLStateManager.suspendPixelPackBuffer();
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
GLStateManager.restorePixelPackBuffer();
}
public static void bufferData(int target, FloatBuffer data, int usage) {
GLStateManager.glBufferData(target, data, usage);
}
public static int bufferStorage(int target, FloatBuffer data, int usage) {
return dsaState.bufferStorage(target, data, usage);
}
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
}
public static void detachShader(int program, int shader) {
RENDER_BACKEND.detachShader(program, shader);
}
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
}
public static int getTexParameteri(int texture, int target, int pname) {
return dsaState.getTexParameteri(texture, target, pname);
}
public static float getTexParameterf(int texture, int target, int pname) {
return dsaState.getTexParameterf(texture, target, pname);
}
public static int getTexLevelParameteri(int texture, int level, int pname) {
return dsaState.getTexLevelParameteri(texture, level, pname);
}
public static void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
GLStateManager.glBindImageTexture(unit, texture, level, layered, layer, access, format);
}
public static int getMaxImageUnits() {
return maxImageUnits;
}
public static boolean supportsImageLoadStore() {
if (RENDER_BACKEND.isSDLGPU()) return true;
return supportsImageLoadStore;
}
public static boolean hasClipCullDistance() {
return hasClipCullDistance;
}
private static boolean hasExtension(String name) {
final int count = GLStateManager.glGetInteger(GL30.GL_NUM_EXTENSIONS);
for (int i = 0; i < count; i++) {
if (name.equals(GLStateManager.glGetStringi(GL11.GL_EXTENSIONS, i))) return true;
}
return false;
}
public static boolean isGLES() {
if (!glesDetected) detectGLES();
return isGLES;
}
private static synchronized void detectGLES() {
if (glesDetected) return;
try {
final String v = RENDER_BACKEND.getString(GL11.GL_VERSION);
if (v != null && v.startsWith("OpenGL ES ")) {
isGLES = true;
glesVersion = Integer.parseInt(parseGlVersionString(v));
}
glesDetected = true;
} catch (Throwable ignored) {
// No GL context on this thread yet (splash); retry on next call.
}
}
public static boolean isLTW() {
if (!ltwDetected) detectLTW();
return isLTW;
}
private static synchronized void detectLTW() {
if (ltwDetected) return;
if (SystemProperties.DISABLE_LTW_WORKAROUND) {
ltwDetected = true;
return;
}
try {
final String v = RENDER_BACKEND.getString(GL11.GL_VERSION);
if (v == null) return;
isLTW = LTWWorkaround.isLtwVersionString(v);
ltwDetected = true;
} catch (Throwable ignored) {
}
}
public static boolean supportsSSBO() {
return supportsSSBO;
}
public static boolean supportsMultiDrawIndirect() {
return supportsMultiDrawIndirect;
}
public static boolean supportsBufferStorage() {
return supportsBufferStorage;
}
public static boolean supportsClearTexture() {
return supportsClearTexture;
}
public static int getMaxSSBOBindings() {
return maxSSBOBindings;
}
public static void getProgramiv(int program, int value, IntBuffer storage) {
RENDER_BACKEND.getProgramiv(program, value, storage);
}
public static void dispatchCompute(int workX, int workY, int workZ) {
RENDER_BACKEND.dispatchCompute(workX, workY, workZ);
}
public static void dispatchCompute(Vector3i workGroups) {
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
}
public static void dispatchComputeIndirect(long offset) {
RENDER_BACKEND.dispatchComputeIndirect(offset);
}
public static void bindBuffer(int target, int buffer) {
GLStateManager.glBindBuffer(target, buffer);
}
public static void memoryBarrier(int barriers) {
if (supportsCompute) {
RENDER_BACKEND.memoryBarrier(barriers);
}
}
public static boolean supportsBufferBlending() {
return GLStateManager.capabilities.GL_ARB_draw_buffers_blend || GLStateManager.capabilities.OpenGL40;
}
public static void disableBufferBlend(int buffer) {
RENDER_BACKEND.disablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void enableBufferBlend(int buffer) {
RENDER_BACKEND.enablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
RENDER_BACKEND.blendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
}
public static void bindTextureToUnit(int unit, int texture) {
dsaState.bindTextureToUnit(unit, texture);
}
public static void bindTextureToUnit(int target, int unit, int texture) {
dsaState.bindTextureToUnit(target, unit, texture);
}
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
public static void setupProjectionMatrix(Matrix4f matrix) {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
matrix.get(0, PROJECTION_MATRIX_BUFFER);
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void restoreProjectionMatrix() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopMatrix();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter) {
final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
if (scissor) GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
try {
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
} finally {
if (scissor) GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
}
}
public static int createFramebuffer() {
return dsaState.createFramebuffer();
}
public static int createTexture(int target) {
return dsaState.createTexture(target);
}
// TODO: Proper notification of compute support
public static boolean supportsCompute() {
try {
return GLStateManager.capabilities.GL_ARB_compute_shader || GLStateManager.capabilities.OpenGL43;
} catch (Exception ignored) {
return false;
}
}
public static int createBuffers() {
return GLStateManager.glGenBuffers();
}
public static int getIndexedBufferBinding(int target, int index) {
return RENDER_BACKEND.getIndexedBufferBinding(target, index);
}
public static void bindBufferBase(int target, int index, int buffer) {
RENDER_BACKEND.bindBufferBase(target, index, buffer);
}
public static void bufferStorage(int target, long size, int flags) {
GLStateManager.glBufferStorage(target, size, flags);
}
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
buf.asIntBuffer().put(data);
GLStateManager.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
}
public static void deleteBuffers(int buffer) {
GLStateManager.glDeleteBuffers(buffer);
}
public static long getVRAM() {
if (GLStateManager.capabilities.GL_NVX_gpu_memory_info) {
return RENDER_BACKEND.getInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1024L;
} else {
// Default to 4GB if we can't query VRAM
return 4294967296L;
}
}
public static void clearTexImage(int texture, int target, int level, int format, int type) {
RENDER_BACKEND.clearTexImage(texture, level, format, type);
}
public static void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
dsaState.textureStorage1D(texture, target, levels, internalFormat, width);
}
public static void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
dsaState.textureStorage2D(texture, target, levels, internalFormat, width, height);
}
public static void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
dsaState.textureStorage3D(texture, target, levels, internalFormat, width, height, depth);
}
public static int getMaxGlslVersion() {
return maxGlslVersion;
}
public static boolean supportsGpuShader4() {
return supportsGpuShader4;
}
public static boolean supportsSnormFormats() {
if (isGLES()) return false;
return GLStateManager.capabilities.OpenGL31;
}
public static boolean supportsPackedFloatRenderable() {
return GLStateManager.capabilities.OpenGL30 && maxGlslVersion >= 130;
}
public static boolean supportsTesselation() {
return supportsTesselation;
}
public static boolean supportsSamplerObjects() {
return supportsSamplerObjects;
}
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return RENDER_BACKEND.genSamplers();
}
public static void destroySampler(int sampler) {
if (!supportsSamplerObjects || sampler == 0) return;
GLStateManager.glDeleteSamplers(sampler);
}
public static void samplerParameteri(int sampler, int pname, int param) {
if (!supportsSamplerObjects || sampler == 0) return;
RENDER_BACKEND.samplerParameteri(sampler, pname, param);
}
public static void bindSamplerToUnit(int unit, int sampler) {
if (!supportsSamplerObjects) return;
if (samplers[unit] == sampler) return;
RENDER_BACKEND.bindSampler(unit, sampler);
samplers[unit] = sampler;
}
public static void unbindAllSamplers() {
if (!supportsSamplerObjects) return;
for (int i = 0; i < samplers.length; i++) {
if (samplers[i] != 0) {
RENDER_BACKEND.bindSampler(i, 0);
samplers[i] = 0;
}
}
}
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Strips
* "OpenGL ES " / "OpenGL ES GLSL ES " prefixes so ES contexts parse too. */
public static String parseGlVersionString(String info) {
Objects.requireNonNull(info);
String stripped = info;
if (stripped.startsWith("OpenGL ES GLSL ES ")) {
stripped = stripped.substring("OpenGL ES GLSL ES ".length());
} else if (stripped.startsWith("OpenGL ES ")) {
stripped = stripped.substring("OpenGL ES ".length());
}
final Matcher matcher = SEMVER_PATTERN.matcher(stripped);
if (!matcher.matches()) {
throw new IllegalStateException("Could not parse GL version from \"" + info + "\"");
}
final String major = matcher.group("major");
final String minor = matcher.group("minor");
String bugfix = matcher.group("bugfix");
if (bugfix == null || bugfix.isEmpty()) bugfix = "0";
return major + minor + bugfix;
}
/** Parse a GL version string into packed major*10+minor (e.g. "4.3.0 - Build ..." -> 43), or -1 if unparseable. */
public static int parseMajorMinor(String glVersion) {
if (glVersion == null) return -1;
final Matcher matcher = SEMVER_PATTERN.matcher(glVersion.trim());
if (!matcher.matches()) return -1;
return Integer.parseInt(matcher.group("major")) * 10 + Integer.parseInt(matcher.group("minor"));
}
public static boolean isContextValid(int requestedMajor, int requestedMinor, int actualVersion, boolean coreBitSet) {
if (!coreBitSet) return false;
if (actualVersion < 0) return true;
return actualVersion >= requestedMajor * 10 + requestedMinor;
}
}
@@ -1,27 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
/// Matches
public enum Vendor {
AMD("amd"),
INTEL("intel"),
MESA("mesa"),
NVIDIA("nvidia"),
UNKNOWN("");
final String[] names;
Vendor(String... names) {
this.names = names;
}
public static Vendor getVendor(String vendorString) {
for (var v : values()) {
for (var name : v.names) {
if (vendorString.contains(name)) return v;
}
}
// Shouldn't be needed as "" matches everything, but sanity checks good.
return UNKNOWN;
}
}
@@ -1,53 +0,0 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Iterator;
import java.util.ServiceLoader;
public final class BackendManager {
private static final Logger LOGGER = LogManager.getLogger("BackendManager");
public static final RenderBackend RENDER_BACKEND = loadService();
private BackendManager() {}
public static void init() {
RENDER_BACKEND.init();
LOGGER.info("Initialized backend: {} (priority {})", RENDER_BACKEND.getName(), RENDER_BACKEND.getPriority());
}
public static void shutdown() {
RENDER_BACKEND.shutdown();
}
private static RenderBackend loadService() {
final ServiceLoader<RenderBackend> loader = ServiceLoader.load(RenderBackend.class, RenderBackend.class.getClassLoader());
RenderBackend best = null;
final Iterator<RenderBackend> iterator = loader.iterator();
int attempts = 0;
while (attempts < 16) {
try {
attempts++;
if (!iterator.hasNext()) break;
final RenderBackend backend = iterator.next();
LOGGER.info("Found backend: {} (priority {})", backend.getName(), backend.getPriority());
if (backend.isAvailable() && (best == null || backend.getPriority() > best.getPriority())) {
best = backend;
}
} catch (Throwable t) {
LOGGER.debug("Skipping unavailable backend: {}", t.getMessage());
}
}
if (best == null) {
throw new IllegalStateException("No available render backend!");
}
LOGGER.info("Selected backend: {} (priority {})", best.getName(), best.getPriority());
return best;
}
}
@@ -1,6 +0,0 @@
package com.gtnewhorizons.angelica.glsm.backend;
@FunctionalInterface
public interface DebugMessageHandler {
void handleMessage(int source, int type, int id, int severity, String message);
}
@@ -1,5 +0,0 @@
package com.gtnewhorizons.angelica.glsm.backend;
public interface GLDebugMessageListener {
void onMessage(int source, int type, int id, int severity, int length, long message, long userParam);
}
@@ -1,501 +0,0 @@
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.GL20;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Collections;
import java.util.List;
/**
* Abstract rendering backend.
*/
public abstract class RenderBackend {
public void onPostWindowCreate(long window) {}
public abstract void init();
public abstract void shutdown();
public abstract boolean isAvailable();
public abstract String getName();
/** Returns true if the current thread has a valid render context. */
public abstract boolean hasContext();
/** Returns true if the calling thread owns the "GL Context" */
public abstract boolean isCurrent();
/** Higher priority backends are preferred. */
public int getPriority() { return 0; }
public boolean supportsFileDrop() { return false; }
public void startFileDrop() {}
public void stopFileDrop() {}
public List<String> pollDroppedFiles() { return Collections.emptyList(); }
public boolean isIndirectRequired() { return false; }
public boolean supportsGeometryShaders() { return true; }
public boolean isSDLGPU() { return false; }
public boolean framebufferCompletenessIsMeaningful() { return true; }
public void onPersistentBufferWrite(int glId, long offset, long size) {}
public boolean isAnisotropicSupported() { return true; }
public boolean supportsGpuDrivenCulling() { return RenderSystem.supportsCompute(); }
public void beginComputeDispatchBatch() {}
public void endComputeDispatchBatch() {}
public abstract int getMinGLSLVersion();
public abstract void flush();
public abstract void finish();
protected abstract void setSwapInterval(boolean vsync);
private VSyncMode preferredVSyncMode = VSyncMode.AUTO;
private VSyncMode effectiveVSyncMode = VSyncMode.ON;
public final void setVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
preferredVSyncMode = preferred == null ? VSyncMode.AUTO : preferred;
effectiveVSyncMode = applyVSyncMode(preferredVSyncMode, vsyncEnabled);
}
protected VSyncMode applyVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
final VSyncMode mode = preferred.resolve(vsyncEnabled).swapIntervalEquivalent();
setSwapInterval(mode.usesVSyncSwapInterval());
return mode;
}
public boolean supportsVSyncMode(VSyncMode mode) { return mode != VSyncMode.MAILBOX; }
public int getMinRenderAhead() { return 0; }
public int getMaxRenderAhead() { return 9; }
public void setRenderAheadLimit(int frames) {}
public static int clampRenderAhead(int value, int min, int max) {
if (value <= 0) return min <= 0 ? 0 : max;
return Math.min(Math.max(value, Math.max(min, 1)), max);
}
public final VSyncMode getPreferredVSyncMode() { return preferredVSyncMode; }
public final VSyncMode getEffectiveVSyncMode() { return effectiveVSyncMode; }
public int getDisplayRefreshRateHz() { return 0; }
public static int refreshHzFrom(int numerator, int denominator, float fallbackHz) {
if (numerator > 0 && denominator > 0) return (numerator + denominator - 1) / denominator;
return fallbackHz > 0.0f ? (int) Math.ceil(fallbackHz) : 0;
}
public String getPresentDebugInfo() { return null; }
public void onFrameBegin() {
if (GLStateManager.takeStateSeedPending()) {
GLStateManager.replayStateToBackend();
}
}
public void onFrameEnd() {}
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
public void onPreSwapchainInvalidatingChange(Object change) {}
public boolean handleMakeCurrent(Object drawable) { return false; }
public boolean handleReleaseContext(Object drawable) { return false; }
public boolean handleSwapBuffers() { return false; }
public void onRenderThreadReleased(Thread thread) {}
public String getTransferDebugInfo() { return null; }
public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) { return false; }
public long beginVoxelizationBatch(int ssboBinding) { return 0L; }
public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {}
public void endVoxelizationBatch(long pass) {}
public abstract void enable(int cap);
public abstract void enablei(int cap, int index);
public abstract void disable(int cap);
public abstract void disablei(int cap, int index);
public abstract void blendFuncSeparatei(int buf, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
public abstract void blendFunc(int sfactor, int dfactor);
public abstract void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
public abstract void blendEquation(int mode);
public abstract void blendEquationSeparate(int modeRGB, int modeAlpha);
public abstract void blendColor(float red, float green, float blue, float alpha);
public abstract void depthFunc(int func);
public abstract void depthMask(boolean flag);
public abstract void colorMask(boolean red, boolean green, boolean blue, boolean alpha);
public abstract void cullFace(int mode);
public abstract void frontFace(int mode);
public abstract void polygonMode(int face, int mode);
public abstract void polygonOffset(float factor, float units);
public void polygonOffsetClamp(float factor, float units, float clamp) { throw new UnsupportedOperationException(getName() + ": polygon offset clamp unsupported"); }
public abstract void stencilFunc(int func, int ref, int mask);
public abstract void stencilOp(int sfail, int dpfail, int dppass);
public abstract void stencilMask(int mask);
public abstract void stencilFuncSeparate(int face, int func, int ref, int mask);
public abstract void stencilOpSeparate(int face, int sfail, int dpfail, int dppass);
public abstract void stencilMaskSeparate(int face, int mask);
public abstract void viewport(int x, int y, int width, int height);
public abstract void depthRange(double nearVal, double farVal);
public abstract void scissor(int x, int y, int width, int height);
public abstract void clearColor(float red, float green, float blue, float alpha);
public abstract void clearDepth(double depth);
public abstract void clearStencil(int s);
public abstract void clear(int mask);
public abstract void lineWidth(float width);
public abstract void pointSize(float size);
public abstract void logicOp(int opcode);
public abstract void hint(int target, int mode);
public abstract void drawArrays(int mode, int first, int count);
public abstract void drawElements(int mode, int count, int type, long indices);
public void drawElements(int mode, ByteBuffer indices) {}
public void drawElements(int mode, IntBuffer indices) {}
public void drawElements(int mode, ShortBuffer indices) {}
public void drawElements(int mode, int count, int type, ByteBuffer indices) {}
public abstract void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
public void multiDrawArraysIndirectCount(int mode, long indirect, long drawcount, int maxdrawcount, int stride) { throw new UnsupportedOperationException(getName() + ": indirect draw-count buffers unsupported"); }
public void multiDrawElementsIndirectCount(int mode, int type, long indirect, long drawcount, int maxdrawcount, int stride) { throw new UnsupportedOperationException(getName() + ": indirect draw-count buffers unsupported"); }
public abstract void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size);
public abstract void drawElementsInstanced(int mode, int count, int type, long indices, int primcount);
public abstract void drawArraysInstanced(int mode, int first, int count, int primcount);
public void provokingVertex(int provokeMode) {}
public abstract void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex);
public abstract void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex);
public void drawRangeElements(int mode, int start, int end, int count, int type, long indices) {
drawElements(mode, count, type, indices);
}
public void drawRangeElementsBaseVertex(int mode, int start, int end, int count, int type, long indices, int baseVertex) {
drawElementsBaseVertex(mode, count, type, indices, baseVertex);
}
public abstract void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts);
public abstract void primitiveRestartIndex(int index);
public abstract void pointParameterf(int pname, float param);
public abstract void pointParameteri(int pname, int param);
public abstract void drawBuffer(int mode);
public abstract void dispatchCompute(int numGroupsX, int numGroupsY, int numGroupsZ);
public abstract void dispatchComputeIndirect(long offset);
public abstract int genTextures();
public abstract void genTextures(IntBuffer textures);
public abstract void deleteTextures(int texture);
public abstract void bindTexture(int target, int texture);
public abstract void activeTexture(int texture);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset);
public abstract void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
public abstract void copyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height);
public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {}
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {}
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {}
public void texSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {}
public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {}
public void texSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {}
public void copyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {}
public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {}
public void copyTexSubImage1D(int target, int level, int xoffset, int x, int y, int width) {}
public void copyTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int x, int y, int width, int height) {}
public abstract void texParameteri(int target, int pname, int param);
public abstract void texParameterf(int target, int pname, float param);
public abstract void texParameteriv(int target, int pname, IntBuffer params);
public abstract void texParameterfv(int target, int pname, FloatBuffer params);
public abstract int getTexParameteri(int target, int pname);
public abstract float getTexParameterf(int target, int pname);
public abstract int getTexLevelParameteri(int target, int level, int pname);
public abstract void generateMipmap(int target);
public abstract void pixelStorei(int pname, int param);
public void sampleCoverage(float value, boolean invert) {}
public abstract int genSamplers();
public abstract void deleteSamplers(int sampler);
public abstract void bindSampler(int unit, int sampler);
public abstract void samplerParameteri(int sampler, int pname, int param);
public abstract void samplerParameterf(int sampler, int pname, float param);
public abstract int genFramebuffers();
public abstract void deleteFramebuffers(int framebuffer);
public abstract void bindFramebuffer(int target, int framebuffer);
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level);
public abstract void framebufferTexture(int target, int attachment, int texture, int level);
public abstract int genRenderbuffers();
public abstract void deleteRenderbuffers(int renderbuffer);
public abstract void bindRenderbuffer(int target, int renderbuffer);
public abstract void renderbufferStorage(int target, int internalformat, int width, int height);
public abstract void renderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height);
public abstract void framebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer);
public abstract int checkFramebufferStatus(int target);
public abstract void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public abstract void drawBuffers(int buffer);
public abstract void drawBuffers(IntBuffer bufs);
public abstract void readBuffer(int mode);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
public abstract int getFramebufferAttachmentParameteri(int target, int attachment, int pname);
public abstract int createShader(int type);
public abstract void deleteShader(int shader);
public abstract void shaderSource(int shader, CharSequence source);
public abstract void compileShader(int shader);
public void specializeShader(int shader, CharSequence entryPoint, IntBuffer constantIndex, IntBuffer constantValue) { throw new UnsupportedOperationException(getName() + ": externally supplied SPIR-V binaries unsupported"); }
public abstract int createProgram();
public abstract void deleteProgram(int program);
public abstract void attachShader(int program, int shader);
public abstract void detachShader(int program, int shader);
public abstract void linkProgram(int program);
public abstract void useProgram(int program);
public abstract String getShaderInfoLog(int shader, int maxLength);
public String getShaderInfoLog(int shader) {
return getShaderInfoLog(shader, getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
}
public abstract void getShaderInfoLog(int shader, IntBuffer length, ByteBuffer infoLog);
public abstract String getProgramInfoLog(int program, int maxLength);
public String getProgramInfoLog(int program) {
return getProgramInfoLog(program, getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
public abstract void getProgramInfoLog(int program, IntBuffer length, ByteBuffer infoLog);
public abstract int getShaderi(int shader, int pname);
public abstract int getProgrami(int program, int pname);
public abstract void getProgramiv(int program, int pname, IntBuffer params);
public abstract String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType);
public abstract void getActiveUniform(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name);
public abstract void getActiveAttrib(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name);
public abstract String getActiveAttrib(int program, int index, int maxLength, IntBuffer sizeType);
public abstract void bindAttribLocation(int program, int index, CharSequence name);
public void bindFragDataLocation(int program, int colorNumber, CharSequence name) {}
public abstract int getAttribLocation(int program, CharSequence name);
public abstract int getAttribLocation(int program, ByteBuffer name);
public abstract int getUniformLocation(int program, CharSequence name);
public abstract int getUniformLocation(int program, ByteBuffer name);
public boolean isShader(int obj) { return false; }
public boolean isProgram(int obj) { return false; }
public void validateProgram(int program) {}
public void getAttachedShaders(int program, IntBuffer count, IntBuffer shaders) {
if (count != null) count.put(0, 0);
}
public String getShaderSource(int shader, int maxLength) { return ""; }
public abstract void getShaderSource(int shader, IntBuffer length, ByteBuffer source);
public abstract void uniform1i(int location, int v0);
public abstract void uniform1f(int location, float v0);
public void uniform1fv(int location, FloatBuffer values) {}
public void uniform3fv(int location, float[] values) {}
public void uniform4fv(int location, float[] values) {}
public abstract void uniform2f(int location, float v0, float v1);
public abstract void uniform2i(int location, int v0, int v1);
public abstract void uniform3f(int location, float v0, float v1, float v2);
public abstract void uniform3i(int location, int v0, int v1, int v2);
public abstract void uniform4f(int location, float v0, float v1, float v2, float v3);
public abstract void uniform4i(int location, int v0, int v1, int v2, int v3);
public abstract void uniform2(int location, FloatBuffer value);
public abstract void uniform3(int location, FloatBuffer value);
public abstract void uniform4(int location, FloatBuffer value);
public abstract void uniform1iv(int location, IntBuffer value);
public abstract void uniform2iv(int location, IntBuffer value);
public abstract void uniform3iv(int location, IntBuffer value);
public abstract void uniform4iv(int location, IntBuffer value);
public abstract void uniformMatrix3(int location, boolean transpose, FloatBuffer value);
public abstract void uniformMatrix4(int location, boolean transpose, FloatBuffer value);
public void uniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {}
public void getUniformfv(int program, int location, FloatBuffer params) {}
public void getUniformiv(int program, int location, IntBuffer params) {}
public abstract void vertexAttrib2f(int index, float v0, float v1);
public abstract void vertexAttrib3f(int index, float v0, float v1, float v2);
public abstract void vertexAttrib4f(int index, float v0, float v1, float v2, float v3);
public abstract int genBuffers();
public abstract void deleteBuffers(int buffer);
public abstract void deleteBuffers(IntBuffer buffers);
public abstract void bindBuffer(int target, int buffer);
public abstract void bindBufferBase(int target, int index, int buffer);
public abstract void bindBufferRange(int target, int index, int buffer, long offset, long size);
public abstract int getUniformBlockIndex(int program, CharSequence name);
public abstract void uniformBlockBinding(int program, int blockIndex, int binding);
public abstract int getIndexedBufferBinding(int target, int index);
public abstract int getIntegerIndexed(int pname, int index);
public abstract void bufferData(int target, long size, int usage);
public abstract void bufferData(int target, ByteBuffer data, int usage);
public abstract void bufferData(int target, FloatBuffer data, int usage);
public abstract void bufferData(int target, ShortBuffer data, int usage);
public abstract void bufferData(int target, IntBuffer data, int usage);
public abstract void bufferData(int target, DoubleBuffer data, int usage);
public abstract void bufferData(int target, int[] data, int usage);
public abstract void bufferData(int target, float[] data, int usage);
public abstract void bufferSubData(int target, long offset, ByteBuffer data);
public abstract void bufferSubData(int target, long offset, ShortBuffer data);
public abstract void bufferSubData(int target, long offset, IntBuffer data);
public abstract void bufferSubData(int target, long offset, FloatBuffer data);
public abstract void bufferSubData(int target, long offset, DoubleBuffer data);
public abstract ByteBuffer mapBuffer(int target, int access);
public abstract ByteBuffer mapBuffer(int target, int access, long length, ByteBuffer old_buffer);
public abstract boolean unmapBuffer(int target);
public abstract void bufferStorage(int target, ByteBuffer data, int flags);
public abstract void bufferStorage(int target, long size, int flags);
public abstract void getBufferSubData(int target, long offset, ByteBuffer data);
public abstract void getBufferSubData(int target, long offset, ShortBuffer data);
public abstract void getBufferSubData(int target, long offset, IntBuffer data);
public abstract void getBufferSubData(int target, long offset, FloatBuffer data);
public abstract void getBufferSubData(int target, long offset, DoubleBuffer data);
public abstract int getBufferParameteri(int target, int pname);
public abstract boolean isBuffer(int buffer);
public abstract ByteBuffer mapBufferRange(int target, long offset, long length, int access);
public abstract void flushMappedBufferRange(int target, long offset, long length);
public long mapBufferRangeAddress(int target, long offset, long length, int access) {
final ByteBuffer buf = mapBufferRange(target, offset, length, access);
return buf == null ? 0L : MemoryUtilities.memAddress0(buf);
}
public abstract boolean isTexture(int texture);
public abstract boolean isFramebuffer(int framebuffer);
public abstract boolean isRenderbuffer(int renderbuffer);
public abstract boolean isSampler(int sampler);
public abstract boolean isQuery(int query);
public abstract int genVertexArrays();
public abstract void deleteVertexArrays(int array);
public abstract void bindVertexArray(int array);
public abstract boolean isVertexArray(int array);
public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer);
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
vertexAttribPointer(index, size, type, normalized, stride, MemoryUtilities.memAddress0(pointer));
}
public void vertexAttribIPointer(int index, int size, int type, int stride, ByteBuffer pointer) {
vertexAttribIPointer(index, size, type, stride, MemoryUtilities.memAddress0(pointer));
}
public abstract void vertexAttribIPointer(int index, int size, int type, int stride, long pointer);
public abstract void enableVertexAttribArray(int index);
public abstract void disableVertexAttribArray(int index);
public abstract void vertexAttribDivisor(int index, int divisor);
public abstract void bindVertexBuffer(int bindingindex, int buffer, long offset, int stride);
public abstract void vertexAttribFormat(int attribindex, int size, int type, boolean normalized, int relativeoffset);
public abstract void vertexAttribIFormat(int attribindex, int size, int type, int relativeoffset);
public abstract void vertexAttribBinding(int attribindex, int bindingindex);
public abstract int createTextures(int target);
public abstract void bindTextureUnit(int unit, int texture);
public abstract void textureParameteri(int texture, int target, int pname, int param);
public abstract void textureParameterf(int texture, int target, int pname, float param);
public abstract void textureParameteriv(int texture, int target, int pname, IntBuffer params);
public abstract void texStorage1D(int target, int levels, int internalFormat, int width);
public abstract void texStorage2D(int target, int levels, int internalFormat, int width, int height);
public abstract void texStorage3D(int target, int levels, int internalFormat, int width, int height, int depth);
public abstract void textureStorage1D(int texture, int levels, int internalFormat, int width);
public abstract void textureStorage2D(int texture, int levels, int internalFormat, int width, int height);
public abstract void textureStorage3D(int texture, int levels, int internalFormat, int width, int height, int depth);
public abstract void generateTextureMipmap(int texture);
public abstract void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
public abstract void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
public abstract void textureSubImage2D(int texture, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void textureSubImage2D(int texture, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
public abstract int createFramebuffers();
public abstract void namedFramebufferTexture(int framebuffer, int attachment, int texture, int level);
public abstract void namedFramebufferReadBuffer(int framebuffer, int mode);
public abstract void namedFramebufferDrawBuffers(int framebuffer, IntBuffer bufs);
public abstract 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);
public abstract int createBuffers();
public abstract void namedBufferData(int buffer, long size, int usage);
public abstract void namedBufferData(int buffer, ByteBuffer data, int usage);
public abstract void namedBufferData(int buffer, FloatBuffer data, int usage);
public abstract void namedBufferSubData(int buffer, long offset, ByteBuffer data);
public abstract void copyTextureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int x, int y, int width, int height);
public abstract int getTextureParameteri(int texture, int target, int pname);
public abstract float getTextureParameterf(int texture, int target, int pname);
public abstract int getTextureLevelParameteri(int texture, int level, int pname);
public abstract int getInteger(int pname);
public abstract void getInteger(int pname, IntBuffer params);
public void getInteger(int pname, int[] params) { if (params.length > 0) params[0] = getInteger(pname); }
public abstract float getFloat(int pname);
public abstract void getFloat(int pname, FloatBuffer params);
public double getDouble(int pname) { return getFloat(pname); }
public void getDouble(int pname, DoubleBuffer params) { if (params.remaining() > 0) params.put(params.position(), getDouble(pname)); }
public abstract boolean getBoolean(int pname);
public abstract void getBoolean(int pname, ByteBuffer params);
public abstract String getString(int pname);
public abstract String getStringi(int name, int index);
public abstract int getError();
public abstract long fenceSync(int condition, int flags);
public abstract int clientWaitSync(long sync, int flags, long timeout);
public abstract void deleteSync(long sync);
public void waitSync(long sync, int flags, long timeout) { throw new UnsupportedOperationException("sync objects unsupported"); }
public int getSynci(long sync, int pname, IntBuffer length) { throw new UnsupportedOperationException("sync objects unsupported"); }
public void genQueries(IntBuffer ids) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public int genQueries() { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void deleteQueries(int id) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void beginQuery(int target, int id) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void endQuery(int target) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void getQueryObjectui(int id, int pname, IntBuffer params) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public int getQueryObjecti(int id, int pname) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void queryCounter(int id, int target) { throw new UnsupportedOperationException("timer queries unsupported"); }
public long getQueryObjectui64(int id, int pname) { throw new UnsupportedOperationException("timer queries unsupported"); }
public boolean supportsGpuProfiling() { return false; }
public int genQuery() { throw new UnsupportedOperationException("timer queries unsupported"); }
public void deleteQuery(int query) { throw new UnsupportedOperationException("timer queries unsupported"); }
public void queryCounter(int query) { throw new UnsupportedOperationException("timer queries unsupported"); }
public boolean isQueryResultAvailable(int query) { throw new UnsupportedOperationException("timer queries unsupported"); }
public long getQueryResult64(int query) { throw new UnsupportedOperationException("timer queries unsupported"); }
public long getGpuTimestamp() { throw new UnsupportedOperationException("timer queries unsupported"); }
public abstract void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data);
public abstract void clearBufferData(int target, int internalformat, int format, int type, ByteBuffer data);
public abstract void clearTexImage(int texture, int level, int format, int type);
public abstract void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format);
public abstract void memoryBarrier(int barriers);
public abstract void copyImageSubData(int srcName, int srcTarget, int srcLevel, int srcX, int srcY, int srcZ,
int dstName, int dstTarget, int dstLevel, int dstX, int dstY, int dstZ,
int srcWidth, int srcHeight, int srcDepth);
// Debug output
public boolean supportsDebugOutput() { return false; }
public int setupDebugOutput(DebugMessageHandler handler) { return 0; }
public int disableDebugOutput() { return 0; }
// Object labeling
public void objectLabel(int identifier, int name, CharSequence label) {}
public String getObjectLabel(int identifier, int name) { return ""; }
// Debug groups
public void pushDebugGroup(int source, int id, CharSequence message) {}
public void popDebugGroup() {}
// Debug message insertion
public void debugMessageInsert(int source, int type, int id, int severity, CharSequence message) {}
public void debugMessageControl(int source, int type, int severity, IntBuffer ids, boolean enabled) {}
public void debugMessageCallback(GLDebugMessageListener listener, long userParam) {}
public int getDebugMessageLog(int count, IntBuffer sources, IntBuffer types, IntBuffer ids, IntBuffer severities, IntBuffer lengths, ByteBuffer messageLog) { return 0; }
}
@@ -1,54 +0,0 @@
package com.gtnewhorizons.angelica.glsm.backend;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public enum VSyncMode {
AUTO,
ON,
MAILBOX,
OFF;
private static final List<VSyncMode> OFF_ORDER = List.of(OFF, MAILBOX, ON);
private static final List<VSyncMode> MAILBOX_ORDER = List.of(MAILBOX, ON);
private static final List<VSyncMode> ON_ORDER = List.of(ON);
private static final List<VSyncMode> DISPLAY_ORDER = List.of(OFF, ON, MAILBOX);
public boolean blocksOnVBlank() {
return this == ON;
}
public boolean usesVSyncSwapInterval() {
return this == ON || this == MAILBOX;
}
public VSyncMode swapIntervalEquivalent() {
return this == MAILBOX ? ON : this;
}
public VSyncMode resolve(boolean vsyncEnabled) {
return this != AUTO ? this : (vsyncEnabled ? ON : OFF);
}
public List<VSyncMode> fallbackOrder() {
return switch (this) {
case OFF -> OFF_ORDER;
case MAILBOX -> MAILBOX_ORDER;
default -> ON_ORDER;
};
}
public boolean tearFree() {
return this != OFF;
}
public static List<VSyncMode> selectable(Predicate<VSyncMode> supported) {
final List<VSyncMode> modes = new ArrayList<>(DISPLAY_ORDER.size());
for (VSyncMode mode : DISPLAY_ORDER) {
if (supported.test(mode)) modes.add(mode);
}
return modes;
}
}
@@ -1,11 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat;
/**
* Simple holder for fog color values captured from vanilla rendering.
* Used by terrain renderers to access fog state.
*/
public class FogHelper {
public static float red;
public static float green;
public static float blue;
}
@@ -1,142 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Cylinder;
import static org.lwjgl.opengl.GL11.GL_LINES;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Cylinder}.
*/
@SuppressWarnings("unused")
public class AngelicaCylinder extends Cylinder {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float baseRadius, float topRadius, float height, int slices, int stacks) {
final float da, dr, dz, nz, nsign;
float r, x, y, z;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
da = 2.0f * PI / slices;
dr = (topRadius - baseRadius) / stacks;
dz = height / stacks;
nz = (baseRadius - topRadius) / height;
if (super.drawStyle == GLU_POINT) {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glVertex3f(x * r, y * r, z);
z += dz;
r += dr;
}
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// Draw rings
if (super.drawStyle == GLU_LINE) {
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * r, y * r, z);
}
GLStateManager.glEnd();
z += dz;
r += dr;
}
} else {
// draw one ring at each end
if (baseRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
}
GLStateManager.glEnd();
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
}
}
// draw length lines
GLStateManager.glBegin(GL_LINES);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_FILL) {
final float ds = 1.0f / slices;
final float dt = 1.0f / stacks;
float t = 0.0f;
z = 0.0f;
r = baseRadius;
for (int j = 0; j < stacks; j++) {
float s = 0.0f;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
final float angle = (i == slices) ? 0.0f : i * da;
x = sin(angle);
y = cos(angle);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * r, y * r, z);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t + dt);
GLStateManager.glVertex3f(x * (r + dr), y * (r + dr), z + dz);
s += ds;
}
GLStateManager.glEnd();
r += dr;
t += dt;
z += dz;
}
}
}
}
@@ -1,134 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Disk;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Disk}.
*/
@SuppressWarnings("unused")
public class AngelicaDisk extends Disk {
private static final float PI = (float) Math.PI;
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops) {
final float da, dr;
if (super.normals != GLU_NONE) {
GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
}
da = 2.0f * PI / slices;
dr = (outerRadius - innerRadius) / loops;
switch (super.drawStyle) {
case GLU_FILL -> {
final float dtc = 2.0f * outerRadius;
float sa, ca;
float r1 = innerRadius;
for (int l = 0; l < loops; l++) {
final float r2 = r1 + dr;
if (super.orientation == GLU_OUTSIDE) {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = 0; s <= slices; s++) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f + sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f + sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
} else {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = slices; s >= 0; s--) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f - sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f - sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
}
r1 = r2;
}
}
case GLU_LINE -> {
/* draw loops */
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int s = 0; s < slices; s++) {
final float a = s * da;
GLStateManager.glVertex2f(r * sin(a), r * cos(a));
}
GLStateManager.glEnd();
}
/* draw spokes */
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
GLStateManager.glBegin(GL_LINE_STRIP);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius * l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
}
GLStateManager.glEnd();
}
case GLU_SILHOUETTE -> {
if (innerRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0.0f; a < 2.0 * PI; a += da) {
final float x = innerRadius * sin(a);
final float y = innerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0; a < 2.0f * PI; a += da) {
final float x = outerRadius * sin(a);
final float y = outerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
default -> {
}
}
}
}
@@ -1,243 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.PartialDisk;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_FLAT;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
import static org.lwjgl.util.glu.GLU.GLU_SMOOTH;
/**
* Core-profile compatible replacement for {@link PartialDisk}.
*/
@SuppressWarnings("unused")
public class AngelicaPartialDisk extends PartialDisk {
private static final float PI = (float) Math.PI;
private static final int CACHE_SIZE = 240;
private final float[] sinCache = new float[CACHE_SIZE];
private final float[] cosCache = new float[CACHE_SIZE];
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops, float startAngle, float sweepAngle) {
final float deltaRadius, angleOffset;
float angle, sintemp, costemp, radiusLow, radiusHigh, texLow = 0, texHigh = 0;
final int slices2, finish;
if (slices >= CACHE_SIZE) slices = CACHE_SIZE - 1;
if (slices < 2 || loops < 1 || outerRadius <= 0.0f || innerRadius < 0.0f || innerRadius > outerRadius) {
GLStateManager.LOGGER.warn("PartialDisk: GLU_INVALID_VALUE (slices={}, loops={}, innerRadius={}, outerRadius={})", slices, loops, innerRadius, outerRadius);
return;
}
if (sweepAngle < -360.0f) sweepAngle = 360.0f;
if (sweepAngle > 360.0f) sweepAngle = 360.0f;
if (sweepAngle < 0) {
startAngle += sweepAngle;
sweepAngle = -sweepAngle;
}
slices2 = (sweepAngle == 360.0f) ? slices : slices + 1;
/* Compute length (needed for normal calculations) */
deltaRadius = outerRadius - innerRadius;
angleOffset = startAngle / 180.0f * PI;
for (int i = 0; i <= slices; i++) {
angle = angleOffset + ((PI * sweepAngle) / 180.0f) * i / slices;
sinCache[i] = sin(angle);
cosCache[i] = cos(angle);
}
if (sweepAngle == 360.0f) {
sinCache[slices] = sinCache[0];
cosCache[slices] = cosCache[0];
}
switch (super.normals) {
case GLU_FLAT, GLU_SMOOTH -> GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
default -> {
}
}
switch (super.drawStyle) {
case GLU_FILL -> {
if (innerRadius == .0f) {
finish = loops - 1;
/* Triangle strip for inner polygons */
GLStateManager.glBegin(GL_TRIANGLE_FAN);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(0.5f, 0.5f);
}
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
radiusLow = outerRadius - deltaRadius * ((float) (loops - 1) / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
if (super.orientation == GLU_OUTSIDE) {
for (int i = slices; i >= 0; i--) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
} else {
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
}
GLStateManager.glEnd();
} else {
finish = loops;
}
for (int j = 0; j < finish; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
radiusHigh = outerRadius - deltaRadius * ((float) (j + 1) / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
texHigh = radiusHigh / outerRadius / 2;
}
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.orientation == GLU_OUTSIDE) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
} else {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices2; i++) {
sintemp = sinCache[i];
costemp = cosCache[i];
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
}
GLStateManager.glEnd();
}
case GLU_LINE -> {
if (innerRadius == outerRadius) {
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(sinCache[i] / 2 + 0.5f, cosCache[i] / 2 + 0.5f);
}
GLStateManager.glVertex3f(innerRadius * sinCache[i], innerRadius * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
break;
}
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
}
for (int i = 0; i < slices2; i++) {
sintemp = sinCache[i];
costemp = cosCache[i];
GLStateManager.glBegin(GL_LINE_STRIP);
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
GLStateManager.glEnd();
}
}
case GLU_SILHOUETTE -> {
if (sweepAngle < 360.0f) {
for (int i = 0; i <= slices; i += slices) {
sintemp = sinCache[i];
costemp = cosCache[i];
GLStateManager.glBegin(GL_LINE_STRIP);
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
GLStateManager.glEnd();
}
}
for (int j = 0; j <= loops; j += loops) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
if (innerRadius == outerRadius) break;
}
}
default -> {
}
}
}
}
@@ -1,183 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Sphere;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Sphere}.
*/
@SuppressWarnings("unused")
public class AngelicaSphere extends Sphere {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float radius, int slices, int stacks) {
final float drho, dtheta, ds, dt, nsign;
float rho, theta, x, y, z, s, t;
final int imin, imax;
final boolean normals;
normals = super.normals != GLU_NONE;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
drho = PI / stacks;
dtheta = 2.0f * PI / slices;
if (super.drawStyle == GLU_FILL) {
if (!super.textureFlag) {
// draw +Z end as a triangle fan
GLStateManager.glBegin(GL_TRIANGLE_FAN);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glVertex3f(0.0f, 0.0f, nsign * radius);
for (int j = 0; j <= slices; j++) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(drho);
y = cos(theta) * sin(drho);
z = nsign * cos(drho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
ds = 1.0f / slices;
dt = 1.0f / stacks;
t = 1.0f;
if (super.textureFlag) {
imin = 0;
imax = stacks;
} else {
imin = 1;
imax = stacks - 1;
}
// draw intermediate stacks as triangle strips (GL_QUAD_STRIP removed in core profile)
for (int i = imin; i < imax; i++) {
rho = i * drho;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
s = 0.0f;
for (int j = 0; j <= slices; j++) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(rho);
y = cos(theta) * sin(rho);
z = nsign * cos(rho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
x = -sin(theta) * sin(rho + drho);
y = cos(theta) * sin(rho + drho);
z = nsign * cos(rho + drho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
emitTexCoord(s, t - dt);
s += ds;
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
t -= dt;
}
if (!super.textureFlag) {
// draw -Z end as a triangle fan
GLStateManager.glBegin(GL_TRIANGLE_FAN);
GLStateManager.glNormal3f(0.0f, 0.0f, -1.0f);
GLStateManager.glVertex3f(0.0f, 0.0f, -radius * nsign);
rho = PI - drho;
s = 1.0f;
for (int j = slices; j >= 0; j--) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(rho);
y = cos(theta) * sin(rho);
z = nsign * cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
s -= ds;
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// draw stack lines
for (int i = 1; i < stacks; i++) {
rho = i * drho;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
// draw slice lines
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= stacks; i++) {
rho = i * drho;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
} else if (super.drawStyle == GLU_POINT) {
// top and bottom-most points
GLStateManager.glBegin(GL_POINTS);
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, nsign);
GLStateManager.glVertex3f(0.0f, 0.0f, radius);
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, -nsign);
GLStateManager.glVertex3f(0.0f, 0.0f, -radius);
// loop over stacks
for (int i = 1; i < stacks - 1; i++) {
rho = i * drho;
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
}
GLStateManager.glEnd();
}
}
}
@@ -1,154 +0,0 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public class DSAARB extends DSAUnsupported {
@Override
public void generateMipmaps(int texture, int target) {
RENDER_BACKEND.generateTextureMipmap(texture);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
// Note: DSA glTextureImage2D doesn't exist in ARB_direct_state_access
// We need to use glTextureStorage2D + glTextureSubImage2D, or fall back to bind-based approach
// For simplicity, use the fallback which binds, uploads, and restores
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.textureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
RENDER_BACKEND.textureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
param = GLStateManager.remapTexClamp(pname, param);
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
RENDER_BACKEND.textureParameteri(texture, target, pname, param);
}
@Override
public void texParameterf(int texture, int target, int pname, float param) {
param = GLStateManager.remapTexClamp(pname, param);
if(!GLStateManager.updateTexParameterfCache(target, texture, pname, param)) return;
RENDER_BACKEND.textureParameterf(texture, target, pname, param);
}
@Override
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
GLStateManager.remapTexClampBuffer(pname, params);
RENDER_BACKEND.textureParameteriv(texture, target, pname, params);
}
@Override
public void readBuffer(int framebuffer, int buffer) {
RENDER_BACKEND.namedFramebufferReadBuffer(framebuffer, buffer);
}
@Override
public void drawBuffers(int framebuffer, IntBuffer buffers) {
RENDER_BACKEND.namedFramebufferDrawBuffers(framebuffer, buffers);
}
@Override
public int getTexParameteri(int texture, int target, int pname) {
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname));
}
@Override
public float getTexParameterf(int texture, int target, int pname) {
return RENDER_BACKEND.getTextureParameterf(texture, target, pname);
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
return RENDER_BACKEND.getTextureLevelParameteri(texture, level, pname);
}
@Override
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
RENDER_BACKEND.copyTextureSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
}
@Override
public void bindTextureToUnit(int unit, int texture) {
bindTextureToUnit(0, unit, texture);
}
@Override
public void bindTextureToUnit(int target, int unit, int texture) {
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
if (texture == 0) {
super.bindTextureToUnit(target != 0 ? target : GL11.GL_TEXTURE_2D, unit, texture);
} else {
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture, target);
GLStateManager.trackMaxBoundTextureUnit(unit);
}
}
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = GLStateManager.glCreateBuffers();
GLStateManager.glNamedBufferData(buffer, data, usage);
return buffer;
}
@Override
public void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2,
int bufferChoice, int filter) {
RENDER_BACKEND.blitNamedFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
}
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
RENDER_BACKEND.namedFramebufferTexture(fb, attachment, texture, levels);
}
@Override
public int createFramebuffer() {
return RENDER_BACKEND.createFramebuffers();
}
@Override
public int createTexture(int target) {
return RENDER_BACKEND.createTextures(target);
}
@Override
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
RENDER_BACKEND.textureStorage1D(texture, levels, internalFormat, width);
}
@Override
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
RENDER_BACKEND.textureStorage2D(texture, levels, internalFormat, width, height);
}
@Override
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
RENDER_BACKEND.textureStorage3D(texture, levels, internalFormat, width, height, depth);
}
}
@@ -1,48 +0,0 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public interface DSAAccess {
void generateMipmaps(int texture, int target);
// Texture image upload - DSA allows specifying texture directly without binding
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
void texParameteri(int texture, int target, int pname, int param);
void texParameterf(int texture, int target, int pname, float param);
void texParameteriv(int texture, int target, int pname, IntBuffer params);
void readBuffer(int framebuffer, int buffer);
void drawBuffers(int framebuffer, IntBuffer buffers);
int getTexParameteri(int texture, int target, int pname);
float getTexParameterf(int texture, int target, int pname);
int getTexLevelParameteri(int texture, int level, int pname);
void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height);
void bindTextureToUnit(int unit, int texture);
void bindTextureToUnit(int target, int unit, int texture);
int bufferStorage(int target, FloatBuffer data, int usage);
void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter);
void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels);
int createFramebuffer();
int createTexture(int target);
// Texture storage methods for custom images
void textureStorage1D(int texture, int target, int levels, int internalFormat, int width);
void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height);
void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth);
}
@@ -1,18 +0,0 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public class DSAEXT extends DSACore {
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.textureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
RENDER_BACKEND.textureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
}
@@ -1,189 +0,0 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public class DSAUnsupported implements DSAAccess {
@Override
public void generateMipmaps(int texture, int target) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
RENDER_BACKEND.generateMipmap(target);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
RENDER_BACKEND.bindTexture(target, cachedBinding); // Restore to cache value
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
RENDER_BACKEND.bindTexture(target, cachedBinding);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
RENDER_BACKEND.bindTexture(target, cachedBinding);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
RENDER_BACKEND.bindTexture(target, cachedBinding);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameteri(target, pname, param);
}
@Override
public void texParameterf(int texture, int target, int pname, float param) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameterf(target, pname, param);
}
@Override
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameter(target, pname, params);
}
@Override
public void readBuffer(int framebuffer, int buffer) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
RENDER_BACKEND.readBuffer(buffer);
}
@Override
public void drawBuffers(int framebuffer, IntBuffer buffers) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
RENDER_BACKEND.drawBuffers(buffers);
}
@Override
public int getTexParameteri(int texture, int target, int pname) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return GLStateManager.glGetTexParameteri(target, pname);
}
@Override
public float getTexParameterf(int texture, int target, int pname) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return GLStateManager.glGetTexParameterf(target, pname);
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
final int previous = GLStateManager.getBoundTextureForServerState();
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, texture);
final int result = RENDER_BACKEND.getTexLevelParameteri(GL11.GL_TEXTURE_2D, level, pname);
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, previous);
return result;
}
@Override
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
final int previous = GLStateManager.getBoundTextureForServerState();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, destTexture);
RENDER_BACKEND.copyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
}
@Override
public void bindTextureToUnit(int unit, int texture) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
@Override
public void bindTextureToUnit(int target, int unit, int texture) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glBindTexture(target, texture);
}
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(target, buffer);
GLStateManager.glBufferData(target, data, usage);
GLStateManager.glBindBuffer(target, 0);
return buffer;
}
@Override
public void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2,
int bufferChoice, int filter) {
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, source);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, dest);
RENDER_BACKEND.blitFramebuffer(offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
}
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
GLStateManager.glBindFramebuffer(fbtarget, fb);
RENDER_BACKEND.framebufferTexture2D(fbtarget, attachment, target, texture, levels);
}
@Override
public int createFramebuffer() {
final int framebuffer = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
return framebuffer;
}
@Override
public int createTexture(int target) {
final int texture = RENDER_BACKEND.genTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return texture;
}
@Override
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
final int previous = RENDER_BACKEND.getInteger(GL11.GL_TEXTURE_BINDING_1D);
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texStorage1D(target, levels, internalFormat, width);
RENDER_BACKEND.bindTexture(target, previous);
}
@Override
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
final int previous = (target == GL11.GL_TEXTURE_2D) ? GLStateManager.getBoundTextureForServerState() : RENDER_BACKEND.getInteger(GL31.GL_TEXTURE_BINDING_RECTANGLE);
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texStorage2D(target, levels, internalFormat, width, height);
RENDER_BACKEND.bindTexture(target, previous);
}
@Override
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
final int previous = RENDER_BACKEND.getInteger(GL12.GL_TEXTURE_BINDING_3D);
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texStorage3D(target, levels, internalFormat, width, height, depth);
RENDER_BACKEND.bindTexture(target, previous);
}
}
@@ -1,119 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
public final class FFPUniformBlock {
public static final String BLOCK_NAME = "FFPUniformBlock";
public static final int BINDING_POINT = 0;
private static final StringBuilder DECL = new StringBuilder(2048);
private static int cursor = 0;
static final Object2IntMap<String> MEMBER_OFFSETS = new Object2IntLinkedOpenHashMap<>();
private static int align(int alignment) {
cursor = (cursor + alignment - 1) & -alignment;
return cursor;
}
private static int member(String type, String name, int alignment, int size) {
DECL.append(" ").append(type).append(' ').append(name).append(";\n");
final int offset = align(alignment);
cursor += size;
MEMBER_OFFSETS.put(name, offset);
return offset;
}
private static int mat4(String name) { return member("mat4", name, 16, 64); }
private static int mat3(String name) { return member("mat3", name, 16, 48); }
private static int vec4(String name) { return member("vec4", name, 16, 16); }
private static int vec3(String name) { return member("vec3", name, 16, 12); }
private static int vec2(String name) { return member("vec2", name, 8, 8); }
private static int flt(String name) { return member("float", name, 4, 4); }
private static int integer(String name) { return member("int", name, 4, 4); }
private static int vec4Array(String name, int count) {
DECL.append(" vec4 ").append(name).append('[').append(count).append("];\n");
final int offset = align(16);
cursor += 16 * count;
MEMBER_OFFSETS.put(name + "[0]", offset);
return offset;
}
static { DECL.append("layout(std140) uniform ").append(BLOCK_NAME).append(" {\n"); }
public static final int MODEL_VIEW_MATRIX = mat4("u_ModelViewMatrix");
public static final int PROJECTION_MATRIX = mat4("u_ProjectionMatrix");
public static final int MVP_MATRIX = mat4("u_MVPMatrix");
public static final int NORMAL_MATRIX = mat3("u_NormalMatrix");
public static final int TEXTURE_MATRIX_0 = mat4("u_TextureMatrix0");
public static final int TEXTURE_MATRIX_2 = mat4("u_TextureMatrix2");
public static final int TEXTURE_MATRIX_3 = mat4("u_TextureMatrix3");
public static final int LIGHTMAP_TEXTURE_MATRIX = mat4("u_LightmapTextureMatrix");
public static final int CURRENT_COLOR = vec4("u_CurrentColor");
public static final int CURRENT_TEX_COORD_0 = vec4("u_CurrentTexCoord0");
public static final int CURRENT_TEX_COORD_2 = vec4("u_CurrentTexCoord2");
public static final int CURRENT_TEX_COORD_3 = vec4("u_CurrentTexCoord3");
public static final int VIEWPORT = vec4("u_Viewport");
public static final int CURRENT_NORMAL = vec3("u_CurrentNormal");
public static final int CURRENT_LIGHTMAP_COORD = vec2("u_CurrentLightmapCoord");
public static final int VIEWPORT_SIZE = vec2("u_ViewportSize");
public static final int NORMAL_SCALE = flt("u_NormalScale");
public static final int MATERIAL_SHININESS = flt("u_MaterialShininess");
public static final int ALPHA_REF = flt("u_AlphaRef");
public static final int LINE_WIDTH = flt("u_LineWidth");
public static final int LINE_STIPPLE = integer("u_LineStipple");
public static final int TEX_GEN_OBJ_PLANE_S = vec4("u_TexGenObjPlaneS");
public static final int TEX_GEN_OBJ_PLANE_T = vec4("u_TexGenObjPlaneT");
public static final int TEX_GEN_OBJ_PLANE_R = vec4("u_TexGenObjPlaneR");
public static final int TEX_GEN_OBJ_PLANE_Q = vec4("u_TexGenObjPlaneQ");
public static final int TEX_GEN_EYE_PLANE_S = vec4("u_TexGenEyePlaneS");
public static final int TEX_GEN_EYE_PLANE_T = vec4("u_TexGenEyePlaneT");
public static final int TEX_GEN_EYE_PLANE_R = vec4("u_TexGenEyePlaneR");
public static final int TEX_GEN_EYE_PLANE_Q = vec4("u_TexGenEyePlaneQ");
public static final int CLIP_PLANES = vec4Array("u_ClipPlane", 8);
public static final int LIGHT_MODEL_AMBIENT = vec4("u_LightModelAmbient");
public static final int MATERIAL_EMISSION = vec4("u_MaterialEmission");
public static final int MATERIAL_AMBIENT = vec4("u_MaterialAmbient");
public static final int MATERIAL_DIFFUSE = vec4("u_MaterialDiffuse");
public static final int MATERIAL_SPECULAR = vec4("u_MaterialSpecular");
public static final int LIGHT0_AMBIENT = vec4("u_Light0Ambient");
public static final int LIGHT0_DIFFUSE = vec4("u_Light0Diffuse");
public static final int LIGHT0_SPECULAR = vec4("u_Light0Specular");
public static final int LIGHT0_POSITION = vec4("u_Light0Position");
public static final int LIGHT1_AMBIENT = vec4("u_Light1Ambient");
public static final int LIGHT1_DIFFUSE = vec4("u_Light1Diffuse");
public static final int LIGHT1_SPECULAR = vec4("u_Light1Specular");
public static final int LIGHT1_POSITION = vec4("u_Light1Position");
public static final int SCENE_COLOR = vec4("u_SceneColor");
public static final int LIGHT_PROD0_AMBIENT = vec3("u_LightProd0Ambient");
public static final int LIGHT_PROD0_DIFFUSE = vec3("u_LightProd0Diffuse");
public static final int LIGHT_PROD0_SPECULAR = vec3("u_LightProd0Specular");
public static final int LIGHT_PROD1_AMBIENT = vec3("u_LightProd1Ambient");
public static final int LIGHT_PROD1_DIFFUSE = vec3("u_LightProd1Diffuse");
public static final int LIGHT_PROD1_SPECULAR = vec3("u_LightProd1Specular");
public static final int TEX_ENV_COLOR_0 = vec4("u_TexEnvColor0");
public static final int TEX_ENV_COLOR_1 = vec4("u_TexEnvColor1");
public static final int TEX_ENV_COLOR_2 = vec4("u_TexEnvColor2");
public static final int TEX_ENV_COLOR_3 = vec4("u_TexEnvColor3");
public static final int OVERLAY_COLOR = vec4("u_OverlayColor");
public static final int FOG_PARAMS = vec4("u_FogParams");
public static final int FOG_COLOR = vec4("u_FogColor");
public static final int SIZE;
public static final String GLSL_DECL;
static {
SIZE = (cursor + 15) & -16;
DECL.append("};\n");
GLSL_DECL = DECL.toString();
}
private FFPUniformBlock() {}
}
@@ -1,400 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import java.util.Arrays;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
/**
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (12) + unit 0 (47 bits at offset 12)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
* Per-unit packing (47 bits):
* 0: enabled
* 1-3: mode (MODULATE=0..COMBINE=5)
* 4: needsEnvColor
* 5-8: combineRgb (encoded 0-7)
* 9-12: combineAlpha (encoded 0-7)
* 13-14: scaleShiftRgb
* 15-16: scaleShiftAlpha
* 17-21: arg0 RGB (source:3 + operand:2)
* 22-26: arg1 RGB
* 27-31: arg2 RGB
* 32-36: arg0 Alpha
* 37-41: arg1 Alpha
* 42-46: arg2 Alpha
*/
public final class FragmentKey {
public static final int TEX_ENV_MODULATE = 0;
public static final int TEX_ENV_REPLACE = 1;
public static final int TEX_ENV_ADD = 2;
public static final int TEX_ENV_DECAL = 3;
public static final int TEX_ENV_BLEND = 4;
public static final int TEX_ENV_COMBINE = 5;
public static final int COMBINE_REPLACE = 0;
public static final int COMBINE_MODULATE = 1;
public static final int COMBINE_ADD = 2;
public static final int COMBINE_ADD_SIGNED = 3;
public static final int COMBINE_INTERPOLATE = 4;
public static final int COMBINE_SUBTRACT = 5;
public static final int COMBINE_DOT3_RGB = 6;
public static final int COMBINE_DOT3_RGBA = 7;
public static final int SRC_TEXTURE = 0;
public static final int SRC_CONSTANT = 1;
public static final int SRC_PRIMARY_COLOR = 2;
public static final int SRC_PREVIOUS = 3;
public static final int OP_SRC_COLOR = 0;
public static final int OP_ONE_MINUS_SRC_COLOR = 1;
public static final int OP_SRC_ALPHA = 2;
public static final int OP_ONE_MINUS_SRC_ALPHA = 3;
public static final int FOG_NONE = 0;
public static final int FOG_LINEAR = 1;
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 12;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int BIT_LINE_STIPPLE = 11; // 1 bit
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
private static final int U_NEEDS_ENV_COLOR = 4; // 1 bit
private static final int U_COMBINE_RGB = 5; // 4 bits
private static final int U_COMBINE_ALPHA = 9; // 4 bits
private static final int U_SCALE_RGB = 13; // 2 bits
private static final int U_SCALE_ALPHA = 15; // 2 bits
private static final int U_ARG0_RGB = 17; // 5 bits (source:3 + operand:2)
private static final int U_ARG1_RGB = 22; // 5 bits
private static final int U_ARG2_RGB = 27; // 5 bits
private static final int U_ARG0_ALPHA = 32; // 5 bits
private static final int U_ARG1_ALPHA = 37; // 5 bits
private static final int U_ARG2_ALPHA = 42; // 5 bits
static final int MAX_UNITS = 4;
private final long[] packed;
private final int hash;
private FragmentKey(long[] packed, int hash) {
this.packed = packed;
this.hash = hash;
}
/**
* Pack current GLSM fragment state into pre-allocated output array.
* @param out must be at least MAX_UNITS (4) longs
* @return number of significant longs (1..4)
*/
public static int packFromState(long[] out) {
long global = 0;
// Fog
if (GLStateManager.getFogMode().isEnabled()) {
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
case GL11.GL_LINEAR -> FOG_LINEAR;
case GL11.GL_EXP -> FOG_EXP;
case GL11.GL_EXP2 -> FOG_EXP2;
default -> FOG_NONE;
};
global |= (long) fogMode;
}
// Alpha test
if (GLStateManager.getAlphaTest().isEnabled()) {
global |= (1L << BIT_ALPHA_TEST);
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
}
// Damage overlay
if (GLStateManager.getOverlayA() != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
if (GLStateManager.lineStippleActive) {
global |= (1L << BIT_LINE_STIPPLE);
}
// Separate specular
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
global |= (1L << BIT_SEPARATE_SPECULAR);
}
// Per-unit state
int highestEnabled = -1;
long unit0Bits = 0;
for (int i = 0; i < MAX_UNITS; i++) {
// Per Mesa - an enabled unit with no complete texture bound counts as disabled.
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled() && GLStateManager.getTextures().getTextureUnitBindings(i).getBinding() != 0;
if (texEnabled) highestEnabled = i;
final long unitBits = packUnit(i, texEnabled);
if (i == 0) {
unit0Bits = unitBits;
} else {
out[i] = unitBits;
}
}
final int nrEnabled = highestEnabled + 1;
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
global |= (unit0Bits << GLOBAL_BITS);
out[0] = global;
return Math.max(1, nrEnabled);
}
private static long packUnit(int unitIndex, boolean texEnabled) {
if (!texEnabled) return 0;
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
final int mode = encodeTexEnvMode(envState.mode);
long bits = 1L; // enabled
bits |= ((long) (mode & 0x7)) << U_MODE;
boolean needsEnvColor = (mode == TEX_ENV_BLEND);
if (mode == TEX_ENV_COMBINE) {
final int combRgb = encodeCombineFunc(envState.combineRgb);
final int combAlpha = encodeCombineFunc(envState.combineAlpha);
bits |= ((long) (combRgb & 0xF)) << U_COMBINE_RGB;
bits |= ((long) (combAlpha & 0xF)) << U_COMBINE_ALPHA;
bits |= ((long) (encodeScale(envState.scaleRgb) & 0x3)) << U_SCALE_RGB;
bits |= ((long) (encodeScale(envState.scaleAlpha) & 0x3)) << U_SCALE_ALPHA;
// Only pack args actually used by each channel's combine function.
// Unused arg slots stay 0, preventing TexEnvState defaults from inflating the cache.
final int numArgsRgb = combineNumArgs(combRgb);
final int numArgsAlpha = combineNumArgs(combAlpha);
final int maxArgs = Math.max(numArgsRgb, numArgsAlpha);
for (int a = 0; a < maxArgs; a++) {
if (a < numArgsRgb) {
final int srcR = encodeSource(envState.sourceRgb[a]);
final int opR = encodeOperand(envState.operandRgb[a]);
bits |= ((long) ((srcR & 0x7) | ((opR & 0x3) << 3))) << (U_ARG0_RGB + a * 5);
if (srcR == SRC_CONSTANT) needsEnvColor = true;
}
if (a < numArgsAlpha) {
final int srcA = encodeSource(envState.sourceAlpha[a]);
final int opA = encodeOperand(envState.operandAlpha[a]);
bits |= ((long) ((srcA & 0x7) | ((opA & 0x3) << 3))) << (U_ARG0_ALPHA + a * 5);
if (srcA == SRC_CONSTANT) needsEnvColor = true;
}
}
}
if (needsEnvColor) {
bits |= (1L << U_NEEDS_ENV_COLOR);
}
return bits;
}
/** Create FragmentKey from packed scratch data. Only called on cache miss. */
public static FragmentKey fromPacked(long[] scratch, int len) {
final long[] copy = Arrays.copyOf(scratch, len);
return new FragmentKey(copy, Arrays.hashCode(copy));
}
/** Convenience: pack + create. Allocates. For infrequent paths. */
public static FragmentKey fromState() {
final long[] scratch = new long[MAX_UNITS];
final int len = packFromState(scratch);
return fromPacked(scratch, len);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof FragmentKey other)) return false;
if (hash != other.hash) return false;
return Arrays.equals(packed, other.packed);
}
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
}
public boolean unitEnabled(int i) { return (unitBits(i) & 1) != 0; }
public int unitMode(int i) { return (int) ((unitBits(i) >> U_MODE) & 0x7); }
public boolean unitNeedsEnvColor(int i) { return ((unitBits(i) >> U_NEEDS_ENV_COLOR) & 1) != 0; }
public int unitCombineRgb(int i) { return (int) ((unitBits(i) >> U_COMBINE_RGB) & 0xF); }
public int unitCombineAlpha(int i) { return (int) ((unitBits(i) >> U_COMBINE_ALPHA) & 0xF); }
public int unitScaleShiftRgb(int i) { return (int) ((unitBits(i) >> U_SCALE_RGB) & 0x3); }
public int unitScaleShiftAlpha(int i) { return (int) ((unitBits(i) >> U_SCALE_ALPHA) & 0x3); }
public int unitSourceRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5)) & 0x7); }
public int unitOperandRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5 + 3)) & 0x3); }
public int unitSourceAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5)) & 0x7); }
public int unitOperandAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5 + 3)) & 0x3); }
public boolean textureEnabled() {
for (int i = 0; i < nrEnabledUnits(); i++) {
if (unitEnabled(i)) return true;
}
return false;
}
/** Per-unit enable bits as a 4-bit mask (bit i = unit i enabled). */
public int enabledUnitMask() {
int mask = 0;
final int n = Math.min(nrEnabledUnits(), MAX_UNITS);
for (int i = 0; i < n; i++) {
if (unitEnabled(i)) mask |= (1 << i);
}
return mask;
}
/** Per-unit enable bits from a packed scratch as a 4-bit mask (bit i = unit i enabled). */
public static int unitMaskFromPacked(long[] packed, int len) {
int mask = 0;
if (len < 1) return 0;
if (((packed[0] >>> GLOBAL_BITS) & 1L) != 0) mask |= 1;
final int n = Math.min(len, MAX_UNITS);
for (int i = 1; i < n; i++) {
if ((packed[i] & 1L) != 0) mask |= (1 << i);
}
return mask;
}
public boolean lightmapEnabled() {
return nrEnabledUnits() > 1 && unitEnabled(1);
}
private static int encodeAlphaFunc(int glFunc) {
return glFunc & 0x7;
}
static int encodeTexEnvMode(int glMode) {
return switch (glMode) {
case GL11.GL_MODULATE -> TEX_ENV_MODULATE;
case GL11.GL_REPLACE -> TEX_ENV_REPLACE;
case GL11.GL_ADD -> TEX_ENV_ADD;
case GL11.GL_DECAL -> TEX_ENV_DECAL;
case GL11.GL_BLEND -> TEX_ENV_BLEND;
case GL13.GL_COMBINE -> TEX_ENV_COMBINE;
default -> TEX_ENV_MODULATE;
};
}
static int encodeCombineFunc(int glFunc) {
return switch (glFunc) {
case GL11.GL_REPLACE -> COMBINE_REPLACE;
case GL11.GL_MODULATE -> COMBINE_MODULATE;
case GL11.GL_ADD -> COMBINE_ADD;
case GL13.GL_ADD_SIGNED -> COMBINE_ADD_SIGNED;
case GL13.GL_INTERPOLATE -> COMBINE_INTERPOLATE;
case GL13.GL_SUBTRACT -> COMBINE_SUBTRACT;
case GL13.GL_DOT3_RGB -> COMBINE_DOT3_RGB;
case GL13.GL_DOT3_RGBA -> COMBINE_DOT3_RGBA;
default -> COMBINE_REPLACE;
};
}
static int encodeSource(int glSource) {
return switch (glSource) {
case GL11.GL_TEXTURE -> SRC_TEXTURE;
case GL13.GL_CONSTANT -> SRC_CONSTANT;
case GL13.GL_PRIMARY_COLOR -> SRC_PRIMARY_COLOR;
case GL13.GL_PREVIOUS -> SRC_PREVIOUS;
default -> {
GLStateManager.LOGGER.debug("Unknown TexEnv source 0x{}, falling back to SRC_PREVIOUS", Integer.toHexString(glSource));
yield SRC_PREVIOUS;
}
};
}
static int encodeOperand(int glOperand) {
return switch (glOperand) {
case GL11.GL_SRC_COLOR -> OP_SRC_COLOR;
case GL11.GL_ONE_MINUS_SRC_COLOR -> OP_ONE_MINUS_SRC_COLOR;
case GL11.GL_SRC_ALPHA -> OP_SRC_ALPHA;
case GL11.GL_ONE_MINUS_SRC_ALPHA -> OP_ONE_MINUS_SRC_ALPHA;
default -> OP_SRC_COLOR;
};
}
/** Number of source/operand args used by the given (encoded) combine function. */
public static int combineNumArgs(int func) {
return switch (func) {
case COMBINE_REPLACE -> 1;
case COMBINE_INTERPOLATE -> 3;
default -> 2; // MODULATE, ADD, ADD_SIGNED, SUBTRACT, DOT3_*
};
}
private static int encodeScale(float scale) {
if (scale >= 4.0f) return 2;
if (scale >= 2.0f) return 1;
return 0;
}
public static int decodeAlphaFunc(int encoded) {
return 0x0200 | encoded;
}
@Override
public String toString() {
final String fogName = switch (fogMode()) {
case FOG_NONE -> "NONE";
case FOG_LINEAR -> "LINEAR";
case FOG_EXP -> "EXP";
case FOG_EXP2 -> "EXP2";
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b overlay=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), overlayEnabled(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
} else {
final String modeName = switch (unitMode(i)) {
case TEX_ENV_MODULATE -> "MOD";
case TEX_ENV_REPLACE -> "REPL";
case TEX_ENV_ADD -> "ADD";
case TEX_ENV_DECAL -> "DEC";
case TEX_ENV_BLEND -> "BL";
case TEX_ENV_COMBINE -> "COMB";
default -> "?";
};
sb.append(String.format(" u%d=%s", i, modeName));
}
}
sb.append(']');
return sb.toString();
}
}
@@ -1,333 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.lwjgl.opengl.GL11;
/**
* Generates GLSL 330 core fragment shaders for FFP emulation.
* Supports per-unit texenv chain including GL_COMBINE mode.
* Follows Mesa's ff_fragment_shader.c and st_nir_lower_fog.c.
*/
public final class FragmentShaderGenerator {
private FragmentShaderGenerator() {}
public static String generate(FragmentKey key) {
final StringBuilder sb = new StringBuilder(2048);
sb.append("#version 330 core\n\n");
emitInputs(sb, key);
emitUniforms(sb, key);
sb.append("out vec4 fragColor;\n\n");
sb.append("void main() {\n");
emitLineStipple(sb, key);
emitTextureSampling(sb, key);
emitTexEnvChain(sb, key);
emitSpecularAdd(sb, key);
emitAlphaTest(sb, key);
emitFog(sb, key);
sb.append(" fragColor = color;\n");
sb.append("}\n");
return sb.toString();
}
private static void emitInputs(StringBuilder sb, FragmentKey key) {
sb.append("// Inputs from vertex shader\n");
sb.append("in vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor;\n");
}
// Per-unit texcoord inputs. Each enabled unit sources from its own varying
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("in vec4 v_TexCoord").append(i).append(";\n");
}
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("in float v_FogCoord;\n");
}
if (key.lineStipple()) {
sb.append("flat in vec2 v_LineStart;\n");
}
sb.append('\n');
}
private static void emitLineStipple(StringBuilder sb, FragmentKey key) {
if (!key.lineStipple()) return;
sb.append(" vec2 stippleDelta = abs(gl_FragCoord.xy - v_LineStart);\n");
sb.append(" int stippleBit = int(floor(max(stippleDelta.x, stippleDelta.y) / float(u_LineStipple >> 16))) & 15;\n");
sb.append(" if (((u_LineStipple >> stippleBit) & 1) == 0) discard;\n\n");
}
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
}
}
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
}
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
sb.append(" vec4 tex").append(i).append("Color = textureProj(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
}
}
private static void emitTexEnvChain(StringBuilder sb, FragmentKey key) {
if (!key.textureEnabled()) {
sb.append(" // No texture -- vertex color only\n");
sb.append(" vec4 color = v_Color;\n");
emitOverlay(sb, key);
return;
}
// Process units in order; "previous" starts as vertex color.
boolean firstUnit = true;
boolean overlayInjected = false;
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
if (!firstUnit && !overlayInjected) {
emitOverlay(sb, key);
overlayInjected = true;
}
final String texVar = "tex" + i + "Color";
final String envColorVar = "u_TexEnvColor" + i;
final String prevVar = firstUnit ? "v_Color" : "color";
final String assign = firstUnit ? " vec4 color = " : " color = ";
if (key.unitMode(i) == FragmentKey.TEX_ENV_COMBINE) {
emitCombineUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
} else {
emitSimpleUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
}
firstUnit = false;
}
if (!overlayInjected) emitOverlay(sb, key);
}
/**
* Emit the modern-style damage overlay mix.
*/
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
if (!key.overlayEnabled()) return;
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
switch (key.unitMode(unit)) {
case FragmentKey.TEX_ENV_REPLACE -> {
sb.append(" // Unit ").append(unit).append(" (REPLACE)\n");
sb.append(assign).append(texVar).append(";\n");
}
case FragmentKey.TEX_ENV_ADD -> {
sb.append(" // Unit ").append(unit).append(" (ADD)\n");
sb.append(assign).append("vec4(clamp(").append(prevVar).append(".rgb + ").append(texVar).append(".rgb, 0.0, 1.0), ")
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
}
case FragmentKey.TEX_ENV_DECAL -> {
sb.append(" // Unit ").append(unit).append(" (DECAL)\n");
sb.append(assign).append("vec4(mix(").append(prevVar).append(".rgb, ").append(texVar).append(".rgb, ").append(texVar).append(".a), ")
.append(prevVar).append(".a);\n");
}
case FragmentKey.TEX_ENV_BLEND -> {
sb.append(" // Unit ").append(unit).append(" (BLEND)\n");
sb.append(assign).append("vec4(").append(prevVar).append(".rgb * (vec3(1.0) - ").append(texVar).append(".rgb) + ")
.append(envColorVar).append(".rgb * ").append(texVar).append(".rgb, ")
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
}
default -> { // MODULATE
sb.append(" // Unit ").append(unit).append(" (MODULATE)\n");
sb.append(assign).append(prevVar).append(" * ").append(texVar).append(";\n");
}
}
}
/** Emit a GL_COMBINE texenv unit */
private static void emitCombineUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
sb.append(" // Unit ").append(unit).append(" (COMBINE)\n");
final String rgbResult = "combRgb" + unit;
final String alphaResult = "combAlpha" + unit;
if (key.unitCombineRgb(unit) == FragmentKey.COMBINE_DOT3_RGBA) {
sb.append(" vec3 ").append(rgbResult).append(";\n");
sb.append(" {\n");
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
sb.append(" }\n");
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(rgbResult).append(".x);\n");
} else {
sb.append(" vec3 ").append(rgbResult).append(";\n");
sb.append(" float ").append(alphaResult).append(";\n");
sb.append(" {\n");
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, false);
sb.append(" }\n");
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(alphaResult).append(");\n");
}
}
/**
* Emit one channel (RGB or Alpha) of a GL_COMBINE unit.
*/
private static void emitCombineChannel(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, boolean isRgb) {
final String suffix = isRgb ? "Rgb" : "Alpha";
final String type = isRgb ? "vec3" : "float";
final int combineFunc = isRgb ? key.unitCombineRgb(unit) : key.unitCombineAlpha(unit);
final int scaleShift = isRgb ? key.unitScaleShiftRgb(unit) : key.unitScaleShiftAlpha(unit);
// Guard: DOT3 is invalid for alpha channel — fallback to REPLACE
final int effectiveFunc;
if (!isRgb && (combineFunc == FragmentKey.COMBINE_DOT3_RGB || combineFunc == FragmentKey.COMBINE_DOT3_RGBA)) {
effectiveFunc = FragmentKey.COMBINE_REPLACE;
} else {
effectiveFunc = combineFunc;
}
// Determine how many args we need
final int numArgs = combineNumArgs(effectiveFunc);
// Emit argument resolution
for (int a = 0; a < numArgs; a++) {
final int source = isRgb ? key.unitSourceRgb(unit, a) : key.unitSourceAlpha(unit, a);
final int operand = isRgb ? key.unitOperandRgb(unit, a) : key.unitOperandAlpha(unit, a);
final String argName = "arg" + suffix + a + "_" + unit;
final String sourceExpr = resolveSource(source, texVar, envColorVar, prevVar);
final String operandExpr = applyOperand(sourceExpr, operand, isRgb);
sb.append(" ").append(type).append(" ").append(argName).append(" = ").append(operandExpr).append(";\n");
}
// Emit combine operation
final String resultName = "comb" + suffix + unit;
final String a0 = "arg" + suffix + "0_" + unit;
final String a1 = "arg" + suffix + "1_" + unit;
final String a2 = "arg" + suffix + "2_" + unit;
sb.append(" ").append(resultName).append(" = ");
switch (effectiveFunc) {
case FragmentKey.COMBINE_REPLACE -> sb.append(a0);
case FragmentKey.COMBINE_MODULATE -> sb.append(a0).append(" * ").append(a1);
case FragmentKey.COMBINE_ADD -> sb.append(a0).append(" + ").append(a1);
case FragmentKey.COMBINE_ADD_SIGNED ->
sb.append(a0).append(" + ").append(a1).append(" - ").append(isRgb ? "vec3(0.5)" : "0.5");
case FragmentKey.COMBINE_SUBTRACT -> sb.append(a0).append(" - ").append(a1);
case FragmentKey.COMBINE_INTERPOLATE ->
sb.append("mix(").append(a1).append(", ").append(a0).append(", ").append(a2).append(")");
case FragmentKey.COMBINE_DOT3_RGB, FragmentKey.COMBINE_DOT3_RGBA ->
sb.append("vec3(4.0 * dot(").append(a0).append(" - vec3(0.5), ").append(a1).append(" - vec3(0.5)))");
default -> sb.append(a0); // fallback to REPLACE
}
sb.append(";\n");
// Apply scale
if (scaleShift > 0) {
final String scaleFactor = scaleShift == 1 ? "2.0" : "4.0";
sb.append(" ").append(resultName).append(" *= ").append(scaleFactor).append(";\n");
}
// Clamp
sb.append(" ").append(resultName).append(" = clamp(").append(resultName).append(", ")
.append(isRgb ? "vec3(0.0), vec3(1.0)" : "0.0, 1.0").append(");\n");
}
private static String resolveSource(int source, String texVar, String envColorVar, String prevVar) {
return switch (source) {
case FragmentKey.SRC_TEXTURE -> texVar;
case FragmentKey.SRC_CONSTANT -> envColorVar;
case FragmentKey.SRC_PRIMARY_COLOR -> "v_Color";
case FragmentKey.SRC_PREVIOUS -> prevVar;
default -> prevVar;
};
}
private static String applyOperand(String sourceExpr, int operand, boolean isRgb) {
if (isRgb) {
return switch (operand) {
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".rgb";
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(vec3(1.0) - " + sourceExpr + ".rgb)";
case FragmentKey.OP_SRC_ALPHA -> "vec3(" + sourceExpr + ".a)";
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "vec3(1.0 - " + sourceExpr + ".a)";
default -> sourceExpr + ".rgb";
};
} else {
return switch (operand) {
case FragmentKey.OP_SRC_ALPHA -> sourceExpr + ".a";
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "(1.0 - " + sourceExpr + ".a)";
// Per GL spec, alpha channel operands are SRC_ALPHA or ONE_MINUS_SRC_ALPHA only, but handle color operands gracefully
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".a";
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(1.0 - " + sourceExpr + ".a)";
default -> sourceExpr + ".a";
};
}
}
private static int combineNumArgs(int func) {
return FragmentKey.combineNumArgs(func);
}
private static void emitSpecularAdd(StringBuilder sb, FragmentKey key) {
if (key.separateSpecular()) {
sb.append(" // Add separate specular\n");
sb.append(" color.rgb += v_SpecularColor;\n");
}
}
private static void emitAlphaTest(StringBuilder sb, FragmentKey key) {
if (!key.alphaTestEnabled()) return;
sb.append(" // Alpha test\n");
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
sb.append(" float qa = round(color.a * 255.0);\n");
switch (func) {
case GL11.GL_NEVER ->
sb.append(" discard;\n");
case GL11.GL_LESS ->
sb.append(" if (qa >= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_EQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa != qr) discard;\n");
}
case GL11.GL_LEQUAL ->
sb.append(" if (qa > u_AlphaRef * 255.0) discard;\n");
case GL11.GL_GREATER ->
sb.append(" if (qa <= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_NOTEQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa == qr) discard;\n");
}
case GL11.GL_GEQUAL ->
sb.append(" if (qa < u_AlphaRef * 255.0) discard;\n");
case GL11.GL_ALWAYS -> {}
}
}
private static void emitFog(StringBuilder sb, FragmentKey key) {
final int fogMode = key.fogMode();
if (fogMode == FragmentKey.FOG_NONE) return;
sb.append(" // Fog\n");
sb.append(" float f;\n");
switch (fogMode) {
case FragmentKey.FOG_LINEAR ->
sb.append(" f = v_FogCoord * u_FogParams.x + u_FogParams.y;\n");
case FragmentKey.FOG_EXP ->
sb.append(" f = exp2(-(v_FogCoord * u_FogParams.z));\n");
case FragmentKey.FOG_EXP2 -> {
sb.append(" float fogTmp = v_FogCoord * u_FogParams.w;\n");
sb.append(" f = exp2(-(fogTmp * fogTmp));\n");
}
}
sb.append(" f = clamp(f, 0.0, 1.0);\n");
sb.append(" color.rgb = mix(u_FogColor.rgb, color.rgb, f);\n");
}
}
@@ -1,107 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* Generates GLSL 330 core geometry shaders for wide line emulation.
*/
public final class GeometryShaderGenerator {
private GeometryShaderGenerator() {}
public static String generate(VertexKey key) {
final StringBuilder sb = new StringBuilder(1024);
sb.append("#version 330 core\n\n");
sb.append("layout(lines) in;\n");
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
sb.append("// Pass-through varyings\n");
sb.append("in vec4 v_Color_gs[];\n");
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor_gs[];\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("in vec4 v_TexCoord0_gs[];\n");
if (key.lightmapEnabled()) sb.append("in vec4 v_TexCoord1_gs[];\n");
if (key.unitTexCoordEnabled(2)) sb.append("in vec4 v_TexCoord2_gs[];\n");
if (key.unitTexCoordEnabled(3)) sb.append("in vec4 v_TexCoord3_gs[];\n");
if (key.fogEnabled()) {
sb.append("in float v_FogCoord_gs[];\n");
}
if (key.lineStipple()) {
sb.append("flat in vec2 v_LineStart_gs[];\n");
}
sb.append('\n');
sb.append("out vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("out vec4 v_TexCoord0;\n");
if (key.lightmapEnabled()) sb.append("out vec4 v_TexCoord1;\n");
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
sb.append('\n');
sb.append("void main() {\n");
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
sb.append(" // NDC positions\n");
sb.append(" vec2 n0 = c0.xy / c0.w;\n");
sb.append(" vec2 n1 = c1.xy / c1.w;\n\n");
sb.append(" // Screen-space direction and perpendicular\n");
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
sb.append(" vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;\n\n");
emitVertex(sb, key, 0, "+offset");
emitVertex(sb, key, 0, "-offset");
emitVertex(sb, key, 1, "+offset");
emitVertex(sb, key, 1, "-offset");
sb.append(" EndPrimitive();\n");
sb.append("}\n");
return sb.toString();
}
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
final String ci = "c" + endpointIdx;
final String ni = "n" + endpointIdx;
final String idx = "[" + endpointIdx + "]";
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
sb.append(" v_Color = v_Color_gs").append(idx).append(";\n");
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = v_TexCoord2_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = v_TexCoord3_gs").append(idx).append(";\n");
if (key.fogEnabled()) {
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
}
if (key.lineStipple()) {
sb.append(" v_LineStart = v_LineStart_gs[0];\n");
}
if (key.clipPlanesEnabled()) {
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = gl_in").append(idx).append(".gl_ClipDistance[").append(i).append("];\n");
}
}
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(ci).append(".w, ").append(ci).append(".z, ").append(ci).append(".w);\n");
sb.append(" EmitVertex();\n\n");
}
}
@@ -1,18 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
public final class InstancedAttribs {
public static final int LOC_MATRIX_COL0 = 7;
public static final int LOC_MATRIX_COL1 = 8;
public static final int LOC_MATRIX_COL2 = 9;
public static final int LOC_MATRIX_COL3 = 10;
public static final int LOC_COLOR = 11;
public static final int LOC_LIGHTMAP = 12;
public static final int OFFSET_MATRIX = 0;
public static final int OFFSET_COLOR = 64;
public static final int OFFSET_LIGHTMAP = 68;
public static final int STRIDE = 80;
private InstancedAttribs() {}
}
@@ -1,168 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CompatShaderTransformer;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import lombok.Getter;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* A compiled and linked FFP emulation program (vertex + fragment + optional geometry shaders). Owns the GL program handle and caches uniform locations.
*/
public class Program {
private static final AtomicInteger PROGRAM_COUNTER = new AtomicInteger();
@Getter private final int programId;
@Getter private final VertexKey vertexKey;
@Getter private final FragmentKey fragmentKey;
public final int[] locSampler = { -1, -1, -1, -1 };
Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
this.programId = programId;
this.vertexKey = vertexKey;
this.fragmentKey = fragmentKey;
for (int i = 0; i < 4; i++) {
locSampler[i] = RENDER_BACKEND.getUniformLocation(programId, "u_Sampler" + i);
}
}
public void destroy() {
GLStateManager.glDeleteProgram(programId);
}
/**
* Compile vertex + fragment + optional geometry shaders, link into a program, and return the FFPProgram.
*/
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc, String geomSrc) {
final RenderBackend backend = RENDER_BACKEND;
final int id = PROGRAM_COUNTER.getAndIncrement();
final String vkHex = Long.toHexString(vk.pack());
final boolean hasGeom = geomSrc != null;
int shaderCount = 0;
final int[] shaders = new int[5]; // vertex, fragment, geometry, tess control, tess evaluation
int program = 0;
try {
shaders[shaderCount++] = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + vkHex);
shaders[shaderCount++] = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
if (geomSrc != null) {
shaders[shaderCount++] = compileShader(GL32.GL_GEOMETRY_SHADER, geomSrc, "ffp_g_" + id);
}
program = backend.createProgram();
for (int i = 0; i < shaderCount; i++) backend.attachShader(program, shaders[i]);
backend.linkProgram(program);
final String log = backend.getProgramInfoLog(program, backend.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
if (!log.isEmpty()) {
GLStateManager.LOGGER.warn("FFP program link log (vk=0x{}, fk={}): {}", vkHex, fk, log);
}
if (backend.getProgrami(program, GL20.GL_LINK_STATUS) != GL11.GL_TRUE) {
throw new RuntimeException("FFP shader link failed (vk=0x" + vkHex + ", fk=" + fk + "): " + log);
}
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
// Detach and delete individual shaders - they're linked into the program
for (int i = 0; i < shaderCount; i++) {
backend.detachShader(program, shaders[i]);
backend.deleteShader(shaders[i]);
}
shaderCount = 0;
final Program ffpProgram = new Program(program, vk, fk);
final int blockIndex = backend.getUniformBlockIndex(program, FFPUniformBlock.BLOCK_NAME);
if (blockIndex != GL31.GL_INVALID_INDEX) {
backend.uniformBlockBinding(program, blockIndex, FFPUniformBlock.BINDING_POINT);
}
final int previousProgram = GLStateManager.getActiveProgram();
backend.useProgram(program);
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) backend.uniform1i(ffpProgram.locSampler[i], i);
}
backend.useProgram(previousProgram);
return ffpProgram;
} catch (RuntimeException e) {
if (program != 0) backend.deleteProgram(program);
for (int i = 0; i < shaderCount; i++) backend.deleteShader(shaders[i]);
throw e;
}
}
private static int compileShader(int type, String src, String name) {
final RenderBackend backend = RENDER_BACKEND;
final int shader = backend.createShader(type);
if (RenderSystem.isGLES()) {
src = CompatShaderTransformer.toGLES(src, type, type == GL20.GL_FRAGMENT_SHADER);
dumpTranslatedFFPShader(type, src, name);
}
backend.shaderSource(shader, src);
backend.compileShader(shader);
final String typeName = switch (type) {
case GL20.GL_VERTEX_SHADER -> "vertex";
case GL20.GL_FRAGMENT_SHADER -> "fragment";
case GL32.GL_GEOMETRY_SHADER -> "geometry";
case GL40.GL_TESS_CONTROL_SHADER -> "tess_control";
case GL40.GL_TESS_EVALUATION_SHADER -> "tess_evaluation";
case GL43.GL_COMPUTE_SHADER -> "compute";
default -> "unknown(0x" + Integer.toHexString(type) + ")";
};
GLDebug.nameObject(KHRDebug.GL_SHADER, shader, name + "(" + typeName + ")");
final String log = backend.getShaderInfoLog(shader, backend.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
if (!log.isEmpty()) {
GLStateManager.LOGGER.warn("FFP {} shader compilation log for {}: {}", typeName, name, log);
}
final int result = backend.getShaderi(shader, GL20.GL_COMPILE_STATUS);
if (result != GL11.GL_TRUE) {
GLStateManager.LOGGER.error("FFP {} shader source:\n{}", typeName, src);
backend.deleteShader(shader);
throw new RuntimeException("FFP " + typeName + " shader compilation failed for " + name + ": " + log);
}
return shader;
}
// Post-translation GLSL ES dump, paired with ShaderCache's pre-translation 330 core dump.
private static void dumpTranslatedFFPShader(int type, String translatedSrc, String name) {
final Path dir = SystemProperties.shaderDumpDir("ffp");
if (dir == null) return;
final String suffix = switch (type) {
case GL20.GL_VERTEX_SHADER -> ".vert.glsl";
case GL20.GL_FRAGMENT_SHADER -> ".frag.glsl";
case GL32.GL_GEOMETRY_SHADER -> ".geom.glsl";
default -> ".glsl";
};
try {
Files.createDirectories(dir);
Files.writeString(dir.resolve(name + ".gles" + suffix), translatedSrc);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump translated FFP shader: {}", e.getMessage());
}
}
}
@@ -1,304 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Setter;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.IdentityHashMap;
/**
* Open-addressed cache for FFP shader program variants.
* Keyed on (vertexKey, fragmentKey) pairs stored inline as raw longs — zero allocation on cache hits.
* Converges on Mesa's prog_cache.c: hash+memcmp over packed bytes, open addressing.
*
* <p>Layout per slot in {@code keys[]}: {@link #SLOT_STRIDE}=6 longs
* <pre>
* [0]: vertexKeyPacked
* [1]: (fkLen &lt;&lt; 48) | (hash &amp; 0x0000_FFFF_FFFF_FFFF) — never 0 for valid entries (fkLen &gt;= 1)
* [2..5]: fk0..fk3
* </pre>
*/
public class ShaderCache {
private static final Tracy.ZoneId Z_FFP_SHADER_GEN = Tracy.zoneId("ffpShaderGen", Tracy.COLOR_FFP);
private static final int SLOT_STRIDE = 6;
private static final int INITIAL_CAPACITY = 64; // power of 2
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
// Open-addressed table — parallel arrays
private long[] keys;
private Program[] programs;
private String[] fragSources; // fragment source per slot; reused across VK variants via findFragSource
private int capacity;
private int count;
private int uniqueFragCount;
// Fast-path: last-used key (raw longs, no objects)
private long lastVK = Long.MIN_VALUE;
private int lastFKLen;
private long lastFK0, lastFK1, lastFK2, lastFK3;
private Program lastProgram;
private int fragmentDumpCounter;
@Setter private Path dumpDir = null;
public ShaderCache() {
capacity = INITIAL_CAPACITY;
keys = new long[capacity * SLOT_STRIDE];
programs = new Program[capacity];
fragSources = new String[capacity];
}
/**
* Get or create an FFP program for the given state keys.
*
* @param vkPacked packed vertex key
* @param fkPacked pre-allocated scratch array with packed fragment key longs
* @param fkLen number of significant longs in fkPacked (1..4)
*/
public Program getOrCreate(long vkPacked, long[] fkPacked, int fkLen) {
// Fast-path: compare raw longs inline
if (vkPacked == lastVK && fkLen == lastFKLen && lastProgram != null
&& fkPacked[0] == lastFK0
&& (fkLen < 2 || fkPacked[1] == lastFK1)
&& (fkLen < 3 || fkPacked[2] == lastFK2)
&& (fkLen < 4 || fkPacked[3] == lastFK3)) {
return lastProgram;
}
final long hash = hashKey(vkPacked, fkPacked, fkLen);
final long storedWord = ((long) fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFFL);
final int mask = capacity - 1;
int idx = (int) (hash & mask);
while (true) {
final int base = idx * SLOT_STRIDE;
final long slot1 = keys[base + 1];
if (slot1 == EMPTY_MARKER) {
break;
}
if (slot1 == storedWord && keys[base] == vkPacked && keysMatch(base + 2, fkPacked, fkLen)) {
final Program p = programs[idx];
setLastUsed(vkPacked, fkPacked, fkLen, p);
return p;
}
idx = (idx + 1) & mask;
}
if (count >= (capacity * 3) / 4) {
grow();
return getOrCreate(vkPacked, fkPacked, fkLen);
}
final VertexKey vk = VertexKey.fromPacked(vkPacked);
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
final String vertSrc;
String fragSrc;
final String geomSrc;
final Program program;
if (Tracy.ENABLED) Tracy.beginZone(Z_FFP_SHADER_GEN);
try {
vertSrc = getOrGenerateVertex(vk);
fragSrc = findFragSource(fkPacked, fkLen);
if (fragSrc == null) {
fragSrc = FragmentShaderGenerator.generate(fk);
uniqueFragCount++;
}
geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
final int insertIdx = findEmptySlot(hash);
final int base = insertIdx * SLOT_STRIDE;
keys[base] = vkPacked;
keys[base + 1] = storedWord;
for (int i = 0; i < fkLen; i++) keys[base + 2 + i] = fkPacked[i];
programs[insertIdx] = program;
fragSources[insertIdx] = fragSrc;
count++;
setLastUsed(vkPacked, fkPacked, fkLen, program);
GLStateManager.LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, count);
if (dumpDir != null) {
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
if (geomSrc != null) {
dumpShader(Long.toHexString(vkPacked), ".geom.glsl", geomSrc);
}
}
return program;
}
private String findFragSource(long[] fkPacked, int fkLen) {
for (int i = 0; i < capacity; i++) {
final int base = i * SLOT_STRIDE;
if (keys[base + 1] == EMPTY_MARKER) continue;
final int storedLen = (int) (keys[base + 1] >>> 48);
if (storedLen != fkLen) continue;
if (keysMatch(base + 2, fkPacked, fkLen)) {
return fragSources[i];
}
}
return null;
}
private boolean keysMatch(int keysOffset, long[] fkPacked, int fkLen) {
for (int i = 0; i < fkLen; i++) {
if (keys[keysOffset + i] != fkPacked[i]) return false;
}
return true;
}
private void setLastUsed(long vkPacked, long[] fkPacked, int fkLen, Program program) {
lastVK = vkPacked;
lastFKLen = fkLen;
lastFK0 = fkPacked[0];
lastFK1 = fkLen >= 2 ? fkPacked[1] : 0;
lastFK2 = fkLen >= 3 ? fkPacked[2] : 0;
lastFK3 = fkLen >= 4 ? fkPacked[3] : 0;
lastProgram = program;
}
private int findEmptySlot(long hash) {
final int mask = capacity - 1;
int idx = (int) (hash & mask);
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
idx = (idx + 1) & mask;
}
return idx;
}
/**
* Hash over (vk + fk longs). Adapted from Mesa's hash_key — mix-rotate-multiply.
*/
private static long hashKey(long vkPacked, long[] fkPacked, int fkLen) {
long h = vkPacked * 0x9E3779B97F4A7C15L;
for (int i = 0; i < fkLen; i++) {
h ^= fkPacked[i] * 0x517CC1B727220A95L;
h = Long.rotateLeft(h, 31) * 0x9E3779B97F4A7C15L;
}
return h | 1L; // ensure nonzero lower bits
}
private void grow() {
final int oldCap = capacity;
final long[] oldKeys = keys;
final Program[] oldPrograms = programs;
final String[] oldFragSources = fragSources;
capacity = oldCap * 2;
keys = new long[capacity * SLOT_STRIDE];
programs = new Program[capacity];
fragSources = new String[capacity];
count = 0;
final int mask = capacity - 1;
for (int i = 0; i < oldCap; i++) {
final int oldBase = i * SLOT_STRIDE;
if (oldKeys[oldBase + 1] == EMPTY_MARKER) continue;
final long storedHash = oldKeys[oldBase + 1] & 0x0000_FFFF_FFFF_FFFFL;
int idx = (int) (storedHash & mask);
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
idx = (idx + 1) & mask;
}
final int newBase = idx * SLOT_STRIDE;
System.arraycopy(oldKeys, oldBase, keys, newBase, SLOT_STRIDE);
programs[idx] = oldPrograms[i];
fragSources[idx] = oldFragSources[i];
count++;
}
}
private String getOrGenerateVertex(VertexKey vk) {
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
}
private String getOrGenerateGeometry(VertexKey vk) {
return geometrySourceCache.computeIfAbsent(vk.pack(), k -> GeometryShaderGenerator.generate(vk));
}
private void dumpShader(String name, String suffix, String source) {
if (dumpDir == null) return;
try {
Files.createDirectories(dumpDir);
final Path file = dumpDir.resolve("ffp_" + name + suffix);
if (!Files.exists(file)) {
Files.writeString(file, source, StandardCharsets.UTF_8);
}
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump FFP shader to {}: {}", dumpDir, e.getMessage());
}
}
/** Dump all cached shaders to disk. */
public void dumpAll(Path outputDir) {
try {
Files.createDirectories(outputDir);
for (var entry : vertexSourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".vert.glsl", entry.getValue());
}
final IdentityHashMap<String, Integer> seen = new IdentityHashMap<>();
for (int i = 0; i < capacity; i++) {
if (fragSources[i] != null && !seen.containsKey(fragSources[i])) {
final int idx = seen.size();
seen.put(fragSources[i], idx);
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
}
}
for (var entry : geometrySourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".geom.glsl", entry.getValue());
}
GLStateManager.LOGGER.info("Dumped {} vertex, {} geometry, and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), geometrySourceCache.size(), seen.size(), outputDir);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
}
}
/** Destroy all cached programs and clear caches. */
public void destroy() {
for (int i = 0; i < capacity; i++) {
if (programs[i] != null) {
programs[i].destroy();
}
}
Arrays.fill(keys, 0);
Arrays.fill(programs, null);
Arrays.fill(fragSources, null);
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
GLStateManager.LOGGER.debug("FFP shader cache cleared");
}
public int getProgramCount() {
return count;
}
public int getVertexVariantCount() {
return vertexSourceCache.size();
}
public int getFragmentVariantCount() {
return uniqueFragCount;
}
}
@@ -1,234 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import lombok.Getter;
import org.joml.Vector3f;
import org.joml.Vector4f;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* FFP shader manager
*/
public final class ShaderManager {
private static final class Holder {
static final ShaderManager INSTANCE = new ShaderManager();
}
private final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
@Getter
private boolean active = false;
private Program currentProgram = null;
private int lastBoundProgramId = -1;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
private int currentFKLen = 0;
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
private static final Vector4f[] currentTexCoords = {
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
};
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0]);
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
private int preDrawCalls;
private int lastFramePreDrawCalls;
public static long variantSwitches;
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
private ShaderManager() {
cache.setDumpDir(SystemProperties.shaderDumpDir("ffp"));
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
VAOManager.setCurrentVertexFlags(format.getVertexFlags());
return false;
});
}
public static ShaderManager getInstance() {
return Holder.INSTANCE;
}
public void enable() {
warmUp();
enabled = true;
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
// Force loading of classes before the SplashThread kicks off
// Ensure it's GL free
private static void warmUp() {
try {
final long[] fkScratch = new long[FragmentKey.MAX_UNITS];
final int fkLen = FragmentKey.packFromState(fkScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(fkScratch, fkLen);
final long vkPacked = VertexKey.packFromState(true, true, true, true, fragMask);
final VertexKey vk = VertexKey.fromPacked(vkPacked);
VertexShaderGenerator.generate(vk);
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
GeometryShaderGenerator.generate(vk);
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
for (Class<?> c : touched) {
c.getName();
}
} catch (Throwable t) {
GLStateManager.LOGGER.warn("FFP warmup failed; draw-path classes will resolve lazily", t);
}
}
public void disable() {
enabled = false;
}
public void activate() {
active = true;
lastBoundProgramId = GLStateManager.getActiveProgram();
}
public void deactivate() {
active = false;
currentProgram = null;
lastBoundProgramId = -1;
currentVertexKeyPacked = Long.MIN_VALUE;
currentFKLen = 0;
GLStateManager.forceAttribDefaultsDirty();
}
public void preDraw() {
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
// Handle FFP & Iris uniforms
final int currentProgramId = GLStateManager.getActiveProgram();
if (currentProgramId != 0) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
return; // Don't emulate FFP on non-iris core shaders
}
}
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
preDrawCalls++;
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
commitVariant(vkPacked, fkLen);
}
uploadUniforms();
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) {
if (Tracy.ENABLED) variantSwitches++;
RENDER_BACKEND.useProgram(programId);
lastBoundProgramId = programId;
}
}
private void uploadUniforms() {
if (currentProgram != null) {
uniforms.upload();
}
}
public static void endFrame() {
final ShaderManager sm = Holder.INSTANCE;
sm.lastFramePreDrawCalls = sm.preDrawCalls;
sm.preDrawCalls = 0;
sm.uniforms.endFrame();
}
public static void setCurrentNormal(float x, float y, float z) {
currentNormal.set(x, y, z);
normalGeneration++;
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
}
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void destroy() {
cache.destroy();
uniforms.destroy();
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
QuadConverter.destroy();
active = false;
currentProgram = null;
}
public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants) | UBO: %d/%d draws wrote (mat %d, light %d, frag %d, misc %d)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount(),
uniforms.lastFrameBlockWrites, lastFramePreDrawCalls,
uniforms.lastFrameStagedMatrices, uniforms.lastFrameStagedLighting,
uniforms.lastFrameStagedFragment, uniforms.lastFrameStagedMisc);
}
public int statLastFramePreDrawCalls() { return lastFramePreDrawCalls; }
public int statLastFrameBlockWrites() { return uniforms.lastFrameBlockWrites; }
public int statLastFrameBlockSkips() { return uniforms.lastFrameBlockSkips; }
public int statLastFrameStagedMatrices() { return uniforms.lastFrameStagedMatrices; }
public int statLastFrameStagedLighting() { return uniforms.lastFrameStagedLighting; }
public int statLastFrameStagedFragment() { return uniforms.lastFrameStagedFragment; }
public int statLastFrameStagedColor() { return uniforms.lastFrameStagedColor; }
public int statLastFrameStagedNormal() { return uniforms.lastFrameStagedNormal; }
public int statLastFrameStagedTexCoord() { return uniforms.lastFrameStagedTexCoord; }
public int statLastFrameStagedLightmap() { return uniforms.lastFrameStagedLightmap; }
public int statLastFrameStagedTexGen() { return uniforms.lastFrameStagedTexGen; }
public int statLastFrameStagedClipPlanes() { return uniforms.lastFrameStagedClipPlanes; }
public int statLastFrameStagedMisc() { return uniforms.lastFrameStagedMisc; }
public int statProgramCount() { return cache.getProgramCount(); }
}
@@ -1,441 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.LineState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Maintains the shared {@link FFPUniformBlock} image from GLSM cached state and streams it to the UBO ring.
*/
public class Uniforms {
private static final int RING_CAPACITY = 16 * 1024 * 1024;
private final ByteBuffer staging = memCalloc(FFPUniformBlock.SIZE);
private final long stagingAddress = memAddress0(staging);
private UniformRingBuffer ring;
private boolean bound;
private boolean contentChanged;
private int mvGen = -1, mvLinearGen = -1, projGen = -1, texMatGen = -1;
private int lightingGen = -1, fragmentGen = -1, colorGen = -1, normalGen = -1, texCoordGen = -1;
private int texGenGen = -1, clipPlaneGen = -1;
private float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
private int lineStipple = -1;
int blockWrites;
int blockSkips;
int stagedMatrices;
int stagedLighting;
int stagedFragment;
int stagedColor;
int stagedNormal;
int stagedTexCoord;
int stagedLightmap;
int stagedTexGen;
int stagedClipPlanes;
int stagedMisc;
int lastFrameBlockWrites;
int lastFrameBlockSkips;
int lastFrameStagedMatrices;
int lastFrameStagedLighting;
int lastFrameStagedFragment;
int lastFrameStagedColor;
int lastFrameStagedNormal;
int lastFrameStagedTexCoord;
int lastFrameStagedLightmap;
int lastFrameStagedTexGen;
int lastFrameStagedClipPlanes;
int lastFrameStagedMisc;
private final Matrix4f mvpMatrix = new Matrix4f();
private final Matrix3f normalMatrix = new Matrix3f();
private final Vector3f tempVec3 = new Vector3f();
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
public void upload() {
boolean dirty = false;
contentChanged = false;
final int mvG = GLStateManager.getMvGeneration();
final int projG = GLStateManager.getProjGeneration();
final boolean mvChanged = mvG != mvGen;
final boolean projChanged = projG != projGen;
if (mvChanged || projChanged) {
stageMatrices(mvChanged, projChanged);
mvGen = mvG;
projGen = projG;
stagedMatrices++;
dirty = true;
}
final int mvLinG = GLStateManager.getMvLinearGeneration();
if (mvLinG != mvLinearGen) {
stageNormalMatrix();
mvLinearGen = mvLinG;
dirty = true;
}
final int texMatG = GLStateManager.getTexMatrixGeneration();
if (texMatG != texMatGen) {
stageTextureMatrices();
texMatGen = texMatG;
dirty = true;
}
final int litG = GLStateManager.getLightingGeneration();
if (litG != lightingGen) {
stageLighting();
lightingGen = litG;
stagedLighting++;
dirty = true;
}
final int colG = GLStateManager.getColorGeneration();
if (colG != colorGen) {
stageCurrentColor();
colorGen = colG;
stagedColor++;
dirty = true;
}
final int normG = ShaderManager.getNormalGeneration();
if (normG != normalGen) {
final Vector3f normal = ShaderManager.getCurrentNormal();
putVec3(FFPUniformBlock.CURRENT_NORMAL, normal.x, normal.y, normal.z);
normalGen = normG;
stagedNormal++;
dirty = true;
}
final int tcG = ShaderManager.getTexCoordGeneration();
if (tcG != texCoordGen) {
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ShaderManager.getCurrentTexCoord(0));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ShaderManager.getCurrentTexCoord(2));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ShaderManager.getCurrentTexCoord(3));
texCoordGen = tcG;
stagedTexCoord++;
dirty = true;
}
final float brightX = GLSMConfig.lastBrightnessX;
final float brightY = GLSMConfig.lastBrightnessY;
if (brightX != lightmapX || brightY != lightmapY) {
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD, brightX);
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD + 4, brightY);
lightmapX = brightX;
lightmapY = brightY;
stagedLightmap++;
dirty = true;
}
final int tgG = GLStateManager.getTexGenGeneration();
if (tgG != texGenGen) {
stageTexGen();
texGenGen = tgG;
stagedTexGen++;
dirty = true;
}
final int cpG = GLStateManager.getClipPlaneGeneration();
if (cpG != clipPlaneGen) {
stageClipPlanes();
clipPlaneGen = cpG;
stagedClipPlanes++;
dirty = true;
}
final int fragG = GLStateManager.getFragmentGeneration();
if (fragG != fragmentGen) {
stageFragment();
fragmentGen = fragG;
stagedFragment++;
dirty = true;
}
final float lw = GLStateManager.getLineState().getWidth();
if (lw != lineWidth) {
putFloat(FFPUniformBlock.LINE_WIDTH, lw);
lineWidth = lw;
stagedMisc++;
dirty = true;
}
final ViewportState vp = GLStateManager.getViewportState();
if (vp.x != viewportX || vp.y != viewportY || vp.width != viewportWidth || vp.height != viewportHeight) {
putFloat(FFPUniformBlock.VIEWPORT_SIZE, vp.width);
putFloat(FFPUniformBlock.VIEWPORT_SIZE + 4, vp.height);
putVec4(FFPUniformBlock.VIEWPORT, vp.x, vp.y, vp.width, vp.height);
viewportX = vp.x;
viewportY = vp.y;
viewportWidth = vp.width;
viewportHeight = vp.height;
stagedMisc++;
dirty = true;
}
final LineState line = GLStateManager.getLineState();
final int stippleFactor = line.getStippleFactor() < 1 ? 1 : line.getStippleFactor();
final int stipplePacked = (line.getStipplePattern() & 0xFFFF) | (stippleFactor << 16);
if (stipplePacked != lineStipple) {
putInt(FFPUniformBlock.LINE_STIPPLE, stipplePacked);
lineStipple = stipplePacked;
stagedMisc++;
dirty = true;
}
if (dirty || !bound) {
if (!contentChanged && bound) {
blockSkips++;
return;
}
if (ring == null) {
ring = new UniformRingBuffer(RING_CAPACITY, FFPUniformBlock.SIZE);
}
final int offset = ring.writeBlock(staging);
RENDER_BACKEND.bindBufferRange(GL31.GL_UNIFORM_BUFFER, FFPUniformBlock.BINDING_POINT,
ring.getBufferId(), offset, FFPUniformBlock.SIZE);
bound = true;
blockWrites++;
}
}
private void stageMatrices(boolean mvChanged, boolean projChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
if (mvChanged) {
putMat4(FFPUniformBlock.MODEL_VIEW_MATRIX, mv);
}
if (projChanged) {
putMat4(FFPUniformBlock.PROJECTION_MATRIX, proj);
}
proj.mul(mv, mvpMatrix);
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
}
private void stageNormalMatrix() {
GLStateManager.getModelViewMatrix().normal(normalMatrix);
putMat3(FFPUniformBlock.NORMAL_MATRIX, normalMatrix);
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
putFloat(FFPUniformBlock.NORMAL_SCALE, scale);
}
private void stageTextureMatrices() {
putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, GLStateManager.getTextures().getTextureUnitMatrix(0));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, GLStateManager.getTextures().getTextureUnitMatrix(2));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, GLStateManager.getTextures().getTextureUnitMatrix(3));
putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, GLStateManager.getTextures().getTextureUnitMatrix(1));
}
private void stageLighting() {
final MaterialState mat = GLStateManager.getFrontMaterial();
final Vector4f lmAmbient = GLStateManager.getLightModel().ambient;
final LightState light0 = GLStateManager.getLightDataStates()[0];
final LightState light1 = GLStateManager.getLightDataStates()[1];
putVec4(FFPUniformBlock.LIGHT_MODEL_AMBIENT, lmAmbient);
putVec4(FFPUniformBlock.MATERIAL_EMISSION, mat.emission);
putVec4(FFPUniformBlock.MATERIAL_AMBIENT, mat.ambient);
putVec4(FFPUniformBlock.MATERIAL_DIFFUSE, mat.diffuse);
putVec4(FFPUniformBlock.MATERIAL_SPECULAR, mat.specular);
putFloat(FFPUniformBlock.MATERIAL_SHININESS, mat.shininess);
putVec4(FFPUniformBlock.LIGHT0_AMBIENT, light0.ambient);
putVec4(FFPUniformBlock.LIGHT0_DIFFUSE, light0.diffuse);
putVec4(FFPUniformBlock.LIGHT0_SPECULAR, light0.specular);
putVec4(FFPUniformBlock.LIGHT0_POSITION, light0.position);
putVec4(FFPUniformBlock.LIGHT1_AMBIENT, light1.ambient);
putVec4(FFPUniformBlock.LIGHT1_DIFFUSE, light1.diffuse);
putVec4(FFPUniformBlock.LIGHT1_SPECULAR, light1.specular);
putVec4(FFPUniformBlock.LIGHT1_POSITION, light1.position);
putVec4(FFPUniformBlock.SCENE_COLOR,
mat.emission.x + mat.ambient.x * lmAmbient.x,
mat.emission.y + mat.ambient.y * lmAmbient.y,
mat.emission.z + mat.ambient.z * lmAmbient.z,
mat.diffuse.w);
putLightProduct(FFPUniformBlock.LIGHT_PROD0_AMBIENT, light0.ambient, mat.ambient);
putLightProduct(FFPUniformBlock.LIGHT_PROD0_DIFFUSE, light0.diffuse, mat.diffuse);
putLightProduct(FFPUniformBlock.LIGHT_PROD0_SPECULAR, light0.specular, mat.specular);
putLightProduct(FFPUniformBlock.LIGHT_PROD1_AMBIENT, light1.ambient, mat.ambient);
putLightProduct(FFPUniformBlock.LIGHT_PROD1_DIFFUSE, light1.diffuse, mat.diffuse);
putLightProduct(FFPUniformBlock.LIGHT_PROD1_SPECULAR, light1.specular, mat.specular);
}
private void putLightProduct(int offset, Vector4f lightVal, Vector4f materialVal) {
putVec3(offset, lightVal.x * materialVal.x, lightVal.y * materialVal.y, lightVal.z * materialVal.z);
}
private void stageCurrentColor() {
final var color = GLStateManager.getColor();
putVec4(FFPUniformBlock.CURRENT_COLOR,
Math.clamp(0f, 1f, color.getRed()),
Math.clamp(0f, 1f, color.getGreen()),
Math.clamp(0f, 1f, color.getBlue()),
Math.clamp(0f, 1f, color.getAlpha()));
}
private void stageTexGen() {
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_S, tg.getObjectPlane(GL11.GL_S));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_T, tg.getObjectPlane(GL11.GL_T));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_R, tg.getObjectPlane(GL11.GL_R));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_Q, tg.getObjectPlane(GL11.GL_Q));
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_S, tg.getEyePlane(GL11.GL_S));
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_T, tg.getEyePlane(GL11.GL_T));
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_R, tg.getEyePlane(GL11.GL_R));
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_Q, tg.getEyePlane(GL11.GL_Q));
}
private void stageClipPlanes() {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
final long src = memAddress0(clipPlaneBuf);
for (int i = 0; i < 32 * 4; i += 4) {
putInt(FFPUniformBlock.CLIP_PLANES + i, memGetInt(src + i));
}
}
private void stageFragment() {
putFloat(FFPUniformBlock.ALPHA_REF, GLStateManager.getAlphaState().getReference());
for (int i = 0; i < 4; i++) {
final var envState = GLStateManager.getTextures().getTexEnvState(i);
putVec4(FFPUniformBlock.TEX_ENV_COLOR_0 + i * 16,
envState.envColorR, envState.envColorG, envState.envColorB, envState.envColorA);
}
putVec4(FFPUniformBlock.OVERLAY_COLOR,
GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
// Mesa STATE_FOG_PARAMS_OPTIMIZED
final FogState fog = GLStateManager.getFogState();
final float start = fog.getStart();
final float end = fog.getEnd();
final float density = fog.getDensity();
final float range = end - start;
putVec4(FFPUniformBlock.FOG_PARAMS,
range != 0.0f ? -1.0f / range : 0.0f,
range != 0.0f ? end / range : 1.0f,
(float) (density / LN2),
(float) (density / SQRT_LN2));
putVec4(FFPUniformBlock.FOG_COLOR,
(float) fog.getFogColor().x, (float) fog.getFogColor().y, (float) fog.getFogColor().z,
fog.getFogAlpha());
}
private void putFloat(int offset, float v) {
final long a = stagingAddress + offset;
final int bits = Float.floatToRawIntBits(v);
if (memGetInt(a) != bits) contentChanged = true;
memPutInt(a, bits);
}
private void putInt(int offset, int v) {
final long a = stagingAddress + offset;
if (memGetInt(a) != v) contentChanged = true;
memPutInt(a, v);
}
private void putMat4(int offset, Matrix4f m) {
putVec4(offset, m.m00(), m.m01(), m.m02(), m.m03());
putVec4(offset + 16, m.m10(), m.m11(), m.m12(), m.m13());
putVec4(offset + 32, m.m20(), m.m21(), m.m22(), m.m23());
putVec4(offset + 48, m.m30(), m.m31(), m.m32(), m.m33());
}
/** std140 mat3: three vec4-aligned columns. */
private void putMat3(int offset, Matrix3f m) {
putVec3(offset, m.m00(), m.m01(), m.m02());
putVec3(offset + 16, m.m10(), m.m11(), m.m12());
putVec3(offset + 32, m.m20(), m.m21(), m.m22());
}
private void putVec4(int offset, Vector4f v) {
putVec4(offset, v.x, v.y, v.z, v.w);
}
private void putVec4(int offset, float x, float y, float z, float w) {
putFloat(offset, x);
putFloat(offset + 4, y);
putFloat(offset + 8, z);
putFloat(offset + 12, w);
}
private void putVec3(int offset, float x, float y, float z) {
putFloat(offset, x);
putFloat(offset + 4, y);
putFloat(offset + 8, z);
}
private void putPlane(int offset, float[] plane) {
putVec4(offset, plane[0], plane[1], plane[2], plane[3]);
}
public void endFrame() {
if (ring != null) {
ring.endFrame();
}
lastFrameBlockWrites = blockWrites;
lastFrameBlockSkips = blockSkips;
lastFrameStagedMatrices = stagedMatrices;
lastFrameStagedLighting = stagedLighting;
lastFrameStagedFragment = stagedFragment;
lastFrameStagedColor = stagedColor;
lastFrameStagedNormal = stagedNormal;
lastFrameStagedTexCoord = stagedTexCoord;
lastFrameStagedLightmap = stagedLightmap;
lastFrameStagedTexGen = stagedTexGen;
lastFrameStagedClipPlanes = stagedClipPlanes;
lastFrameStagedMisc = stagedMisc;
blockWrites = 0;
blockSkips = 0;
stagedMatrices = 0;
stagedLighting = 0;
stagedFragment = 0;
stagedColor = 0;
stagedNormal = 0;
stagedTexCoord = 0;
stagedLightmap = 0;
stagedTexGen = 0;
stagedClipPlanes = 0;
stagedMisc = 0;
}
UniformRingBuffer getRing() {
return ring;
}
ByteBuffer getStaging() {
return staging;
}
public void destroy() {
memFree(staging);
memFree(clipPlaneBuf);
if (ring != null) {
ring.destroy();
ring = null;
}
bound = false;
}
}
@@ -1,358 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class VAOManager {
public static final int MAX_ATTRIBS = 16;
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
private static VAOData current = null;
private static int currentVertexFlags = 0;
private static Attrib[] currentAttribs = null;
public static int boundEBO = 0;
private static int clientSideEnabledCount = 0;
public static void init(int defaultVAO) {
final VAOData data = new VAOData();
current = data;
currentAttribs = data.attribs;
vaoMap.put(defaultVAO, data);
}
public static void onBindEBO(int ebo) {
boundEBO = ebo;
}
public static void onBindVertexArrayPre(int vaoId) {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
if (current.ebo != boundEBO) {
removeEboOwner(current.ebo, current);
current.ebo = boundEBO;
addEboOwner(current.ebo, current);
}
current.attribs = currentAttribs;
current.clientSideEnabledCount = clientSideEnabledCount;
}
VAOData data = vaoMap.get(vaoId);
if (data == null) {
data = new VAOData();
vaoMap.put(vaoId, data);
}
current = data;
currentVertexFlags = data.vertexFlags;
currentAttribs = data.attribs;
boundEBO = data.ebo;
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
final VAOData data = vaoMap.remove(vaoId);
if (data == null) return;
removeEboOwner(data.ebo, data);
if (data == current) current = null;
}
public static void onDeleteBuffer(int buffer) {
if (buffer == 0) return;
VAOData owner = eboOwners.remove(buffer);
while (owner != null) {
final VAOData next = owner.nextEboOwner;
owner.nextEboOwner = null;
owner.ebo = 0;
owner = next;
}
}
private static void addEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
data.nextEboOwner = eboOwners.put(ebo, data);
}
private static void removeEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
VAOData node = eboOwners.get(ebo);
if (node == data) {
final VAOData next = data.nextEboOwner;
if (next == null) eboOwners.remove(ebo); else eboOwners.put(ebo, next);
data.nextEboOwner = null;
return;
}
while (node != null) {
final VAOData next = node.nextEboOwner;
if (next == data) {
node.nextEboOwner = data.nextEboOwner;
data.nextEboOwner = null;
return;
}
node = next;
}
}
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public static int getCurrentVertexFlags() { return currentVertexFlags; }
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
public int vertexFlags;
public int ebo;
public int clientSideEnabledCount;
private VAOData nextEboOwner;
public VAOData() {
attribs = new Attrib[MAX_ATTRIBS];
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
a.genericPointer = true;
if (wasClient && a.enabled) {
clientSideEnabledCount--;
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = 0;
a.clientPointer = pointer;
a.genericPointer = true;
if (!wasClient && a.enabled) {
clientSideEnabledCount++;
}
}
public static boolean isGenericPointerEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public static void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
clientSideEnabledCount++;
}
a.enabled = true;
}
public static void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
clientSideEnabledCount--;
}
a.enabled = false;
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private static Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
currentAttribs[index] = a;
}
return a;
}
public static boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
}
public static int getAttribEffectiveStride(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.effectiveStride() : 0;
}
public static long getAttribOffset(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.offset : 0;
}
public static int getAttribVBO(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.vboId : 0;
}
public static @Nullable Attrib get(int index) {
return currentAttribs[index];
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
/**
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
public static void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
}
if (totalBytes == 0) return;
if (clientArraysVBO == 0) {
clientArraysVBO = RENDER_BACKEND.genBuffers();
}
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, clientArraysVBO);
if (totalBytes > clientArraysVBOCapacity) {
int newCap = Math.max(4096, clientArraysVBOCapacity);
while (newCap < totalBytes) newCap *= 2;
clientArraysVBOCapacity = newCap;
}
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, clientArraysVBOCapacity, GL15.GL_STREAM_DRAW);
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = VAOManager.get(i);
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
public static final class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean genericPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
genericPointer = false;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return GLTypes.sizeBytes(type);
}
public float readComponent(ByteBuffer buf, int base, int component) {
return readComponent(type, normalized, buf, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -1,275 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
/**
* Compact key capturing the vertex shader permutation axes for FFP emulation.
* Packed into a single long for fast hash/compare.
*
* Per-unit fields are 4-bit base-indexed: bit (BASE + i) for unit i in [0..3].
* Unit 1 is the lightmap; texcoord enable for unit 1 also gates the lightmap-specific code paths.
*/
public final class VertexKey {
public static final int MAX_UNITS = 4;
private final long packed;
private static final int BIT_LIGHTING = 0;
private static final int BIT_LIGHT0 = 1;
private static final int BIT_LIGHT1 = 2;
private static final int BIT_LIGHT0_DIR = 3;
private static final int BIT_LIGHT1_DIR = 4;
private static final int BIT_COLOR_MATERIAL = 5;
private static final int BIT_SEPARATE_SPECULAR = 6;
private static final int BIT_FOG = 7;
private static final int BIT_FOG_DIST_MODE = 8; // 2 bits: 8-9
private static final int BIT_NORMALIZE = 10;
private static final int BIT_RESCALE_NORMAL = 11;
// Per-unit texcoord enable: bits 12-15 (unit 0..3). Unit 1 doubles as the lightmap flag.
private static final int BIT_UNIT_TEX_BASE = 12;
private static final int BIT_HAS_VERTEX_COLOR = 16;
private static final int BIT_HAS_VERTEX_NORMAL = 17;
private static final int BIT_HAS_VERTEX_TEX = 18;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 19;
private static final int BIT_COLOR_MAT_MODE = 20; // 3 bits: 20-22
// TexGen modes (unit 0 only, for now): 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR).
private static final int BIT_TEXGEN_S = 23; // 3 bits: 23-25
private static final int BIT_TEXGEN_T = 26; // 3 bits: 26-28
private static final int BIT_TEXGEN_R = 29; // 3 bits: 29-31
private static final int BIT_TEXGEN_Q = 32; // 3 bits: 32-34
private static final int BIT_CLIP_PLANES = 35;
private static final int BIT_WIDE_LINE = 36;
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
private static final int BIT_UNIT_TEXMAT_BASE = 37;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41;
private static final int BIT_LINE_STIPPLE = 42;
private static final int BIT_INSTANCED = 44;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
public static final int TG_EYE_LINEAR = 2;
public static final int CM_AMBIENT = 0;
public static final int CM_DIFFUSE = 1;
public static final int CM_SPECULAR = 2;
public static final int CM_EMISSION = 3;
public static final int CM_AMBIENT_AND_DIFFUSE = 4;
private VertexKey(long packed) {
this.packed = packed;
}
public long pack() { return packed; }
public boolean lightingEnabled() { return bit(BIT_LIGHTING); }
public boolean light0Enabled() { return bit(BIT_LIGHT0); }
public boolean light1Enabled() { return bit(BIT_LIGHT1); }
public boolean light0Directional() { return bit(BIT_LIGHT0_DIR); }
public boolean light1Directional() { return bit(BIT_LIGHT1_DIR); }
public boolean colorMaterialEnabled() { return bit(BIT_COLOR_MATERIAL); }
public boolean separateSpecular() { return bit(BIT_SEPARATE_SPECULAR); }
public boolean fogEnabled() { return bit(BIT_FOG); }
public int fogDistanceMode() { return (int)((packed >> BIT_FOG_DIST_MODE) & 0x3); }
public boolean normalizeEnabled() { return bit(BIT_NORMALIZE); }
public boolean rescaleNormalsEnabled() { return bit(BIT_RESCALE_NORMAL); }
public boolean unitTexCoordEnabled(int unit) { return bit(BIT_UNIT_TEX_BASE + unit); }
public boolean unitTexMatEnabled(int unit) { return bit(BIT_UNIT_TEXMAT_BASE + unit); }
public boolean anyUnitTexCoordEnabled() { return ((packed >> BIT_UNIT_TEX_BASE) & 0xF) != 0; }
public boolean lightmapEnabled() { return unitTexCoordEnabled(1); }
public boolean hasVertexColor() { return bit(BIT_HAS_VERTEX_COLOR); }
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
public boolean hasVertexLightmap() { return bit(BIT_HAS_VERTEX_LIGHTMAP); }
public boolean unit23UvFromUnit0() { return bit(BIT_UNIT23_UV_FROM_UNIT0); }
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). Unit 0 only; see TODO in packFromState. */
public int texGenModeS() { return (int)((packed >> BIT_TEXGEN_S) & 0x7); }
public int texGenModeT() { return (int)((packed >> BIT_TEXGEN_T) & 0x7); }
public int texGenModeR() { return (int)((packed >> BIT_TEXGEN_R) & 0x7); }
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public boolean instancedDraw() { return bit(BIT_INSTANCED); }
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
public boolean cmReplacesSpecular() { return colorMaterialMode() == CM_SPECULAR; }
public boolean cmReplacesEmission() { return colorMaterialMode() == CM_EMISSION; }
private boolean bit(int pos) { return ((packed >> pos) & 1) != 0; }
static int encodeTexGenMode(int glMode) {
return switch (glMode) {
case GL11.GL_OBJECT_LINEAR -> TG_OBJ_LINEAR;
case GL11.GL_EYE_LINEAR -> TG_EYE_LINEAR;
default -> TG_NONE;
};
}
static int encodeColorMaterialMode(int glMode) {
return switch (glMode) {
case GL11.GL_AMBIENT -> CM_AMBIENT;
case GL11.GL_DIFFUSE -> CM_DIFFUSE;
case GL11.GL_SPECULAR -> CM_SPECULAR;
case GL11.GL_EMISSION -> CM_EMISSION;
case GL11.GL_AMBIENT_AND_DIFFUSE -> CM_AMBIENT_AND_DIFFUSE;
default -> CM_AMBIENT_AND_DIFFUSE;
};
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
long bits = 0;
final boolean lighting = GLStateManager.getLightingState().isEnabled();
if (lighting) {
bits |= (1L << BIT_LIGHTING);
if (GLStateManager.getLightStates()[0].isEnabled()) {
bits |= (1L << BIT_LIGHT0);
// position.w == 0 means directional
if (GLStateManager.getLightDataStates()[0].position.w == 0.0f) {
bits |= (1L << BIT_LIGHT0_DIR);
}
}
if (GLStateManager.getLightStates()[1].isEnabled()) {
bits |= (1L << BIT_LIGHT1);
if (GLStateManager.getLightDataStates()[1].position.w == 0.0f) {
bits |= (1L << BIT_LIGHT1_DIR);
}
}
if (GLStateManager.getColorMaterial().isEnabled()) {
bits |= (1L << BIT_COLOR_MATERIAL);
final int cmMode = encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue());
bits |= ((long) cmMode & 0x7) << BIT_COLOR_MAT_MODE;
}
if (GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
bits |= (1L << BIT_SEPARATE_SPECULAR);
}
}
if (GLStateManager.getFogMode().isEnabled()) {
bits |= (1L << BIT_FOG);
final int fogDistMode = GLStateManager.getFogState().getFogDistanceMode();
bits |= ((long) fogDistMode & 0x3) << BIT_FOG_DIST_MODE;
}
if (GLStateManager.getNormalizeState().isEnabled()) {
bits |= (1L << BIT_NORMALIZE);
}
if (GLStateManager.getRescaleNormalState().isEnabled()) {
bits |= (1L << BIT_RESCALE_NORMAL);
}
final TextureUnitArray texUnit = GLStateManager.getTextures();
for (int i = 0; i < MAX_UNITS; i++) {
if ((fragUnitMask & (1 << i)) != 0) {
bits |= (1L << (BIT_UNIT_TEX_BASE + i));
}
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
bits |= (1L << (BIT_UNIT_TEXMAT_BASE + i));
}
}
// TexGen (unit 0 only) - per-coordinate mode if enabled.
// TODO: per-unit texgen for units 2/3. Deferred - no MC code path uses glTexGen on those.
final TexGenState tg = texUnit.getTexGenState(0);
boolean anyTexGen = false;
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
final int m = encodeTexGenMode(tg.getMode(GL11.GL_S));
bits |= ((long) m & 0x7) << BIT_TEXGEN_S;
if (m != TG_NONE) anyTexGen = true;
}
if (texUnit.getTexGenTStates(0).isEnabled() && tg.getMode(GL11.GL_T) != 0) {
final int m = encodeTexGenMode(tg.getMode(GL11.GL_T));
bits |= ((long) m & 0x7) << BIT_TEXGEN_T;
if (m != TG_NONE) anyTexGen = true;
}
if (texUnit.getTexGenRStates(0).isEnabled() && tg.getMode(GL11.GL_R) != 0) {
final int m = encodeTexGenMode(tg.getMode(GL11.GL_R));
bits |= ((long) m & 0x7) << BIT_TEXGEN_R;
if (m != TG_NONE) anyTexGen = true;
}
if (texUnit.getTexGenQStates(0).isEnabled() && tg.getMode(GL11.GL_Q) != 0) {
final int m = encodeTexGenMode(tg.getMode(GL11.GL_Q));
bits |= ((long) m & 0x7) << BIT_TEXGEN_Q;
if (m != TG_NONE) anyTexGen = true;
}
// Force unit-0 texture matrix on when texgen is active (end portal pattern always uses it).
if (anyTexGen) {
bits |= (1L << BIT_UNIT_TEXMAT_BASE);
}
// Vertex format flags
if (hasColor) bits |= (1L << BIT_HAS_VERTEX_COLOR);
if (hasNormal) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw() && hasTexCoord) {
bits |= (1L << BIT_UNIT23_UV_FROM_UNIT0);
}
if (GLStateManager.anyClipPlaneEnabled()) {
bits |= (1L << BIT_CLIP_PLANES);
}
if (GLStateManager.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
if (GLStateManager.lineStippleActive) {
bits |= (1L << BIT_LINE_STIPPLE);
}
if (GLStateManager.instancedFfpDrawActive) {
bits |= (1L << BIT_INSTANCED);
}
return bits;
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
}
static VertexKey fromPacked(long packed) {
return new VertexKey(packed);
}
@Override
public int hashCode() {
return Long.hashCode(packed);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof VertexKey other)) return false;
return packed == other.packed;
}
@Override
public String toString() {
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%b]",
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancedDraw());
}
}
@@ -1,362 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
/**
* Generates GLSL 330 core vertex shaders for FFP emulation.
* Follows Mesa's ffvertex_prog.c build order.
*
* Attribute locations are sourced from {@link VertexFormatElement.Usage} to stay in sync with GTNHLib's vertex format setup.
*/
public final class VertexShaderGenerator {
private VertexShaderGenerator() {}
public static String generate(VertexKey key) {
final StringBuilder sb = new StringBuilder(2048);
sb.append("#version 330 core\n\n");
emitAttributes(sb, key);
emitUniforms(sb, key);
emitOutputs(sb, key);
sb.append("void main() {\n");
emitPositionTransform(sb, key);
if (key.lightingEnabled()) {
emitNormalTransform(sb, key);
emitLighting(sb, key);
} else {
emitColorPassthrough(sb, key);
}
emitTexCoordPassthrough(sb, key);
if (key.lineStipple()) {
emitLineStart(sb);
}
if (key.fogEnabled()) {
emitFogDistance(sb, key);
}
if (key.clipPlanesEnabled()) {
emitClipDistances(sb);
}
sb.append("}\n");
String source = sb.toString();
if (key.wideLineEmulation()) {
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
.replace("v_TexCoord0", "v_TexCoord0_gs")
.replace("v_TexCoord1", "v_TexCoord1_gs")
.replace("v_TexCoord2", "v_TexCoord2_gs")
.replace("v_TexCoord3", "v_TexCoord3_gs")
.replace("v_FogCoord", "v_FogCoord_gs")
.replace("v_LineStart", "v_LineStart_gs")
.replace("v_Color", "v_Color_gs");
}
return source;
}
private static void emitAttributes(StringBuilder sb, VertexKey key) {
sb.append("// Vertex attributes\n");
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
if (key.hasVertexColor()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
}
if (key.hasVertexTexCoord()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
}
if (key.hasVertexLightmap()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
}
if (key.hasVertexNormal()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
if (key.instancedDraw()) {
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL0).append(") in vec4 a_InstCol0;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL1).append(") in vec4 a_InstCol1;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL2).append(") in vec4 a_InstCol2;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL3).append(") in vec4 a_InstCol3;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_COLOR).append(") in vec4 a_InstColor;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_LIGHTMAP).append(") in vec2 a_InstLightmap;\n");
}
sb.append('\n');
}
private static void emitLineStart(StringBuilder sb) {
sb.append(" vec2 ndc = gl_Position.xy / gl_Position.w;\n");
sb.append(" v_LineStart = u_Viewport.xy + (ndc * 0.5 + 0.5) * u_Viewport.zw;\n\n");
}
private static void emitUniforms(StringBuilder sb, VertexKey key) {
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
}
private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n");
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
// Per-unit varying outputs. Each enabled texture unit (and texgen, which targets unit 0) gets its own v_TexCoord<i>.
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) {
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
sb.append('\n');
}
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Position transform\n");
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
if (key.instancedDraw()) {
sb.append(" mat4 instMV = mat4(a_InstCol0, a_InstCol1, a_InstCol2, a_InstCol3);\n");
sb.append(" vec4 eyePos = instMV * pos4;\n");
sb.append(" gl_Position = u_ProjectionMatrix * eyePos;\n");
} else {
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
}
}
sb.append('\n');
}
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n");
final String srcNormal = key.hasVertexNormal() ? "a_Normal" : "u_CurrentNormal";
if (key.instancedDraw()) {
sb.append(" vec3 normal = normalize(mat3(instMV) * ").append(srcNormal).append(");\n");
sb.append('\n');
return;
}
sb.append(" vec3 normal = u_NormalMatrix * ").append(srcNormal).append(";\n");
if (key.normalizeEnabled()) {
sb.append(" normal = normalize(normal);\n");
} else if (key.rescaleNormalsEnabled()) {
sb.append(" normal *= u_NormalScale;\n");
}
sb.append('\n');
}
private static void emitLighting(StringBuilder sb, VertexKey key) {
sb.append(" // Gouraud lighting\n");
if (key.colorMaterialEnabled()) {
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
sb.append(" vec4 matColor = ");
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancedDraw()) {
sb.append(" * a_InstColor");
}
sb.append(";\n");
// Emission source: vertex color for CM_EMISSION, else material uniform
final String emSrc = key.cmReplacesEmission() ? "matColor.rgb" : "u_MaterialEmission.rgb";
// Ambient source for scene color: vertex color for CM_AMBIENT / CM_AMBIENT_AND_DIFFUSE
final String amSrc = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
sb.append(" vec3 sceneColor = ").append(emSrc).append(" + ").append(amSrc).append(" * u_LightModelAmbient.rgb;\n");
// Alpha from diffuse source (per GL spec)
sb.append(" float outAlpha = ").append(key.cmReplacesDiffuse() ? "matColor.a" : "u_MaterialDiffuse.a").append(";\n");
} else {
// Pre-computed scene color
sb.append(" vec3 sceneColor = u_SceneColor.rgb;\n");
sb.append(" float outAlpha = u_SceneColor.a;\n");
}
sb.append(" vec3 color0 = sceneColor;\n");
if (key.separateSpecular()) {
sb.append(" vec3 specAccum = vec3(0.0);\n");
}
// Per-light accumulation
if (key.light0Enabled()) {
emitLightContribution(sb, key, 0);
}
if (key.light1Enabled()) {
emitLightContribution(sb, key, 1);
}
sb.append(" v_Color = vec4(clamp(color0, 0.0, 1.0), outAlpha);\n");
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = clamp(specAccum, 0.0, 1.0);\n");
}
sb.append('\n');
}
private static void emitLightContribution(StringBuilder sb, VertexKey key, int lightIndex) {
final String li = String.valueOf(lightIndex);
final boolean directional = (lightIndex == 0) ? key.light0Directional() : key.light1Directional();
final String posUniform = "u_Light" + li + "Position";
sb.append(" { // Light ").append(li).append('\n');
// Light vector
if (directional) {
// Directional: position is already normalized direction
sb.append(" vec3 VPpli = normalize(").append(posUniform).append(".xyz);\n");
} else {
// Positional light
sb.append(" vec3 VPpli = ").append(posUniform).append(".xyz - eyePos.xyz;\n");
sb.append(" VPpli = normalize(VPpli);\n");
}
// Dot products
sb.append(" float NdotVP = max(dot(normal, VPpli), 0.0);\n");
// Half vector for specular (infinite viewer: H = normalize(L + (0,0,1)))
sb.append(" vec3 halfVec = normalize(VPpli + vec3(0.0, 0.0, 1.0));\n");
sb.append(" float NdotHV = max(dot(normal, halfVec), 0.0);\n");
// Specular power
sb.append(" float spec = (NdotVP > 0.0 && u_MaterialShininess > 0.0) ? pow(NdotHV, u_MaterialShininess) : 0.0;\n");
// Accumulate — per-property source depends on color material mode
if (key.colorMaterialEnabled()) {
final String ambU = "u_Light" + li + "Ambient";
final String difU = "u_Light" + li + "Diffuse";
final String speU = "u_Light" + li + "Specular";
// Ambient product: light.ambient * (vertex color or material)
final String ambMat = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
sb.append(" vec3 ambient = ").append(ambU).append(".rgb * ").append(ambMat).append(";\n");
// Diffuse product
final String difMat = key.cmReplacesDiffuse() ? "matColor.rgb" : "u_MaterialDiffuse.rgb";
sb.append(" vec3 diffuse = ").append(difU).append(".rgb * ").append(difMat).append(";\n");
// Specular product
final String speMat = key.cmReplacesSpecular() ? "matColor.rgb" : "u_MaterialSpecular.rgb";
sb.append(" vec3 specular = ").append(speU).append(".rgb * ").append(speMat).append(";\n");
} else {
sb.append(" vec3 ambient = u_LightProd").append(li).append("Ambient;\n");
sb.append(" vec3 diffuse = u_LightProd").append(li).append("Diffuse;\n");
sb.append(" vec3 specular = u_LightProd").append(li).append("Specular;\n");
}
sb.append(" color0 += ambient + NdotVP * diffuse;\n");
if (key.separateSpecular()) {
sb.append(" specAccum += spec * specular;\n");
} else {
sb.append(" color0 += spec * specular;\n");
}
sb.append(" }\n");
}
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
sb.append(" // Color passthrough (no lighting)\n");
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
if (key.instancedDraw()) {
sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
} else {
sb.append(" v_Color = ").append(src).append(";\n");
}
sb.append('\n');
}
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
if (key.texGenEnabled()) {
emitTexGenCoordGeneration(sb, key);
} else if (key.unitTexCoordEnabled(0)) {
sb.append(" // Texture coordinates - unit 0\n");
final String src = key.hasVertexTexCoord()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord0";
if (key.unitTexMatEnabled(0)) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
} else {
sb.append(" v_TexCoord0 = ").append(src).append(";\n");
}
}
if (key.lightmapEnabled()) {
if (key.instancedDraw()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_InstLightmap, 0.0, 1.0);\n");
} else if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
}
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = key.unit23UvFromUnit0()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord" + i;
if (!key.unit23UvFromUnit0() && key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
sb.append(" v_TexCoord").append(i).append(" = ").append(src).append(";\n");
}
}
}
/**
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
* Coordinates without texgen use vertex attrib or default (0,0,0,1).
*/
private static void emitTexGenCoordGeneration(StringBuilder sb, VertexKey key) {
sb.append(" // TexGen coordinate generation\n");
sb.append(" vec4 texGenCoord = vec4(0.0, 0.0, 0.0, 1.0);\n");
emitTexGenComponent(sb, key.texGenModeS(), "s", "S", key);
emitTexGenComponent(sb, key.texGenModeT(), "t", "T", key);
emitTexGenComponent(sb, key.texGenModeR(), "p", "R", key);
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
// Always apply texture matrix when texgen is active (forced on in VertexKey)
sb.append(" v_TexCoord0 = u_TextureMatrix0 * texGenCoord;\n");
}
private static void emitTexGenComponent(StringBuilder sb, int mode, String swizzle, String coordName, VertexKey key) {
switch (mode) {
case VertexKey.TG_OBJ_LINEAR ->
sb.append(" texGenCoord.").append(swizzle).append(" = dot(pos4, u_TexGenObjPlane").append(coordName).append(");\n");
case VertexKey.TG_EYE_LINEAR ->
sb.append(" texGenCoord.").append(swizzle).append(" = dot(eyePos, u_TexGenEyePlane").append(coordName).append(");\n");
// TG_NONE: keep default (0 for s/t/r, 1 for q) — already set in texGenCoord init
}
}
private static boolean texGenNeedsEyePos(VertexKey key) {
return key.texGenModeS() == VertexKey.TG_EYE_LINEAR
|| key.texGenModeT() == VertexKey.TG_EYE_LINEAR
|| key.texGenModeR() == VertexKey.TG_EYE_LINEAR
|| key.texGenModeQ() == VertexKey.TG_EYE_LINEAR;
}
private static void emitFogDistance(StringBuilder sb, VertexKey key) {
sb.append(" // Fog distance\n");
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
final int fogDistMode = key.fogDistanceMode();
switch (fogDistMode) {
case 0 -> // FDM_EYE_RADIAL: Euclidean distance
sb.append(" v_FogCoord = length(eyePos.xyz);\n");
case 1 -> // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
sb.append(" v_FogCoord = eyePos.z;\n");
case 2 -> // FDM_EYE_PLANE_ABS: absolute Z
sb.append(" v_FogCoord = abs(eyePos.z);\n");
default ->
sb.append(" v_FogCoord = abs(eyePos.z);\n");
}
}
private static void emitClipDistances(StringBuilder sb) {
sb.append(" // Clip distances\n");
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = dot(u_ClipPlane[").append(i).append("], eyePos);\n");
}
}
}
@@ -1,7 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredAlphaHandler {
boolean isAlphaTestLocked();
void deferAlphaTestToggle(boolean enabled);
void deferAlphaFunc(int function, float reference);
}
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredBlendHandler {
boolean isBlendLocked();
void deferBlendModeToggle(boolean enabled);
void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha);
void flushDeferredBlend();
}
@@ -1,7 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredDepthColorHandler {
boolean isDepthColorLocked();
void deferDepthEnable(boolean enabled);
void deferColorMask(boolean r, boolean g, boolean b, boolean a);
}
@@ -1,14 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public final class GLSMConfig {
// Lightmap (replaces OpenGlHelper.lastBrightnessX/Y)
public static float lastBrightnessX;
public static float lastBrightnessY;
public static boolean hudCacheOverride;
public static volatile boolean expandVertexFormats;
private GLSMConfig() {}
}
@@ -1,44 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.hooks.events.*;
import net.minecraftforge.eventbus.api.bus.EventBus;
public final class GLSMHooks {
public static DeferredBlendHandler blendHandler;
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ShaderWorkSubmitter shaderWorkSubmitter;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
public static final EventBus<TextureDeleteEvent> TEXTURE_DELETE = EventBus.create(TextureDeleteEvent.class);
public static final EventBus<TextureUnitStateEvent> TEXTURE_UNIT_STATE = EventBus.create(TextureUnitStateEvent.class);
public static final EventBus<ProgramChangeEvent> PROGRAM_CHANGE = EventBus.create(ProgramChangeEvent.class);
public static final EventBus<ProgramDeleteEvent> PROGRAM_DELETE = EventBus.create(ProgramDeleteEvent.class);
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
public static final EventBus<LoadingCheckpointEvent> LOADING_CHECKPOINT = EventBus.create(LoadingCheckpointEvent.class);
// Reusable event instances
public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
public static final TextureDeleteEvent textureDeleteEvent = new TextureDeleteEvent();
public static final TextureUnitStateEvent textureUnitStateEvent = new TextureUnitStateEvent();
public static final ProgramChangeEvent programChangeEvent = new ProgramChangeEvent();
public static final ProgramDeleteEvent programDeleteEvent = new ProgramDeleteEvent();
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
public static final LoadingCheckpointEvent loadingCheckpointEvent = new LoadingCheckpointEvent();
private GLSMHooks() {}
}
@@ -1,94 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import lombok.Getter;
import java.util.function.Consumer;
public final class GLSMInitConfig {
@Getter private final boolean lwjglDebug;
@Getter private final StreamingUploader.UploadStrategy streamingUploadStrategy;
@Getter private final Consumer<DirectTessellator> directDrawer;
@Getter private final Runnable streamingDrawerDestroy;
@Getter private final int displayWidth;
@Getter private final int displayHeight;
@Getter private final Runnable postInitCallback;
@Getter private final boolean DSAEnabled;
@Getter private final boolean noErrorChecks;
private GLSMInitConfig(Builder builder) {
this.lwjglDebug = builder.lwjglDebug;
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.noErrorChecks = builder.noErrorChecks;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
this.DSAEnabled = builder.enableDSA;
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private boolean lwjglDebug = false;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private Consumer<DirectTessellator> directDrawer = null;
private Runnable streamingDrawerDestroy = null;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
private boolean enableDSA = false;
private boolean noErrorChecks;
private Builder() {}
public Builder lwjglDebug(boolean lwjglDebug) {
this.lwjglDebug = lwjglDebug;
return this;
}
public Builder streamingUploadStrategy(StreamingUploader.UploadStrategy strategy) {
this.streamingUploadStrategy = strategy;
return this;
}
public Builder noErrorChecks(boolean noErrorChecks) {
this.noErrorChecks = noErrorChecks;
return this;
}
public Builder directDrawer(Consumer<DirectTessellator> directDrawer) {
this.directDrawer = directDrawer;
return this;
}
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
this.streamingDrawerDestroy = streamingDrawerDestroy;
return this;
}
public Builder displaySize(int width, int height) {
this.displayWidth = width;
this.displayHeight = height;
return this;
}
public Builder postInitCallback(Runnable callback) {
this.postInitCallback = callback;
return this;
}
public Builder enableDSA(boolean enableDSA) {
this.enableDSA = enableDSA;
return this;
}
public GLSMInitConfig build() {
return new GLSMInitConfig(this);
}
}
}
@@ -1,5 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface GlintColorHandler {
void onColorChanged(float red, float green, float blue, float alpha);
}
@@ -1,18 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.shader.UniformType;
import java.util.List;
/**
* Shared per-frame uniform block for SDL GPU. Produced by Iris once per shader pack load. Every shader declares the
* whole block regardless of which members it reads.
*/
public record PerFrameUniformBlock(List<Member> members) {
public record Member(String name, UniformType type) {}
public boolean isEmpty() {
return members.isEmpty();
}
}
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
@FunctionalInterface
public interface ShaderTransformPostProcessor {
void onTransformed(String transformedSource, ShaderType shaderType);
}
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public interface ShaderWorkSubmitter {
<T> CompletableFuture<T> submit(Supplier<T> work);
}
@@ -1,9 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
/**
* AlphaTest changed - Listeners should query GLSM alpha state directly if they care what changed.
*/
public final class AlphaStateChangeEvent extends MutableEvent {
}
@@ -1,10 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class BlendFuncChangeEvent extends MutableEvent {
public int srcRgb;
public int dstRgb;
public int srcAlpha;
public int dstAlpha;
}
@@ -1,6 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
/** FogState changed - Listeners should query GLSM fog state directly if they care what changed. */
public final class FogStateChangeEvent extends MutableEvent {}
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class LightmapCoordsEvent extends MutableEvent {
public float x;
public float y;
}
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
/** Fired at loading-phase boundaries */
public final class LoadingCheckpointEvent extends MutableEvent {
public boolean requiresSync;
}
@@ -1,9 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ProgramChangeEvent extends MutableEvent {
public int previousProgram;
public int newProgram;
public boolean postBind;
}
@@ -1,7 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ProgramDeleteEvent extends MutableEvent {
public int program;
}
@@ -1,10 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ShaderColorChangeEvent extends MutableEvent {
public float red;
public float green;
public float blue;
public float alpha;
}
@@ -1,7 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureBindEvent extends MutableEvent {
public int textureId;
}
@@ -1,7 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureDeleteEvent extends MutableEvent {
public int textureId;
}
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureUnitStateEvent extends MutableEvent {
public int unit;
public boolean enabled;
}
@@ -1,32 +0,0 @@
package com.gtnewhorizons.angelica.glsm.loading;
import java.util.List;
/**
* Verifies required classes/resources are present on the classpath at load time.
* Fails fast with a descriptive error message if a dependency is missing.
*/
public final class DependencyVerifier {
/**
* A single dependency check: a classpath resource that must exist.
* @param resourcePath Resource path to check (e.g. "/com/example/SomeClass.class")
* @param errorMessage Human-readable error message if missing (include download URL)
*/
public record Check(String resourcePath, String errorMessage) {}
/**
* Verify all checks pass. Throws RuntimeException on first failure.
* @param anchor Class whose classloader to use for resource lookups
* @param checks Dependency checks to run
*/
public static void verify(Class<?> anchor, List<Check> checks) {
for (Check check : checks) {
if (anchor.getResource(check.resourcePath()) == null) {
throw new RuntimeException(check.errorMessage());
}
}
}
private DependencyVerifier() {}
}
@@ -1,54 +0,0 @@
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 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;
}
}
@@ -1,104 +0,0 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.loading.TransformerNarrower.NarrowRule;
import java.util.List;
public final class EcosystemNarrowRules {
public static final List<NarrowRule> ALL = List.of(
new NarrowRule("DragonAPI", "Reika.DragonAPI.ASM", List.of(
"Reika.DragonAPI.ASM.Patchers",
"Reika.DragonAPI.ASM.Profiling",
"Reika.DragonAPI.ASM.APIStripper",
"Reika.DragonAPI.ASM.ClassReparenter",
"Reika.DragonAPI.ASM.DependentMethodStripper",
"Reika.DragonAPI.ASM.DragonAPIClassTransformer",
"Reika.DragonAPI.ASM.FMLItemBlockPatch",
"Reika.DragonAPI.ASM.FluidNamePatch",
"Reika.DragonAPI.ASM.InterfaceInjector",
"Reika.DragonAPI.ASM.SpecialBiomePlacement",
"Reika.DragonAPI.ASM.StructureLootHooks"
)),
new NarrowRule("Xaeros", "xaero.common.core", List.of(
"xaero.common.core.transformer"
)),
new NarrowRule("Xaeros", "xaero.map.core", List.of(
"xaero.map.core.transformer"
)),
new NarrowRule("AdvancedLightsabers", "com.fiskmods.lightsabers.asm", List.of(
"com.fiskmods.lightsabers.asm.ALLoadingPlugin",
"com.fiskmods.lightsabers.asm.ASMHooks",
"com.fiskmods.lightsabers.asm.transformers"
)),
new NarrowRule("FiskHeroes", "com.fiskmods.heroes.asm", List.of(
"com.fiskmods.heroes.asm.SHLoadingPlugin",
"com.fiskmods.heroes.asm.transformers"
)),
new NarrowRule("Ears", "com.unascribed.ears", List.of(
"com.unascribed.ears.asm",
"com.unascribed.ears.common.agent"
)),
new NarrowRule("FoamFix", "pl.asie.foamfix", List.of(
"pl.asie.foamfix.bugfixmod",
"pl.asie.foamfix.coremod",
"pl.asie.foamfix.forkage",
"pl.asie.foamfix.ghostbuster",
"pl.asie.foamfix.FoamFixMod",
"pl.asie.foamfix.HackyMessageFactoryWrapper",
"pl.asie.foamfix.LaunchWrapperRuntimeFix",
"pl.asie.foamfix.ProxyClient",
"pl.asie.foamfix.ProxyCommon",
"pl.asie.foamfix.repack.com.unascribed.ears.asm",
"pl.asie.foamfix.repack.com.unascribed.ears.common.agent"
)),
new NarrowRule("Alfheim", "alfheim.common.core.asm.hook", List.of(
"alfheim.common.core.asm.hook.AlfheimFieldHookHandler",
"alfheim.common.core.asm.hook.AlfheimHPHooks",
"alfheim.common.core.asm.hook.Botania18AndUpBackport",
"alfheim.common.core.asm.hook.ElementalDamageAdapter",
"alfheim.common.core.asm.hook.integration",
"alfheim.common.core.asm.hook.extender.FurnaceExtender",
"alfheim.common.core.asm.hook.extender.ItemAuraRingExtender",
"alfheim.common.core.asm.hook.extender.ItemLensExtender",
"alfheim.common.core.asm.hook.extender.ItemTwigWandExtender",
"alfheim.common.core.asm.hook.extender.LightRelayExtender",
"alfheim.common.core.asm.hook.extender.PureDaisyExtender",
"alfheim.common.core.asm.hook.extender.QuartzExtender",
"alfheim.common.core.asm.hook.extender.RelicHooks",
"alfheim.common.core.asm.hook.extender.SparkExtender",
"alfheim.common.core.asm.hook.fixes.BotaniaGlowingRenderFixes",
"alfheim.common.core.asm.hook.fixes.CorporeaInputFix",
"alfheim.common.core.asm.hook.fixes.GodAttributesHooks",
"alfheim.common.core.asm.hook.fixes.RecipeAncientWillsFix"
)),
new NarrowRule("LegendsMod", "com.tihyo.legends.management.asm", List.of(
"com.tihyo.legends.management.asm.LegendsLoadingPlugin",
"com.tihyo.legends.management.asm.AbstractTransformer",
"com.tihyo.legends.management.asm.ASMHelper",
"com.tihyo.legends.management.asm.Transformer"
))
);
public static final String[] LWJGL3IFY_EXCLUSIONS_SHARED = {
"org.embeddedt.embeddium",
"org.taumc.celeritas",
"com.mitchej123.lwjgl.lwjgl3",
"com.mitchej123.glsm",
"com.gtnewhorizons.angelica.lwjgl3",
};
public static final String[] EARLY_REDIRECTOR_TARGETS = {
"cn.tesseract.mycelium.",
};
private EcosystemNarrowRules() {}
}
@@ -1,66 +0,0 @@
package com.gtnewhorizons.angelica.glsm.loading;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Narrows overly-broad transformer exclusions that prevent the GLSM redirector from
* transforming GL calls in mod code. Some coremods register exclusions covering their
* entire package tree, which inadvertently shields rendering code from redirection.
* <p>
* This utility removes the broad exclusion and re-adds narrower ones that cover only
* the coremod's own transformer/ASM classes, leaving rendering code exposed.
* <p>
* Operates on a {@code Set<String>} (the transformer exceptions set from LaunchClassLoader)
* and a {@code Map<String, Object>} (the Launch blackboard). Callers extract these from
* the Forge/launchwrapper infrastructure.
*/
public final class TransformerNarrower {
private static final Logger LOGGER = LogManager.getLogger("GLSM");
/**
* A rule that replaces a broad transformer exclusion with narrower ones.
* @param name Blackboard/config key suffix. (e.g. "DragonAPI").
* @param broad The broad exclusion to remove (e.g. "Reika.DragonAPI.ASM")
* @param narrow Narrower exclusions to add in its place
*/
public record NarrowRule(String name, String broad, List<String> narrow) {}
/**
* Apply narrowing rules to the transformer exceptions set.
*
* @param exceptions The mutable Set from LaunchClassLoader.transformerExceptions
* @param blackboard The Launch.blackboard map (checked for disable flags)
* @param keyPrefix Blackboard key prefix for per-rule opt-out (e.g. "angelica" or "umbra").
* Rule "DragonAPI" checks key "{prefix}.narrow.DragonAPI". If explicitly
* set to Boolean.FALSE, the rule is skipped.
* @param callerName Name for log messages (e.g. "Angelica" or "Umbra")
* @param rules The narrowing rules to apply
* @return number of rules that were applied
*/
public static int narrow(Set<String> exceptions, Map<String, Object> blackboard,
String keyPrefix, String callerName, List<NarrowRule> rules) {
int applied = 0;
for (NarrowRule rule : rules) {
final String key = keyPrefix + ".narrow." + rule.name();
if (Boolean.FALSE.equals(blackboard.get(key))) {
continue;
}
if (exceptions.remove(rule.broad())) {
for (String narrow : rule.narrow()) {
exceptions.add(narrow);
}
LOGGER.info("[{}] Narrowed {} transformer exclusion to allow GL redirection", callerName, rule.broad());
applied++;
}
}
return applied;
}
private TransformerNarrower() {}
}
@@ -1,422 +0,0 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.sun.management.ThreadMXBean;
import lombok.Setter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@SuppressWarnings("ForLoopReplaceableByForEach")
public final class Tracy {
private static final Logger LOGGER = LogManager.getLogger("Tracy");
public static final boolean ENABLED;
public static final boolean FINE_ZONES;
private static final TracyBackend BACKEND;
private static volatile boolean SHUTTING_DOWN;
private static final ConcurrentHashMap<String, Long> CLIENT_SRC_LOCS = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Long> SERVER_SRC_LOCS = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Long> PLAIN_SRC_LOCS = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Long> PLOT_NAMES = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Long> FRAME_NAMES = new ConcurrentHashMap<>();
private static final Set<String> WARNED_LEAKS = ConcurrentHashMap.newKeySet();
public static final int COLOR_CLIENT = 0x3A6EA5; // blue
public static final int COLOR_SERVER = 0xB5651D; // brown
public static final int COLOR_WORKER = 0x3F7F3F; // green
public static final int COLOR_SWAP = 0xAA3333; // red
public static final int COLOR_TERRAIN = 0x2E8B8B; // teal
public static final int COLOR_IRIS = 0x9E862E; // gold
public static final int COLOR_FFP = 0x7A4E9E; // purple
private static final ThreadLocal<ZoneStack> STACK = new ThreadLocal<>() {
@Override
protected ZoneStack initialValue() {
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
return new ZoneStack();
}
};
static {
TracyBackend backend = null;
if (SystemProperties.TRACY) {
backend = loadAndInit();
}
BACKEND = backend;
ENABLED = backend != null;
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
if (ENABLED) {
Runtime.getRuntime().addShutdownHook(new Thread(Tracy::shutdown, "Tracy-Shutdown"));
LOGGER.info("Tracy profiling enabled");
}
}
private Tracy() {}
private static void shutdown() {
SHUTTING_DOWN = true;
final Thread worker = new Thread(BACKEND::shutdown, "Tracy-Shutdown-Worker");
worker.setDaemon(true);
worker.start();
try {
worker.join(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private static TracyBackend loadAndInit() {
TracyBackend found = null;
try {
final Iterator<TracyBackend> it = ServiceLoader.load(TracyBackend.class, TracyBackend.class.getClassLoader()).iterator();
while (found == null && it.hasNext()) {
found = it.next();
}
} catch (ServiceConfigurationError | LinkageError e) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but backend unavailable: {}", e.getMessage());
return null;
}
if (found == null) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but no TracyBackend service present");
return null;
}
final TracyBackend backend = found;
final CountDownLatch done = new CountDownLatch(1);
final AtomicReference<Boolean> result = new AtomicReference<>();
final Thread init = new Thread(new InitRunner(backend, done, result), "Tracy-Init");
init.setDaemon(true);
init.start();
try {
if (!done.await(15, TimeUnit.SECONDS) && result.compareAndSet(null, Boolean.FALSE)) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but init timed out; profiling disabled");
return null;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
if (!result.compareAndSet(null, Boolean.FALSE) && Boolean.TRUE.equals(result.get())) {
backend.shutdown();
}
return null;
}
if (!Boolean.TRUE.equals(result.get())) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but native init failed; profiling disabled");
return null;
}
return backend;
}
private record InitRunner(TracyBackend backend, CountDownLatch done, AtomicReference<Boolean> result) implements Runnable {
@Override
public void run() {
final boolean ok = backend.init();
if (!result.compareAndSet(null, ok) && ok) {
backend.shutdown();
}
done.countDown();
}
}
public static void beginSection(String name) {
if (!ENABLED) return;
if ("root".equals(name)) {
unwindToRoot(STACK.get());
}
if (!CaptureGate.markersThisFrame) {
skip(name);
return;
}
final boolean client = Thread.currentThread() == GLStateManager.getMainThread();
final ConcurrentHashMap<String, Long> map = client ? CLIENT_SRC_LOCS : SERVER_SRC_LOCS;
Long srcLoc = map.get(name);
if (srcLoc == null) {
srcLoc = map.computeIfAbsent(name, n -> BACKEND.internSrcLoc((client ? "cl:" : "sv:") + n, client ? classifySection(n) : COLOR_SERVER));
}
begin(srcLoc, name);
}
private static int classifySection(String name) {
if (name.startsWith("iris_")) return COLOR_IRIS;
if (name.startsWith("draw_chunk_layer")) return COLOR_TERRAIN;
return COLOR_CLIENT;
}
private static void unwindToRoot(ZoneStack stack) {
while (stack.depth() > 0 || stack.hasSkipped()) {
final long ctx = stack.pop();
if (ctx == ZoneStack.EMPTY) continue;
if (stack.poppedGpu()) {
BACKEND.gpuEndZone();
}
if (ctx != 0L) {
BACKEND.endZone(ctx);
}
final String name = stack.poppedName();
if (name != null && WARNED_LEAKS.add(name)) {
LOGGER.warn("Tracy: unbalanced profiler section '{}' left open (leaked by its caller); closed at next root", name);
BACKEND.message("unbalanced profiler section '" + name + "' closed at root");
}
}
}
public static void beginZone(String name) {
beginZone(name, COLOR_WORKER);
}
public static void beginZone(String name, int color) {
if (!ENABLED || SHUTTING_DOWN) return;
if (!CaptureGate.markersThisFrame) {
skip(name);
return;
}
Long srcLoc = PLAIN_SRC_LOCS.get(name);
if (srcLoc == null) {
srcLoc = PLAIN_SRC_LOCS.computeIfAbsent(name, n -> BACKEND.internSrcLoc(n, color));
}
begin(srcLoc, name);
}
public static final class ZoneId {
private final long srcLoc;
private final String name;
private ZoneId(long srcLoc, String name) {
this.srcLoc = srcLoc;
this.name = name;
}
}
public static ZoneId zoneId(String name) {
return zoneId(name, COLOR_WORKER);
}
public static ZoneId zoneId(String name, int color) {
if (!ENABLED) return new ZoneId(0L, name);
Long srcLoc = PLAIN_SRC_LOCS.get(name);
if (srcLoc == null) {
srcLoc = PLAIN_SRC_LOCS.computeIfAbsent(name, n -> BACKEND.internSrcLoc(n, color));
}
return new ZoneId(srcLoc, name);
}
public static void beginZone(ZoneId zone) {
if (!ENABLED || SHUTTING_DOWN) return;
begin(zone.srcLoc, zone.name);
}
private static void skip(String name) {
STACK.get().push(0L, false, name);
}
private static void begin(long srcLoc, String name) {
if (SHUTTING_DOWN) return;
final ZoneStack stack = STACK.get();
if (srcLoc == 0 || !CaptureGate.markersThisFrame || stack.atCap()) {
stack.push(0L, false, name);
return;
}
final long ctx = BACKEND.beginZone(srcLoc);
final boolean gpu = gpuReady && gpuZonesEnabled && Thread.currentThread() == GLStateManager.getMainThread() && BACKEND.gpuBeginZone(srcLoc);
stack.push(ctx, gpu, name);
if (srcLoc == BACKEND.dynamicSrcLoc()) {
BACKEND.zoneText(ctx, name);
}
}
public static void endZone() {
if (!ENABLED || SHUTTING_DOWN) return;
final ZoneStack stack = STACK.get();
final long ctx = stack.pop();
if (ctx == ZoneStack.EMPTY) return;
if (stack.poppedGpu()) {
BACKEND.gpuEndZone();
}
if (ctx != 0L) {
BACKEND.endZone(ctx);
}
}
public static void zoneText(String text) {
if (!ENABLED || SHUTTING_DOWN) return;
final long ctx = STACK.get().peek();
if (ctx != ZoneStack.EMPTY && ctx != 0L) {
BACKEND.zoneText(ctx, text);
}
}
public static void zoneValue(long value) {
if (!ENABLED || SHUTTING_DOWN) return;
final long ctx = STACK.get().peek();
if (ctx != ZoneStack.EMPTY && ctx != 0L) {
BACKEND.zoneValue(ctx, value);
}
}
private static boolean threadNameRefreshed;
public static void frameMark() {
if (!ENABLED || SHUTTING_DOWN) return;
if (!threadNameRefreshed) {
threadNameRefreshed = true;
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
}
BACKEND.frameMark();
}
public static void frameMark(String name) {
if (!ENABLED || SHUTTING_DOWN) return;
Long ptr = FRAME_NAMES.get(name);
if (ptr == null) {
ptr = FRAME_NAMES.computeIfAbsent(name, BACKEND::internFrameName);
}
if (ptr != 0) BACKEND.frameMark(ptr);
}
public static void plotInt(String name, long value) {
plotInt(name, value, TracyBackend.PLOT_FORMAT_NUMBER);
}
public static void plotInt(String name, long value, int format) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
final long ptr = plotName(name, format);
if (ptr != 0) BACKEND.plotInt(ptr, value);
}
public static void plot(String name, double value) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
final long ptr = plotName(name, TracyBackend.PLOT_FORMAT_NUMBER);
if (ptr != 0) BACKEND.plot(ptr, value);
}
private static long plotName(String name, int format) {
Long ptr = PLOT_NAMES.get(name);
if (ptr == null) {
ptr = PLOT_NAMES.computeIfAbsent(name, n -> BACKEND.internPlotName(n, format));
}
return ptr;
}
public static long plotHandle(String name, int format) {
if (!ENABLED || SHUTTING_DOWN) return 0;
return plotName(name, format);
}
public static long plotHandle(String name) {
return plotHandle(name, TracyBackend.PLOT_FORMAT_NUMBER);
}
public static void plotInt(long handle, long value) {
if (handle == 0 || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.plotInt(handle, value);
}
public static void plot(long handle, double value) {
if (handle == 0 || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.plot(handle, value);
}
public static void message(String text) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.message(text);
}
public static boolean isConnected() {
return ENABLED && BACKEND.isConnected();
}
private static boolean gpuInitAttempted;
private static boolean gpuReady;
@Setter private static boolean gpuZonesEnabled = true;
public static void gpuCollect() {
if (!ENABLED) return;
if (!gpuInitAttempted) {
gpuInitAttempted = true;
gpuReady = BACKEND.gpuInit();
}
if (gpuReady) BACKEND.gpuCollect();
}
private static final class AllocBean {
static final ThreadMXBean BEAN;
static {
ThreadMXBean bean = null;
try {
if (ManagementFactory.getThreadMXBean() instanceof ThreadMXBean sun && sun.isThreadAllocatedMemorySupported()) {
bean = sun;
}
} catch (Throwable ignored) {}
BEAN = bean;
}
}
private static final ThreadLocal<long[]> PREV_ALLOC = ThreadLocal.withInitial(() -> new long[1]);
public static void plotAllocRate(long plotHandle) {
if (!ENABLED || SHUTTING_DOWN || AllocBean.BEAN == null) return;
if (!CaptureGate.markersThisFrame) {
PREV_ALLOC.get()[0] = 0;
return;
}
final long now = AllocBean.BEAN.getThreadAllocatedBytes(Thread.currentThread().threadId());
if (now < 0) return;
final long[] prev = PREV_ALLOC.get();
if (prev[0] != 0) {
plotInt(plotHandle, now - prev[0]);
}
prev[0] = now;
}
private static List<GarbageCollectorMXBean> gcBeans;
private static boolean gcBaselineValid;
private static long prevGcCount;
private static long prevGcTimeMs;
private static long gcCountHandle;
private static long gcTimeHandle;
public static void plotGcStats() {
if (!ENABLED || SHUTTING_DOWN) return;
if (!CaptureGate.markersThisFrame) {
gcBaselineValid = false;
return;
}
if (gcBeans == null) {
gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
gcCountHandle = plotHandle("gcCount");
gcTimeHandle = plotHandle("gcTimeMs");
}
long count = 0;
long timeMs = 0;
for (int i = 0; i < gcBeans.size(); i++) {
final GarbageCollectorMXBean bean = gcBeans.get(i);
final long c = bean.getCollectionCount();
final long t = bean.getCollectionTime();
if (c > 0) count += c;
if (t > 0) timeMs += t;
}
if (gcBaselineValid) {
plotInt(gcCountHandle, count - prevGcCount);
plotInt(gcTimeHandle, timeMs - prevGcTimeMs);
if (count > prevGcCount) {
message("GC: +" + (count - prevGcCount) + " collections, +" + (timeMs - prevGcTimeMs) + " ms");
}
}
gcBaselineValid = true;
prevGcCount = count;
prevGcTimeMs = timeMs;
}
}
@@ -1,36 +0,0 @@
package com.gtnewhorizons.angelica.glsm.profiling;
public interface TracyBackend {
int PLOT_FORMAT_NUMBER = 0;
int PLOT_FORMAT_MEMORY = 1;
int PLOT_FORMAT_PERCENTAGE = 2;
boolean init();
long internSrcLoc(String name, int color);
long dynamicSrcLoc();
long beginZone(long srcLoc);
void endZone(long ctx);
void zoneText(long ctx, String text);
void zoneValue(long ctx, long value);
void frameMark();
void frameMark(long namePtr);
long internFrameName(String name);
long internPlotName(String name, int format);
void plotInt(long namePtr, long value);
void plot(long namePtr, double value);
void message(String text);
void setCurrentThreadName(String name);
boolean isConnected();
void shutdown();
boolean gpuInit();
boolean gpuBeginZone(long srcLoc);
void gpuEndZone();
void gpuCollect();
}
@@ -1,87 +0,0 @@
package com.gtnewhorizons.angelica.glsm.profiling;
public final class ZoneStack {
public static final long EMPTY = -1L;
static final int MAX_DEPTH = 512;
private long[] stack = new long[64];
private boolean[] gpu = new boolean[64];
private String[] names = new String[64];
private int depth;
private int skipped;
private boolean lastPoppedGpu;
private String lastPoppedName;
public boolean atCap() {
return depth == MAX_DEPTH;
}
public boolean push(long ctx) {
return push(ctx, false, null);
}
public boolean push(long ctx, boolean gpuBracketed) {
return push(ctx, gpuBracketed, null);
}
public boolean push(long ctx, boolean gpuBracketed, String name) {
if (depth == MAX_DEPTH) {
skipped++;
return false;
}
if (depth == stack.length) {
final long[] grownStack = new long[stack.length * 2];
System.arraycopy(stack, 0, grownStack, 0, stack.length);
stack = grownStack;
final boolean[] grownGpu = new boolean[gpu.length * 2];
System.arraycopy(gpu, 0, grownGpu, 0, gpu.length);
gpu = grownGpu;
final String[] grownNames = new String[names.length * 2];
System.arraycopy(names, 0, grownNames, 0, names.length);
names = grownNames;
}
gpu[depth] = gpuBracketed;
names[depth] = name;
stack[depth++] = ctx;
return true;
}
public long pop() {
if (skipped > 0) {
skipped--;
lastPoppedGpu = false;
lastPoppedName = null;
return EMPTY;
}
if (depth == 0) {
lastPoppedGpu = false;
lastPoppedName = null;
return EMPTY;
}
lastPoppedGpu = gpu[--depth];
lastPoppedName = names[depth];
names[depth] = null;
return stack[depth];
}
public boolean poppedGpu() {
return lastPoppedGpu;
}
public String poppedName() {
return lastPoppedName;
}
public boolean hasSkipped() {
return skipped > 0;
}
public long peek() {
if (skipped > 0 || depth == 0) return EMPTY;
return stack[depth - 1];
}
public int depth() {
return depth;
}
}
@@ -1,59 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Represents an accumulated draw call during display list compilation.
* Matrix-as-Data Architecture: Stores untransformed vertices + captured matrix.
* The matrix will be applied at runtime, not baked into vertices.
*/
public final class AccumulatedDraw {
public final VertexFormat format;
public final List<ByteBuffer> drawBuffers;
public final int drawMode;
public RestoreData restoreData;
public AccumulatedDraw(DirectTessellator tessellator, boolean copyLast) {
this.format = tessellator.getVertexFormat();
this.drawMode = tessellator.drawMode;
this.drawBuffers = new ArrayList<>();
this.drawBuffers.add(tessellator.allocateBufferCopy());
if (copyLast) {
this.restoreData = new RestoreData(tessellator);
}
}
public void mergeDraw(DirectTessellator tessellator, boolean copyLast) {
this.drawBuffers.add(tessellator.allocateBufferCopy());
if (copyLast) {
this.restoreData = new RestoreData(tessellator);
}
}
/**
* Holds last vertex attribute values for GL state restoration after VBO draw.
* Used to sync GLSM cache with actual GL state after VBO rendering.
*/
public static final class RestoreData {
public final int lastColor;
public final int lastNormal;
public final float lastTexCoordU;
public final float lastTexCoordV;
public final int flags;
public RestoreData(DirectTessellator tessellator) {
this.lastColor = tessellator.getPackedColor();
this.lastNormal = tessellator.getPackedNormal();
this.lastTexCoordU = (float) tessellator.getLastTextureU();
this.lastTexCoordV = (float) tessellator.getLastTextureV();
this.flags = tessellator.getVertexFormat().getVertexFlags();
}
}
}
@@ -1,198 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class AttribSnapshot {
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
private static void warnOnce(String key, String fmt, Object... args) {
if (WARN_ONCE.add(key)) GLStateManager.LOGGER.warn(fmt, args);
}
public record AttribDesc(int location, int size, int type, boolean normalized, int stride, long offset, int sourceVboId, ByteBuffer readBuffer, long readBufferBaseOffset) {
public int effectiveStride() {
return stride != 0 ? stride : size * GLTypes.sizeBytes(type);
}
public int typeSizeBytes() {
return GLTypes.sizeBytes(type);
}
}
private final AttribDesc[] perLocation;
private final List<ByteBuffer> allocatedBuffers;
private boolean freed;
private AttribSnapshot(AttribDesc[] perLocation, List<ByteBuffer> allocatedBuffers) {
this.perLocation = perLocation;
this.allocatedBuffers = allocatedBuffers;
}
public AttribDesc get(int location) {
return (location >= 0 && location < perLocation.length) ? perLocation[location] : null;
}
public int maxLocations() {
return perLocation.length;
}
public void free() {
if (freed) return;
freed = true;
for (ByteBuffer buf : allocatedBuffers) {
MemoryUtilities.memFree(buf);
}
allocatedBuffers.clear();
}
/**
* Snapshot attribs over a tight vertex window {@code [firstVertex, firstVertex+vertexCount)}.
*
* <p>Hot path (single VBO backing every enabled attrib — the typical draw) runs without
* allocating any hashmaps. A second distinct VBO promotes to the map-backed fallback.
*/
public static AttribSnapshot snapshot(int firstVertex, int vertexCount) {
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
boolean success = false;
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
int rangeSoleVboId = 0;
long rangeSoleStart = Long.MAX_VALUE;
long rangeSoleEnd = Long.MIN_VALUE;
Int2ObjectOpenHashMap<long[]> vboRanges = null;
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * GLTypes.sizeBytes(a.type);
final long start = a.offset + (long) firstVertex * stride;
final long end = a.offset + lastVtx * stride + typeBytes;
if (vboRanges != null) {
long[] r = vboRanges.get(a.vboId);
if (r == null) {
vboRanges.put(a.vboId, new long[] { start, end });
} else {
if (start < r[0]) r[0] = start;
if (end > r[1]) r[1] = end;
}
} else if (rangeSoleVboId == 0 || rangeSoleVboId == a.vboId) {
rangeSoleVboId = a.vboId;
if (start < rangeSoleStart) rangeSoleStart = start;
if (end > rangeSoleEnd) rangeSoleEnd = end;
} else {
// Spill: second distinct vboId. Promote the accumulated range to the map.
vboRanges = new Int2ObjectOpenHashMap<>();
vboRanges.put(rangeSoleVboId, new long[] { rangeSoleStart, rangeSoleEnd });
vboRanges.put(a.vboId, new long[] { start, end });
}
}
// Pass 2 — readback cache. Same spill pattern.
int readSoleVboId = 0;
ByteBuffer readSoleBuf = null;
long readSoleOffset = 0;
Int2ObjectOpenHashMap<ByteBuffer> vboBuffers = null;
Int2LongOpenHashMap vboReadOffsets = null;
try {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
ByteBuffer buf;
long readOffset;
if (vboBuffers != null) {
buf = vboBuffers.get(a.vboId);
readOffset = buf != null ? vboReadOffsets.get(a.vboId) : 0;
} else if (readSoleVboId == a.vboId) {
buf = readSoleBuf;
readOffset = readSoleOffset;
} else {
buf = null;
readOffset = 0;
}
if (buf == null) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) {
warnOnce("vbo-orphaned", "[AttribSnapshot] GL_BUFFER_SIZE={} for VBO {} -- skipping capture", bufferSize, a.vboId);
return null;
}
final long rStart;
final long rEnd;
if (vboRanges != null) {
final long[] range = vboRanges.get(a.vboId);
rStart = range[0];
rEnd = range[1];
} else {
rStart = rangeSoleStart;
rEnd = rangeSoleEnd;
}
if (rEnd > bufferSize) {
warnOnce("vbo-overrun", "[AttribSnapshot] attrib range {}..{} exceeds VBO {} size {} -- skipping capture", rStart, rEnd, a.vboId, bufferSize);
return null;
}
final long ro = Math.max(0, rStart);
final int rs = (int) (rEnd - ro);
if (rs <= 0) {
warnOnce("vbo-empty-range", "[AttribSnapshot] empty attrib range {}..{} for VBO {} -- skipping capture", rStart, rEnd, a.vboId);
return null;
}
buf = MemoryUtilities.memAlloc(rs);
GLStateManager.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, ro, buf);
allocated.add(buf);
readOffset = ro;
if (vboBuffers != null) {
vboBuffers.put(a.vboId, buf);
vboReadOffsets.put(a.vboId, ro);
} else if (readSoleVboId == 0) {
readSoleVboId = a.vboId;
readSoleBuf = buf;
readSoleOffset = ro;
} else {
vboBuffers = new Int2ObjectOpenHashMap<>();
vboReadOffsets = new Int2LongOpenHashMap();
vboBuffers.put(readSoleVboId, readSoleBuf);
vboReadOffsets.put(readSoleVboId, readSoleOffset);
vboBuffers.put(a.vboId, buf);
vboReadOffsets.put(a.vboId, ro);
}
}
out[i] = new AttribDesc(i, a.size, a.type, a.normalized, a.stride, a.offset, a.vboId, buf, readOffset);
} else if (a.clientPointer != null) {
out[i] = new AttribDesc(i, a.size, a.type, a.normalized, a.stride, a.clientPointer.position(), 0, a.clientPointer, 0L);
}
}
success = true;
return new AttribSnapshot(out, allocated);
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
if (!success) {
for (ByteBuffer b : allocated) MemoryUtilities.memFree(b);
}
}
}
}
@@ -1,530 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetDouble;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
/**
* Executes commands from a CommandBuffer
*/
public final class CommandBufferExecutor {
private CommandBufferExecutor() {}
// Static reusable buffer for matrix ops (render thread only, LWJGL2/3 compatible)
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
// Static reusable buffer for fog/light/material params
private static final FloatBuffer PARAMS_BUFFER = BufferUtils.createFloatBuffer(4);
// Static reusable buffer for clip plane equation
private static final DoubleBuffer PARAMS_DOUBLE_BUFFER = BufferUtils.createDoubleBuffer(4);
// Static reusable buffer for draw buffers
private static final int MAX_DRAW_BUFFERS = 8;
private static final IntBuffer DRAW_BUFFERS_BUFFER = BufferUtils.createIntBuffer(MAX_DRAW_BUFFERS);
/**
* Execute all commands in the buffer.
*
* @param buffer The command buffer (position should be at 0, limit at size)
* @param complexObjects Array of complex objects (TexImage2DCmd, etc.)
* @param ownedVbos Array of VBOs owned by the display list (indexed by DrawRange commands)
*/
public static void execute(
ByteBuffer buffer,
DisplayListCommand[] complexObjects,
DisplayListVBO ownedVbos,
int list,
CompiledDisplayList compiledDisplayList
) {
long ptr = memAddress(buffer);
final long end = ptr + buffer.limit();
while (ptr < end) {
final int cmd = memGetInt(ptr);
ptr += 4;
switch (cmd) {
// === Single int commands ===
case GLCommand.ENABLE -> {
GLStateManager.glEnable(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DISABLE -> {
GLStateManager.glDisable(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CLEAR -> {
GLStateManager.glClear(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CLEAR_STENCIL -> {
GLStateManager.glClearStencil(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CULL_FACE -> {
GLStateManager.glCullFace(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DEPTH_FUNC -> {
GLStateManager.glDepthFunc(memGetInt(ptr));
ptr += 4;
}
case GLCommand.SHADE_MODEL -> {
GLStateManager.glShadeModel(memGetInt(ptr));
ptr += 4;
}
case GLCommand.LOGIC_OP -> {
GLStateManager.glLogicOp(memGetInt(ptr));
ptr += 4;
}
case GLCommand.MATRIX_MODE -> {
GLStateManager.glMatrixMode(memGetInt(ptr));
ptr += 4;
}
case GLCommand.ACTIVE_TEXTURE -> {
GLStateManager.glActiveTexture(memGetInt(ptr));
ptr += 4;
}
case GLCommand.USE_PROGRAM -> {
GLStateManager.glUseProgram(memGetInt(ptr));
ptr += 4;
}
case GLCommand.PUSH_ATTRIB -> {
GLStateManager.glPushAttrib(memGetInt(ptr));
ptr += 4;
}
case GLCommand.POP_ATTRIB -> {
GLStateManager.glPopAttrib();
ptr += 4; // Skip unused padding
}
case GLCommand.LOAD_IDENTITY -> {
GLStateManager.glLoadIdentity();
}
case GLCommand.PUSH_MATRIX -> {
GLStateManager.glPushMatrix();
}
case GLCommand.POP_MATRIX -> {
GLStateManager.glPopMatrix();
}
case GLCommand.STENCIL_MASK -> {
GLStateManager.glStencilMask(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DEPTH_MASK -> {
GLStateManager.glDepthMask(memGetInt(ptr) != 0);
ptr += 4;
}
case GLCommand.FRONT_FACE -> {
GLStateManager.glFrontFace(memGetInt(ptr));
ptr += 4;
}
// === Two int commands ===
case GLCommand.BIND_TEXTURE -> {
final int target = memGetInt(ptr);
final int texture = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glBindTexture(target, texture);
}
case GLCommand.POLYGON_MODE -> {
final int face = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glPolygonMode(face, mode);
}
case GLCommand.COLOR_MATERIAL -> {
final int face = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glColorMaterial(face, mode);
}
case GLCommand.LINE_STIPPLE -> {
final int factor = memGetInt(ptr);
final int pattern = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glLineStipple(factor, (short) pattern);
}
case GLCommand.STENCIL_MASK_SEPARATE -> {
final int face = memGetInt(ptr);
final int mask = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glStencilMaskSeparate(face, mask);
}
case GLCommand.FOGI -> {
final int pname = memGetInt(ptr);
final int param = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glFogi(pname, param);
}
case GLCommand.HINT -> {
final int target = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glHint(target, mode);
}
// === Three int commands ===
case GLCommand.STENCIL_FUNC -> {
final int func = memGetInt(ptr);
final int ref = memGetInt(ptr + 4);
final int mask = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glStencilFunc(func, ref, mask);
}
case GLCommand.STENCIL_OP -> {
final int fail = memGetInt(ptr);
final int zfail = memGetInt(ptr + 4);
final int zpass = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glStencilOp(fail, zfail, zpass);
}
case GLCommand.TEX_PARAMETERI -> {
final int target = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int param = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glTexParameteri(target, pname, param);
}
// === Four int commands ===
case GLCommand.VIEWPORT -> {
final int x = memGetInt(ptr);
final int y = memGetInt(ptr + 4);
final int width = memGetInt(ptr + 8);
final int height = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glViewport(x, y, width, height);
}
case GLCommand.SCISSOR -> {
final int x = memGetInt(ptr);
final int y = memGetInt(ptr + 4);
final int width = memGetInt(ptr + 8);
final int height = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glScissor(x, y, width, height);
}
case GLCommand.BLEND_FUNC -> {
final int srcRgb = memGetInt(ptr);
final int dstRgb = memGetInt(ptr + 4);
final int srcAlpha = memGetInt(ptr + 8);
final int dstAlpha = memGetInt(ptr + 12);
ptr += 16;
if (srcRgb == srcAlpha && dstRgb == dstAlpha) {
GLStateManager.glBlendFunc(srcRgb, dstRgb);
} else {
GLStateManager.tryBlendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
}
}
case GLCommand.COLOR_MASK -> {
final boolean r = memGetInt(ptr) != 0;
final boolean g = memGetInt(ptr + 4) != 0;
final boolean b = memGetInt(ptr + 8) != 0;
final boolean a = memGetInt(ptr + 12) != 0;
ptr += 16;
GLStateManager.glColorMask(r, g, b, a);
}
case GLCommand.STENCIL_FUNC_SEPARATE -> {
final int face = memGetInt(ptr);
final int func = memGetInt(ptr + 4);
final int ref = memGetInt(ptr + 8);
final int mask = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glStencilFuncSeparate(face, func, ref, mask);
}
case GLCommand.STENCIL_OP_SEPARATE -> {
final int face = memGetInt(ptr);
final int sfail = memGetInt(ptr + 4);
final int dpfail = memGetInt(ptr + 8);
final int dppass = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glStencilOpSeparate(face, sfail, dpfail, dppass);
}
// === Float commands ===
case GLCommand.POINT_SIZE -> {
GLStateManager.glPointSize(memGetFloat(ptr));
ptr += 4;
}
case GLCommand.LINE_WIDTH -> {
GLStateManager.glLineWidth(memGetFloat(ptr));
ptr += 4;
}
case GLCommand.POLYGON_OFFSET -> {
final float factor = memGetFloat(ptr);
final float units = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glPolygonOffset(factor, units);
}
case GLCommand.COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glColor4f(r, g, b, a);
}
case GLCommand.CLEAR_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glClearColor(r, g, b, a);
}
case GLCommand.CLEAR_DEPTH -> {
final double depth = memGetDouble(ptr);
ptr += 8;
GLStateManager.glClearDepth(depth);
}
case GLCommand.BLEND_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glBlendColor(r, g, b, a);
}
// === Mixed int+float commands ===
case GLCommand.ALPHA_FUNC -> {
final int func = memGetInt(ptr);
final float ref = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glAlphaFunc(func, ref);
}
case GLCommand.FOGF -> {
final int pname = memGetInt(ptr);
final float param = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glFogf(pname, param);
}
case GLCommand.LIGHTF -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glLightf(light, pname, param);
}
case GLCommand.LIGHT_MODELF -> {
final int pname = memGetInt(ptr);
final float param = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glLightModelf(pname, param);
}
case GLCommand.LIGHTI -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int param = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glLighti(light, pname, param);
}
case GLCommand.LIGHT_MODELI -> {
final int pname = memGetInt(ptr);
final int param = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glLightModeli(pname, param);
}
case GLCommand.MATERIALF -> {
final int face = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glMaterialf(face, pname, param);
}
case GLCommand.TEX_PARAMETERF -> {
final int target = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glTexParameterf(target, pname, param);
}
// === Matrix commands ===
case GLCommand.MULT_MATRIX -> {
// Mode-agnostic: just multiply current matrix
MATRIX_BUFFER.clear();
for (int i = 0; i < 16; i++) {
MATRIX_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
MATRIX_BUFFER.flip();
GLStateManager.glMultMatrix(MATRIX_BUFFER);
}
case GLCommand.TRANSLATE -> {
final float x = memGetFloat(ptr);
final float y = memGetFloat(ptr + 4);
final float z = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glTranslatef(x, y, z);
}
case GLCommand.SCALE -> {
final float x = memGetFloat(ptr);
final float y = memGetFloat(ptr + 4);
final float z = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glScalef(x, y, z);
}
case GLCommand.LOAD_MATRIX -> {
MATRIX_BUFFER.clear();
for (int i = 0; i < 16; i++) {
MATRIX_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
MATRIX_BUFFER.flip();
GLStateManager.glLoadMatrix(MATRIX_BUFFER);
}
// === Buffer commands ===
case GLCommand.FOG -> {
final int pname = memGetInt(ptr);
final int count = memGetInt(ptr + 4);
ptr += 8;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glFog(pname, PARAMS_BUFFER);
}
case GLCommand.LIGHT -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
ptr += 12;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glLight(light, pname, PARAMS_BUFFER);
}
case GLCommand.LIGHT_MODEL -> {
final int pname = memGetInt(ptr);
final int count = memGetInt(ptr + 4);
ptr += 8;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glLightModel(pname, PARAMS_BUFFER);
}
case GLCommand.MATERIAL -> {
final int face = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
ptr += 12;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glMaterial(face, pname, PARAMS_BUFFER);
}
case GLCommand.CLIP_PLANE -> {
final int plane = memGetInt(ptr);
final double a = memGetDouble(ptr + 4);
final double b = memGetDouble(ptr + 12);
final double c = memGetDouble(ptr + 20);
final double d = memGetDouble(ptr + 28);
ptr += 36;
PARAMS_DOUBLE_BUFFER.clear();
PARAMS_DOUBLE_BUFFER.put(a).put(b).put(c).put(d);
PARAMS_DOUBLE_BUFFER.flip();
GLStateManager.glClipPlane(plane, PARAMS_DOUBLE_BUFFER);
}
// === Draw commands ===
case GLCommand.DRAW_RANGE -> {
final int vboIndex = memGetInt(ptr);
ptr += 4;
ownedVbos.render(vboIndex);
}
case GLCommand.DRAW_RANGE_RESTORE -> {
// Draw VBO range then restore GL current state from last vertex attributes
final int vboIndex = memGetInt(ptr);
final int flags = memGetInt(ptr + 4);
// Draw the VBO
ownedVbos.render(vboIndex);
// Restore attributes based on flags
if ((flags & VertexFlags.COLOR_BIT) != 0) {
final int color = memGetInt(ptr + 8);
final byte a = (byte) ((color >> 24) & 0xFF);
final byte b = (byte) ((color >> 16) & 0xFF);
final byte g = (byte) ((color >> 8) & 0xFF);
final byte r = (byte) ((color & 0xFF));
GLStateManager.glColor4ub(r, g, b, a);
}
if ((flags & VertexFlags.NORMAL_BIT) != 0) {
final int normal = memGetInt(ptr + 12);
float nx = (byte)(normal) / 127.0f;
float ny = (byte)(normal >> 8) / 127.0f;
float nz = (byte)(normal >> 16) / 127.0f;
GLStateManager.glNormal3f(nx, ny, nz);
}
if ((flags & VertexFlags.TEXTURE_BIT) != 0) {
final float s = memGetFloat(ptr + 16);
final float t = memGetFloat(ptr + 20);
GLStateManager.glTexCoord2f(s, t);
}
ptr += 24; // Skip full command size (28 - 4 for cmd already read)
}
case GLCommand.CALL_LIST -> {
final int listId = memGetInt(ptr);
ptr += 4;
DisplayListManager.glCallList(listId);
}
case GLCommand.DRAW_BUFFER -> {
GLStateManager.glDrawBuffer(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DRAW_BUFFERS -> {
final int count = memGetInt(ptr);
ptr += 4;
DRAW_BUFFERS_BUFFER.clear();
for (int i = 0; i < count; i++) {
DRAW_BUFFERS_BUFFER.put(memGetInt(ptr + i * 4));
}
DRAW_BUFFERS_BUFFER.flip();
ptr += 4 * MAX_DRAW_BUFFERS;
GLStateManager.glDrawBuffers(DRAW_BUFFERS_BUFFER);
}
case GLCommand.BIND_VBO -> {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, memGetInt(ptr));
ptr += 4;
}
case GLCommand.BIND_VAO -> {
GLStateManager.glBindVertexArray(memGetInt(ptr));
ptr += 4;
}
// === Complex object reference ===
case GLCommand.COMPLEX_REF -> {
final int index = memGetInt(ptr);
ptr += 4;
complexObjects[index].execute();
}
default -> throw new IllegalStateException("Unknown command opcode: " + cmd + " in display list with ID " + list + "\n" + DisplayListManager.getCompiledDisplayListString(list, compiledDisplayList, null));
}
}
}
}
@@ -1,701 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatchBuilder;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
public final class CommandRecorder {
private static final int DEFAULT_CAPACITY = 2048; // Guess at typical display list size
private ByteBuffer buffer;
private long basePointer;
private long writePointer;
private final ArrayList<DisplayListCommand> complexObjects = new ArrayList<>();
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
public CommandRecorder() {
this(DEFAULT_CAPACITY);
}
public CommandRecorder(int initialCapacity) {
this.buffer = memAlloc(initialCapacity);
this.basePointer = memAddress0(buffer);
this.writePointer = basePointer;
}
// === Low-level write operations ===
private void writeInt(int value) {
memPutInt(writePointer, value);
writePointer += 4;
}
private void writeLong(long value) {
memPutLong(writePointer, value);
writePointer += 8;
}
private void writeFloat(float value) {
memPutFloat(writePointer, value);
writePointer += 4;
}
private void writeDouble(double value) {
memPutDouble(writePointer, value);
writePointer += 8;
}
private void ensureCapacity(int needed) {
final int size = size();
final int remaining = buffer.capacity() - size;
if (remaining < needed) {
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
buffer = memRealloc(buffer, newCapacity);
basePointer = memAddress0(buffer);
writePointer = basePointer + size;
}
}
public IndexedDrawBatchBuilder getIndexedDraws() {
return indexedDraws;
}
public void writeIndexedDrawCapture(IndexedDrawCapture capture) {
indexedDraws.add(capture);
writeComplexCommand(capture.placeholder);
}
public void free() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
}
// === Single int commands ===
public void writeEnable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.ENABLE);
writeInt(cap);
}
public void writeDisable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.DISABLE);
writeInt(cap);
}
public void writeClear(int mask) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR);
writeInt(mask);
}
public void writeClearStencil(int s) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR_STENCIL);
writeInt(s);
}
public void writeCullFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.CULL_FACE);
writeInt(mode);
}
public void writeDepthFunc(int func) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_FUNC);
writeInt(func);
}
public void writeShadeModel(int mode) {
ensureCapacity(8);
writeInt(GLCommand.SHADE_MODEL);
writeInt(mode);
}
public void writeLogicOp(int opcode) {
ensureCapacity(8);
writeInt(GLCommand.LOGIC_OP);
writeInt(opcode);
}
public void writeMatrixMode(int mode) {
ensureCapacity(8);
writeInt(GLCommand.MATRIX_MODE);
writeInt(mode);
}
public void writeActiveTexture(int texture) {
ensureCapacity(8);
writeInt(GLCommand.ACTIVE_TEXTURE);
writeInt(texture);
}
public void writeUseProgram(int program) {
ensureCapacity(8);
writeInt(GLCommand.USE_PROGRAM);
writeInt(program);
}
public void writePushAttrib(int mask) {
ensureCapacity(8);
writeInt(GLCommand.PUSH_ATTRIB);
writeInt(mask);
}
public void writePopAttrib() {
ensureCapacity(8);
writeInt(GLCommand.POP_ATTRIB);
writeInt(0); // unused padding
}
public void writeLoadIdentity() {
ensureCapacity(4);
writeInt(GLCommand.LOAD_IDENTITY);
}
public void writePushMatrix() {
ensureCapacity(4);
writeInt(GLCommand.PUSH_MATRIX);
}
public void writePopMatrix() {
ensureCapacity(4);
writeInt(GLCommand.POP_MATRIX);
}
public void writeStencilMask(int mask) {
ensureCapacity(8);
writeInt(GLCommand.STENCIL_MASK);
writeInt(mask);
}
public void writeDepthMask(boolean flag) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_MASK);
writeInt(flag ? 1 : 0);
}
public void writeFrontFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.FRONT_FACE);
writeInt(mode);
}
// === Two int commands ===
public void writeBindTexture(int target, int texture) {
ensureCapacity(12);
writeInt(GLCommand.BIND_TEXTURE);
writeInt(target);
writeInt(texture);
}
public void writePolygonMode(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_MODE);
writeInt(face);
writeInt(mode);
}
public void writeColorMaterial(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.COLOR_MATERIAL);
writeInt(face);
writeInt(mode);
}
public void writeLineStipple(int factor, int pattern) {
ensureCapacity(12);
writeInt(GLCommand.LINE_STIPPLE);
writeInt(factor);
writeInt(pattern);
}
public void writeStencilMaskSeparate(int face, int mask) {
ensureCapacity(12);
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
writeInt(face);
writeInt(mask);
}
public void writeFogi(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.FOGI);
writeInt(pname);
writeInt(param);
}
public void writeHint(int target, int mode) {
ensureCapacity(12);
writeInt(GLCommand.HINT);
writeInt(target);
writeInt(mode);
}
// === Three int commands ===
public void writeStencilFunc(int func, int ref, int mask) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_FUNC);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOp(int fail, int zfail, int zpass) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_OP);
writeInt(fail);
writeInt(zfail);
writeInt(zpass);
}
public void writeTexParameteri(int target, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERI);
writeInt(target);
writeInt(pname);
writeInt(param);
}
// === Four int commands ===
public void writeViewport(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.VIEWPORT);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void writeScissor(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.SCISSOR);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_FUNC);
writeInt(srcRgb);
writeInt(dstRgb);
writeInt(srcAlpha);
writeInt(dstAlpha);
}
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
ensureCapacity(20);
writeInt(GLCommand.COLOR_MASK);
writeInt(red ? 1 : 0);
writeInt(green ? 1 : 0);
writeInt(blue ? 1 : 0);
writeInt(alpha ? 1 : 0);
}
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
writeInt(face);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_OP_SEPARATE);
writeInt(face);
writeInt(sfail);
writeInt(dpfail);
writeInt(dppass);
}
// === Float commands ===
public void writePointSize(float size) {
ensureCapacity(8);
writeInt(GLCommand.POINT_SIZE);
writeFloat(size);
}
public void writeLineWidth(float width) {
ensureCapacity(8);
writeInt(GLCommand.LINE_WIDTH);
writeFloat(width);
}
public void writePolygonOffset(float factor, float units) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_OFFSET);
writeFloat(factor);
writeFloat(units);
}
public void writeColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearDepth(double depth) {
ensureCapacity(12);
writeInt(GLCommand.CLEAR_DEPTH);
writeDouble(depth);
}
public void writeBlendColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
// === Mixed int+float commands ===
public void writeAlphaFunc(int func, float ref) {
ensureCapacity(12);
writeInt(GLCommand.ALPHA_FUNC);
writeInt(func);
writeFloat(ref);
}
public void writeFogf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.FOGF);
writeInt(pname);
writeFloat(param);
}
public void writeLightf(int light, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTF);
writeInt(light);
writeInt(pname);
writeFloat(param);
}
public void writeLightModelf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELF);
writeInt(pname);
writeFloat(param);
}
public void writeLighti(int light, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTI);
writeInt(light);
writeInt(pname);
writeInt(param);
}
public void writeLightModeli(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELI);
writeInt(pname);
writeInt(param);
}
public void writeMaterialf(int face, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.MATERIALF);
writeInt(face);
writeInt(pname);
writeFloat(param);
}
public void writeTexParameterf(int target, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERF);
writeInt(target);
writeInt(pname);
writeFloat(param);
}
// === Transformation commands ===
public void writeTranslate(Vector3f translationVector) {
ensureCapacity(16);
writeInt(GLCommand.TRANSLATE);
writeFloat(translationVector.x);
writeFloat(translationVector.y);
writeFloat(translationVector.z);
}
public void writeScale(Vector3f scaleVector) {
ensureCapacity(16);
writeInt(GLCommand.SCALE);
writeFloat(scaleVector.x);
writeFloat(scaleVector.y);
writeFloat(scaleVector.z);
}
// === Matrix commands ===
public void writeMultMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.MULT_MATRIX);
writeMatrix4f(matrix);
}
public void writeLoadMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.LOAD_MATRIX);
writeMatrix4f(matrix);
}
/**
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
*/
private void writeMatrix4f(Matrix4f m) {
// Column 0
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
// Column 1
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
// Column 2
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
// Column 3
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
}
// === Buffer commands (inline 4 floats) ===
public void writeFog(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.FOG);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLight(int light, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.LIGHT);
writeInt(light);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLightModel(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.LIGHT_MODEL);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeMaterial(int face, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.MATERIAL);
writeInt(face);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeClipPlane(int plane, double a, double b, double c, double d) {
ensureCapacity(40);
writeInt(GLCommand.CLIP_PLANE);
writeInt(plane);
writeDouble(a);
writeDouble(b);
writeDouble(c);
writeDouble(d);
}
// === Draw commands ===
public void writeDrawRange(int vboIndex) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_RANGE);
writeInt(vboIndex);
}
/**
* Write a DRAW_RANGE_RESTORE command that draws a VBO range and restores GL current state after.
* Used for immediate mode VBOs to restore GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS.
*
* @param vboIndex Index into ownedVbos array
*/
public void writeDrawRangeRestore(
int vboIndex, AccumulatedDraw.RestoreData restoreData
) {
ensureCapacity(28);
writeInt(GLCommand.DRAW_RANGE_RESTORE);
writeInt(vboIndex);
writeInt(restoreData.flags);
// Last color (4 bytes)
writeInt(restoreData.lastColor);
// Last normal (4 bytes)
writeInt(restoreData.lastNormal);
// Last texcoord (2 floats = 8 bytes)
writeFloat(restoreData.lastTexCoordU);
writeFloat(restoreData.lastTexCoordV);
}
public void writeCallList(int listId) {
ensureCapacity(8);
writeInt(GLCommand.CALL_LIST);
writeInt(listId);
}
public void writeBindVBO(int vbo) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VBO);
writeInt(vbo);
}
public void writeBindVAO(int vao) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VAO);
writeInt(vao);
}
public void writeDrawBuffer(int mode) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_BUFFER);
writeInt(mode);
}
private static final int MAX_DRAW_BUFFERS = 8;
// cmd (4) + count (4) + buffers (4 * MAX_DRAW_BUFFERS)
private static final int DRAW_BUFFERS_SIZE = 4 + 4 + 4 * MAX_DRAW_BUFFERS;
public void writeDrawBuffers(int count, int buffer) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
writeInt(buffer);
for (int i = 1; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
public void writeDrawBuffers(int count, IntBuffer bufs) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
final int pos = bufs.position();
for (int i = 0; i < count && i < MAX_DRAW_BUFFERS; i++) {
writeInt(bufs.get(pos + i));
}
for (int i = count; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
// === Complex object reference ===
public void writeComplexCommand(DisplayListCommand cmd) {
ensureCapacity(8);
final int index = complexObjects.size();
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
}
// === Lifecycle ===
public int size() {
return (int) (writePointer - basePointer);
}
public void delete() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
complexObjects.clear();
if (buffer != null) {
memFree(buffer);
buffer = null;
}
}
/**
* Deletes every allocated object & returns a ByteBuffer with the capacity equal to its limit.
* This CommandRecorder object cannot be used after calling this anymore
*/
public ByteBuffer finish() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
final int size = size();
buffer.limit(size);
final ByteBuffer out = memRealloc(buffer, size);
buffer = null;
complexObjects.clear();
return out;
}
public DisplayListCommand[] getComplexObjects() {
return complexObjects.toArray(new DisplayListCommand[complexObjects.size()]);
}
public DisplayListCommand getComplexObject(int index) {
return complexObjects.get(index);
}
public boolean isEmpty() {
return size() == 0;
}
/**
* Clear the buffer for reuse. Does NOT free memory.
*/
public void clear() {
writePointer = basePointer;
complexObjects.clear();
}
}
@@ -1,165 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
/**
* Represents a compiled display list.
*
* <p>Contains commands serialized to a direct ByteBuffer for off-heap storage,
* with complex objects (TexImage2DCmd, TexSubImage2DCmd) stored separately.
*
* <p>The ownedVbos array contains VBOs that are referenced by DrawRange commands.
*/
public final class CompiledDisplayList {
/**
* Empty display list singleton. Per OpenGL spec, an empty list is still valid.
* This instance does nothing when rendered and is never deleted (shared singleton).
*/
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList());
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
this.complexObjects = complexObjects;
this.ownedVbos = ownedVbos;
this.indexedBatches = indexedBatches;
}
/**
* Render this display list by executing all commands.
*/
public void render() {
render(-1);
}
public void render(int list) {
if (commandBuffer != null && commandBuffer.limit() > 0) {
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos, list, this);
}
}
/**
* Delete all resources held by this display list.
*/
public void delete() {
if (complexObjects != null) {
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
}
// Close VBOs from immediate-mode DisplayListVBOBuilder.
if (ownedVbos != null) {
ownedVbos.delete();
}
// Release shared (VAO, VBO, EBO) triples from IndexedDrawBatchBuilder.
if (indexedBatches != null) {
for (IndexedDrawBatch batch : indexedBatches) {
batch.delete();
}
}
// Free the command buffer (off-heap memory)
if (commandBuffer != null) {
memFree(commandBuffer);
}
}
/**
* Get the command buffer for debugging/inspection.
*/
public ByteBuffer getCommandBuffer() {
return commandBuffer;
}
/**
* Get complex objects for inlining into another display list (nested lists).
*/
public DisplayListCommand[] getComplexObjects() {
return complexObjects;
}
public DisplayListVBO getOwnedVbos() {
return ownedVbos;
}
public List<IndexedDrawBatch> getIndexedBatches() {
return indexedBatches;
}
// === Test inspection methods ===
/**
* Get counts of each command type in the buffer.
* Used for testing to verify optimization results.
*/
public Int2IntMap getCommandCounts() {
final Int2IntMap counts = new Int2IntOpenHashMap();
if (commandBuffer == null || commandBuffer.limit() == 0) {
return counts;
}
long ptr = memAddress(commandBuffer);
final long end = ptr + commandBuffer.limit();
while (ptr < end) {
final int cmd = memGetInt(ptr);
counts.mergeInt(cmd, 1, Integer::sum);
ptr += GLCommand.getCommandSize(cmd, ptr);
}
return counts;
}
/**
* Get the sequence of command opcodes in order.
* Used for testing to verify command ordering.
*/
public IntList getCommandOpcodes() {
final IntList opcodes = new IntArrayList();
if (commandBuffer == null || commandBuffer.limit() == 0) {
return opcodes;
}
long ptr = memAddress(commandBuffer);
final long end = ptr + commandBuffer.limit();
while (ptr < end) {
final int cmd = memGetInt(ptr);
opcodes.add(cmd);
ptr += GLCommand.getCommandSize(cmd, ptr);
}
return opcodes;
}
public String getCompiledDisplayListString(int listId, StackTraceElement[] stackTrace) {
return DisplayListManager.getCompiledDisplayListString(listId, this, stackTrace);
}
public String toString(int listId, StackTraceElement[] stackTrace) {
return getCompiledDisplayListString(listId, stackTrace);
}
public String toString(int listId) {
return toString(listId, null);
}
@Override
public String toString() {
return toString(0);
}
}
@@ -1,81 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
/**
* A class that stores multiple vertex formats & their corresponding buffers.
*/
public final class DisplayListVBO {
private final SubVBO[] vbos;
DisplayListVBO(SubVBO[] vbos) {
this.vbos = vbos;
}
public void delete() {
for (SubVBO vbo : vbos) {
vbo.delete();
}
}
public SubVBO[] getVBOs() {
return vbos;
}
public SubVBO getVBO(int index) {
return vbos[index];
}
public void render(int index) {
vbos[index].render();
}
public static final class SubVBO {
private final IVertexArrayObject vao;
private final int drawMode;
private final int start;
private final int count;
private final int vertexFlags;
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count, int vertexFlags) {
this.vao = vao;
this.drawMode = drawMode;
this.start = start;
this.count = count;
this.vertexFlags = vertexFlags;
}
public int getStart() {
return start;
}
public int getCount() {
return count;
}
public int getDrawMode() {
return drawMode;
}
public void delete() {
vao.delete();
}
public void render() {
if (vao == null) return;
vao.bind();
if (count <= 0) { vao.unbind(); return; }
VAOManager.setCurrentVertexFlags(vertexFlags);
vao.draw(drawMode, start, count);
vao.unbind();
}
public IVertexArrayObject getVAO() {
return vao;
}
}
}
@@ -1,138 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexedVAO;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.nmemFree;
/**
* Compiles multiple ByteBuffers of the same Vertex format into a VBO. Any buffer added to the builder will be freed afterward.
*/
public final class DisplayListVBOBuilder {
// Removes the need for a HashMap. Since it's only an array of 16 values, this should be faster
private final List<FormatData>[] formats = new List[VertexFlags.BITSET_SIZE];
private int count;
public DisplayListVBOBuilder addDraw(VertexFormat format, int drawMode, List<ByteBuffer> buffers) {
List<FormatData> data = formats[format.getVertexFlags()];
if (data == null) {
data = new ArrayList<>();
formats[format.getVertexFlags()] = data;
}
data.add(new FormatData(buffers, drawMode, count));
count++;
return this;
}
public DisplayListVBOBuilder addDraws(List<AccumulatedDraw> draws) {
for (AccumulatedDraw draw : draws) {
addDraw(draw.format, draw.drawMode, draw.drawBuffers);
}
return this;
}
public DisplayListVBO build() {
final DisplayListVBO.SubVBO[] vbos = new DisplayListVBO.SubVBO[count];
final ArrayList<ByteBuffer> allBuffers = new ArrayList<>();
for (int i = 0; i < VertexFlags.BITSET_SIZE; i++) {
List<FormatData> formatData = formats[i];
if (formatData == null) continue;
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[i];
int start = 0;
final IVertexArrayObject vao = VAOManager.createStorageVAO(format, -1, 0); // drawMode will be ignored
final IVertexBuffer vbo = vao.getVBO();
for (FormatData data : formatData) {
int vertexCount;
final List<ByteBuffer> drawBuffers = data.buffers;
if (drawBuffers.size() == 1) {
final ByteBuffer buffer = drawBuffers.get(0);
allBuffers.add(buffer);
vertexCount = format.getVertexCount(buffer);
} else {
allBuffers.addAll(drawBuffers);
int size = 0;
for (ByteBuffer buffer : drawBuffers) {
size += buffer.limit();
}
vertexCount = format.getVertexCount(size);
}
final int flags = format.getVertexFlags();
if (data.drawMode == GL11.GL_QUADS) {
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6, flags);
} else if (data.drawMode == GL11.GL_QUAD_STRIP) {
// GL_QUAD_STRIP is removed in core profile; an even-length GL_TRIANGLE_STRIP produces the same quads
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_STRIP, start, vertexCount & ~1, flags);
} else if (data.drawMode == GL11.GL_POLYGON) {
// GL_POLYGON is removed in core profile; convert to GL_TRIANGLE_FAN which is equivalent for convex polygons
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_FAN, start, vertexCount, flags);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount, flags);
}
start += vertexCount;
}
ByteBuffer bigBuffer = mergeAndDelete(allBuffers);
vbo.allocate(bigBuffer, start);
memFree(bigBuffer);
allBuffers.clear();
}
return new DisplayListVBO(vbos);
}
private static ByteBuffer mergeAndDelete(List<ByteBuffer> buffers) {
if (buffers.size() == 1) {
return buffers.get(0);
}
int needed = 0;
for (ByteBuffer buffer : buffers) {
needed += buffer.remaining();
}
ByteBuffer out = memAlloc(needed);
long dst = memAddress0(out);
for (ByteBuffer buffer : buffers) {
final int len = buffer.limit();
final long address = memAddress0(buffer);
memCopy(address, dst, len);
dst += len;
nmemFree(address);
}
out.limit(needed);
return out;
}
private static final class FormatData {
private final List<ByteBuffer> buffers;
private final int drawMode;
private final int drawIndex;
public FormatData(List<ByteBuffer> buffers, int drawMode, int drawIndex) {
this.buffers = buffers;
this.drawMode = drawMode;
this.drawIndex = drawIndex;
}
}
}
@@ -1,252 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
/**
* Command opcodes for ByteBuffer-based display list serialization.
* Each command has a fixed layout described in comments.
* All values are 4-byte aligned.
*/
public final class GLCommand {
private GLCommand() {}
// === Single int commands (8 bytes: opcode + param) ===
public static final int ENABLE = 1; // [cmd:4][cap:4]
public static final int DISABLE = 2; // [cmd:4][cap:4]
public static final int CLEAR = 3; // [cmd:4][mask:4]
public static final int CLEAR_STENCIL = 4; // [cmd:4][s:4]
public static final int CULL_FACE = 5; // [cmd:4][mode:4]
public static final int DEPTH_FUNC = 6; // [cmd:4][func:4]
public static final int SHADE_MODEL = 7; // [cmd:4][mode:4]
public static final int LOGIC_OP = 8; // [cmd:4][opcode:4]
public static final int MATRIX_MODE = 9; // [cmd:4][mode:4]
public static final int ACTIVE_TEXTURE = 10; // [cmd:4][texture:4]
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
// === Two int commands (12 bytes) ===
public static final int BIND_TEXTURE = 20; // [cmd:4][target:4][texture:4]
public static final int POLYGON_MODE = 21; // [cmd:4][face:4][mode:4]
public static final int COLOR_MATERIAL = 22; // [cmd:4][face:4][mode:4]
public static final int LINE_STIPPLE = 23; // [cmd:4][factor:4][pattern:4]
public static final int STENCIL_MASK_SEPARATE = 24; // [cmd:4][face:4][mask:4]
public static final int FOGI = 25; // [cmd:4][pname:4][param:4]
public static final int HINT = 26; // [cmd:4][target:4][mode:4]
// === Three int commands (16 bytes) ===
public static final int STENCIL_FUNC = 30; // [cmd:4][func:4][ref:4][mask:4]
public static final int STENCIL_OP = 31; // [cmd:4][fail:4][zfail:4][zpass:4]
public static final int TEX_PARAMETERI = 32; // [cmd:4][target:4][pname:4][param:4]
// === Four int commands (20 bytes) ===
public static final int VIEWPORT = 40; // [cmd:4][x:4][y:4][width:4][height:4]
public static final int BLEND_FUNC = 41; // [cmd:4][srcRgb:4][dstRgb:4][srcAlpha:4][dstAlpha:4]
public static final int COLOR_MASK = 42; // [cmd:4][r:4][g:4][b:4][a:4] (0 or 1 each)
public static final int STENCIL_FUNC_SEPARATE = 43; // [cmd:4][face:4][func:4][ref:4][mask:4]
public static final int STENCIL_OP_SEPARATE = 44; // [cmd:4][face:4][sfail:4][dpfail:4][dppass:4]
public static final int SCISSOR = 45; // [cmd:4][x:4][y:4][width:4][height:4]
// === Float commands ===
public static final int POINT_SIZE = 50; // [cmd:4][size:4f]
public static final int LINE_WIDTH = 51; // [cmd:4][width:4f]
public static final int POLYGON_OFFSET = 52; // [cmd:4][factor:4f][units:4f]
public static final int NORMAL = 53; // [cmd:4][x:4f][y:4f][z:4f]
public static final int COLOR = 54; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
public static final int FOGF = 61; // [cmd:4][pname:4][param:4f]
public static final int LIGHTF = 62; // [cmd:4][light:4][pname:4][param:4f]
public static final int LIGHT_MODELF = 63; // [cmd:4][pname:4][param:4f]
public static final int LIGHTI = 64; // [cmd:4][light:4][pname:4][param:4]
public static final int LIGHT_MODELI = 65; // [cmd:4][pname:4][param:4]
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
// === Buffer commands (inline 4 floats max) ===
public static final int FOG = 90; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
public static final int LIGHT = 91; // [cmd:4][light:4][pname:4][count:4][params:16f] = 32 bytes
public static final int LIGHT_MODEL = 92; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
public static final int MATERIAL = 93; // [cmd:4][face:4][pname:4][count:4][params:16f] = 32 bytes
public static final int CLIP_PLANE = 94; // [cmd:4][plane:4][a:8d][b:8d][c:8d][d:8d] = 40 bytes
// === Draw commands ===
public static final int DRAW_RANGE = 100; // [cmd:4][vboIndex:4] = 8 bytes
public static final int DRAW_RANGE_RESTORE = 101; // [cmd:4][vboIndex:4][flags:4][color:4][normal:4][lastTexCoord:8f] = 28 bytes
public static final int CALL_LIST = 102; // [cmd:4][listId:4] = 8 bytes
public static final int DRAW_BUFFER = 112; // [cmd:4][mode:4] = 8 bytes
public static final int DRAW_BUFFERS = 113; // [cmd:4][count:4][bufs:4*8] = 40 bytes (up to 8 buffers)
/**
* Draw VBO range with attribute restoration after draw.
* Used for immediate mode VBOs to restore GL current state (color, normal, texcoord).
*/
// === Bind commands ===
public static final int BIND_VBO = 120;
public static final int BIND_VAO = 121;
// === Complex object reference ===
public static final int COMPLEX_REF = 255; // [cmd:4][index:4] = 8 bytes
/**
* Get a human-readable name for an opcode (for debugging).
*/
public static String getName(int opcode) {
return switch (opcode) {
case ENABLE -> "ENABLE";
case DISABLE -> "DISABLE";
case CLEAR -> "CLEAR";
case CLEAR_STENCIL -> "CLEAR_STENCIL";
case CULL_FACE -> "CULL_FACE";
case DEPTH_FUNC -> "DEPTH_FUNC";
case SHADE_MODEL -> "SHADE_MODEL";
case LOGIC_OP -> "LOGIC_OP";
case MATRIX_MODE -> "MATRIX_MODE";
case ACTIVE_TEXTURE -> "ACTIVE_TEXTURE";
case USE_PROGRAM -> "USE_PROGRAM";
case PUSH_ATTRIB -> "PUSH_ATTRIB";
case POP_ATTRIB -> "POP_ATTRIB";
case LOAD_IDENTITY -> "LOAD_IDENTITY";
case PUSH_MATRIX -> "PUSH_MATRIX";
case POP_MATRIX -> "POP_MATRIX";
case STENCIL_MASK -> "STENCIL_MASK";
case DEPTH_MASK -> "DEPTH_MASK";
case FRONT_FACE -> "FRONT_FACE";
case BIND_TEXTURE -> "BIND_TEXTURE";
case POLYGON_MODE -> "POLYGON_MODE";
case COLOR_MATERIAL -> "COLOR_MATERIAL";
case LINE_STIPPLE -> "LINE_STIPPLE";
case STENCIL_MASK_SEPARATE -> "STENCIL_MASK_SEPARATE";
case FOGI -> "FOGI";
case HINT -> "HINT";
case STENCIL_FUNC -> "STENCIL_FUNC";
case STENCIL_OP -> "STENCIL_OP";
case TEX_PARAMETERI -> "TEX_PARAMETERI";
case VIEWPORT -> "VIEWPORT";
case SCISSOR -> "SCISSOR";
case BLEND_FUNC -> "BLEND_FUNC";
case COLOR_MASK -> "COLOR_MASK";
case STENCIL_FUNC_SEPARATE -> "STENCIL_FUNC_SEPARATE";
case STENCIL_OP_SEPARATE -> "STENCIL_OP_SEPARATE";
case POINT_SIZE -> "POINT_SIZE";
case LINE_WIDTH -> "LINE_WIDTH";
case POLYGON_OFFSET -> "POLYGON_OFFSET";
case COLOR -> "COLOR";
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
case LIGHT_MODELF -> "LIGHT_MODELF";
case LIGHTI -> "LIGHTI";
case LIGHT_MODELI -> "LIGHT_MODELI";
case MATERIALF -> "MATERIALF";
case TEX_PARAMETERF -> "TEX_PARAMETERF";
case TRANSLATE -> "TRANSLATE";
case SCALE -> "SCALE";
case CLEAR_DEPTH -> "CLEAR_DEPTH";
case MULT_MATRIX -> "MULT_MATRIX";
case LOAD_MATRIX -> "LOAD_MATRIX";
case FOG -> "FOG";
case LIGHT -> "LIGHT";
case LIGHT_MODEL -> "LIGHT_MODEL";
case MATERIAL -> "MATERIAL";
case CLIP_PLANE -> "CLIP_PLANE";
case DRAW_RANGE -> "DRAW_RANGE";
case CALL_LIST -> "CALL_LIST";
case DRAW_BUFFER -> "DRAW_BUFFER";
case DRAW_BUFFERS -> "DRAW_BUFFERS";
case COMPLEX_REF -> "COMPLEX_REF";
case DRAW_RANGE_RESTORE -> "DRAW_RANGE_RESTORE";
case BIND_VAO -> "BIND_VAO";
case BIND_VBO -> "BIND_VBO";
default -> "UNKNOWN(" + opcode + ")";
};
}
/**
* Get size of a command in bytes (including the opcode).
*/
public static int getCommandSize(int cmd, long ptr) {
return switch (cmd) {
// Opcode-only commands (4 bytes)
case GLCommand.LOAD_IDENTITY, GLCommand.PUSH_MATRIX, GLCommand.POP_MATRIX -> 4;
// Single int commands (8 bytes)
case GLCommand.ENABLE, GLCommand.DISABLE, GLCommand.CLEAR, GLCommand.CLEAR_STENCIL,
GLCommand.CULL_FACE, GLCommand.DEPTH_FUNC, GLCommand.SHADE_MODEL, GLCommand.LOGIC_OP,
GLCommand.MATRIX_MODE, GLCommand.ACTIVE_TEXTURE, GLCommand.USE_PROGRAM,
GLCommand.PUSH_ATTRIB, GLCommand.POP_ATTRIB, GLCommand.STENCIL_MASK,
GLCommand.DEPTH_MASK, GLCommand.FRONT_FACE, GLCommand.POINT_SIZE, GLCommand.LINE_WIDTH,
GLCommand.CALL_LIST, GLCommand.COMPLEX_REF, GLCommand.DRAW_RANGE, GLCommand.BIND_VBO,
GLCommand.BIND_VAO -> 8;
// Two int commands (12 bytes)
case GLCommand.BIND_TEXTURE, GLCommand.POLYGON_MODE, GLCommand.COLOR_MATERIAL,
GLCommand.LINE_STIPPLE, GLCommand.STENCIL_MASK_SEPARATE, GLCommand.FOGI,
GLCommand.HINT, GLCommand.POLYGON_OFFSET, GLCommand.ALPHA_FUNC, GLCommand.FOGF,
GLCommand.LIGHT_MODELF, GLCommand.LIGHT_MODELI -> 12;
// Three int commands (16 bytes)
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
GLCommand.LIGHTF, GLCommand.LIGHTI,
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
// Four int commands (20 bytes)
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
GLCommand.STENCIL_FUNC_SEPARATE, GLCommand.STENCIL_OP_SEPARATE,
GLCommand.SCISSOR,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
// DRAW_RANGE_RESTORE: 28 bytes
case GLCommand.DRAW_RANGE_RESTORE -> 28;
// DRAW_BUFFER: 8 bytes [cmd:4][mode:4]
case GLCommand.DRAW_BUFFER -> 8;
// DRAW_BUFFERS: 40 bytes [cmd:4][count:4][bufs:4*8]
case GLCommand.DRAW_BUFFERS -> 40;
// Double commands
case GLCommand.TRANSLATE, GLCommand.SCALE -> 16; // cmd + 3 floats
case GLCommand.CLEAR_DEPTH -> 12; // cmd + 1 double
// Matrix commands
case GLCommand.MULT_MATRIX, GLCommand.LOAD_MATRIX -> 68; // cmd + 16 floats
// Buffer commands (variable, read count)
case GLCommand.FOG, GLCommand.LIGHT_MODEL -> {
final int count = memGetInt(ptr + 8); // pname at +4, count at +8
yield 12 + count * 4;
}
case GLCommand.LIGHT, GLCommand.MATERIAL -> {
final int count = memGetInt(ptr + 12); // light/face at +4, pname at +8, count at +12
yield 16 + count * 4;
}
case GLCommand.CLIP_PLANE -> 40; // cmd + plane + 4 doubles
default -> throw new IllegalStateException("Unknown command: " + cmd);
};
}
}
Loaded 100 of 1961 files, more files were not shown because too many files have changed in this diff. Show more