SDL GPU backend, TESR Batching/caching API, Tracy Integration (#1993)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-08-08 15:53:58 -07:00
1 parent 413df76ad4
commit 8da561d3b6
647 files changed
+60152 -5331

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
#!/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
@@ -0,0 +1,92 @@
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 :tracy-client:publish
+4
View File
@@ -1,4 +1,5 @@
.gradle
.kotlin/
.settings
/.idea/
/.vs/
@@ -38,3 +39,6 @@ addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
local/
tracy-client/src/main/resources/natives/
dist/tracy-natives/
*.tracy
+17
View File
@@ -0,0 +1,17 @@
# 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
+181 -29
View File
@@ -8,33 +8,120 @@ plugins {
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.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
// extraRunJvmArguments.add("-Dangelica.useVulkan=true")
// extraRunJvmArguments.add("-Dangelica.vulkanDebug=true")
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
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.redirectorLogspam=true")
//extraRunJvmArguments.add("-Dangelica.debug.redirectorLogspam=true")
}
tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
// On macOS ARM64, use an ARM64-compatible JVM for tests
val isMacOsArm64 = System.getProperty("os.name").lowercase().contains("mac") && System.getProperty("os.arch") == "aarch64"
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (isMacOsArm64) {
vendor = JvmVendorSpec.AZUL
// Ensure flyby args are passed 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.flyby.")) {
jvmArgs("-D$name=$value")
}
}
}
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")) {
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>") }
}
}
}
dependsOn(tasks.extractNatives2)
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
}
tasks.processResources {
@@ -57,6 +144,7 @@ val shadowImplementation: Configuration by configurations
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = embedOnly
outputs.dir(temporaryDir)
}
val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
@@ -64,7 +152,7 @@ val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
from(shadowImplementation.map(::zipTree))
exclude("module-info.class")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier = "shadow-stripped"
archiveFileName = "shadow-stripped.jar"
}
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
@@ -72,15 +160,74 @@ val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
outputs.dir(temporaryDir)
}
tasks.test {
dependsOn(downgradeEmbedOnlyForTest, downgradeShadowImplForTest)
classpath = classpath
.minus(embedOnly)
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
.minus(shadowImplementation)
.plus(files({ fileTree(downgradeShadowImplForTest.get().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()
}
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.check { dependsOn(glCoreTest) }
tasks.shadowJar {
dependsOn(embedOnly)
from(embedOnly.map(::zipTree))
@@ -89,13 +236,18 @@ tasks.shadowJar {
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")
}
+13
View File
@@ -0,0 +1,13 @@
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))
}
+5
View File
@@ -0,0 +1,5 @@
dependencyResolutionManagement {
versionCatalogs {
create("libs") { from(files("../gradle/libs.versions.toml")) }
}
}
-189
View File
@@ -1,189 +0,0 @@
/*
* 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)
testRuntimeClasspath.exclude group: "com.github.GTNewHorizons", module: "lwjgl3ify"
// Embed in JAR without relocation (compile only, shadowed into JAR)
embedOnly
embedOnly.canBeConsumed = false
embedOnly.canBeResolved = true
compileClasspath.extendsFrom(embedOnly)
testCompileClasspath.extendsFrom(embedOnly)
testRuntimeClasspath.extendsFrom(embedOnly)
}
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
configurations.all {
exclude group: "com.github.GTNewHorizons", module: "Angelica"
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.158:dev")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.1.0") { transitive = false }
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.42")
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.11.19:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
shadowImplementation("net.minecraftforge:eventbus:7.0-beta.15")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-33.g3ec381e") {
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"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.8-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
// Because who doesn't want NEI
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7: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-749-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.14-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.54:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.0:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.4-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.12-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.12-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.2.13:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.6:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.9.7:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.15-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.10-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.8-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.2-GTNH:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.13:dev") { transitive = false }
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.6-GTNH:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.8.9:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.4:dev")
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { 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.10: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.3-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.1-GTNH:dev")
// Distant Horizons
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
afterEvaluate {
dependencies {
testImplementation(platform('org.junit:junit-bom:5.14.1'))
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation("org.mockito:mockito-core:3.+")
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
testImplementation('org.junit.platform:junit-platform-engine')
testImplementation('org.junit.platform:junit-platform-reporting')
testImplementation('org.junit.platform:junit-platform-launcher')
testImplementation('com.tngtech.archunit:archunit-junit5:1.4.1')
testRuntimeOnly("it.unimi.dsi:fastutil:8.5.18")
testRuntimeOnly("org.joml:joml:1.10.8")
}
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { transitive = false }
}
+209
View File
@@ -0,0 +1,209 @@
/*
* 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)
}
}
+115 -33
View File
@@ -2,6 +2,7 @@ import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
`java-library`
`java-test-fixtures`
`maven-publish`
}
@@ -9,6 +10,8 @@ apply(plugin = "xyz.wagyourtail.jvmdowngrader")
version = rootProject.version
val lwjglNatives: String by extra
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
withSourcesJar()
@@ -20,7 +23,7 @@ val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation",
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
configurations {
listOf("apiElements", "runtimeElements").forEach { name ->
listOf("apiElements", "runtimeElements", "testFixturesApiElements", "testFixturesRuntimeElements").forEach { name ->
named(name) {
attributes {
attribute(rfgObfAttr, "mcp")
@@ -31,7 +34,7 @@ configurations {
}
// 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.
// Not included in the published jar - real MC classes are on the classpath at runtime.
sourceSets {
create("stubs") {
java.srcDir("src/stubs/java")
@@ -65,6 +68,16 @@ repositories {
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()
}
@@ -74,42 +87,62 @@ dependencies {
compileOnly(sourceSets["stubs"].output)
// LWJGL
compileOnly("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
compileOnly("org.lwjgl.lwjgl:lwjgl_util:${property("lwjglVersion")}")
compileOnly(libs.lwjgl2)
compileOnly(libs.lwjgl2.util)
compileOnly("org.ow2.asm:asm-tree:5.0.3")
compileOnly(libs.asm.tree.compile)
// Our Deps
api("com.github.GTNewHorizons:GTNHLib:${property("gtnhlibVersion")}:dev")
api("net.minecraftforge:eventbus:${property("eventbusVersion")}")
api(libs.gtnhlib) { artifact { classifier = "dev" } }
api(libs.eventbus)
compileOnly("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
implementation("org.taumc:glsl-transformation-lib:${property("glslTransformLibVersion")}") { exclude(module = "antlr4") }
implementation("org.antlr:antlr4-runtime:${property("antlr4RuntimeVersion")}")
implementation("org.anarres:jcpp:${property("jcppVersion")}")
compileOnly(libs.celeritas.common) { isTransitive = false }
api(libs.glsl.transformation.lib) { exclude(module = "antlr4") }
api(libs.antlr4.runtime)
implementation(libs.jcpp)
compileOnly("org.projectlombok:lombok:${property("lombokVersion")}") { isTransitive = false }
annotationProcessor("org.projectlombok:lombok:${property("lombokVersion")}")
compileOnly("org.jetbrains:annotations:${property("jetbrainsAnnotationsVersion")}")
compileOnly("org.apache.logging.log4j:log4j-api:${property("log4jVersion")}")
// 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("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("it.unimi.dsi:fastutil:${property("fastutilVersion")}")
testRuntimeOnly("org.joml:joml:${property("jomlVersion")}") { isTransitive = false }
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("org.junit:junit-bom:${property("junitBomVersion")}"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.ow2.asm:asm-tree:9.7")
testImplementation("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.apache.logging.log4j:log4j-core:${property("log4jVersion")}")
testRuntimeOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:${property("celeritasVersion")}") { isTransitive = false }
testRuntimeOnly("xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:${property("jvmDowngraderVersion")}:downgraded-8")
testRuntimeOnly("org.apache.commons:commons-lang3:${property("commonsLang3Version")}")
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 {
@@ -118,8 +151,8 @@ val depsToDowngrade by configurations.creating {
}
dependencies {
depsToDowngrade("net.minecraftforge:eventbus:${property("eventbusVersion")}")
depsToDowngrade("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
depsToDowngrade(libs.eventbus)
depsToDowngrade(libs.celeritas.common) { isTransitive = false }
}
tasks.withType<DowngradeFiles>().configureEach {
@@ -136,9 +169,18 @@ val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
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
@@ -161,7 +203,7 @@ fun Test.configureGlsmJava8() {
}
}
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeDepsForTest)
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeTestFixturesClasses, downgradeDepsForTest)
val mainClassesDirs = sourceSets["main"].output.classesDirs
.plus(sourceSets["stubs"].output.classesDirs)
@@ -175,7 +217,9 @@ fun Test.configureGlsmJava8() {
classpath = downgradedTest
.plus(downgradedMain)
.plus(downgradedDeps)
.plus(sourceSets["test"].runtimeClasspath.minus(mainClassesDirs).minus(testClassesDirs).minus(depsToDowngrade))
.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)
@@ -184,7 +228,10 @@ fun Test.configureGlsmJava8() {
tasks.test {
useJUnitPlatform {
excludeTags = GL_TASK_TAGS.values.toSet()
excludeTags = GL_TASK_TAGS.values.toSet() + "lwjgl3"
}
filter {
excludeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
}
configureGlsmJava8()
}
@@ -225,6 +272,41 @@ val verifyTestsRan by tasks.registering {
}
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") {
-16
View File
@@ -1,16 +0,0 @@
lwjglVersion = 2.9.4-nightly-20150209
eventbusVersion = 7.0-beta.15
celeritasVersion = 2.5.8-GTNH
lombokVersion = 1.18.42
log4jVersion = 2.0-beta9
guavaVersion = 18.0
gtnhlibVersion = 0.11.19
fastutilVersion = 8.5.18
jomlVersion = 1.10.8
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
antlr4RuntimeVersion = 4.13.2
jcppVersion = 1.4.14
jetbrainsAnnotationsVersion = 26.0.2
junitBomVersion = 5.14.1
jvmDowngraderVersion = 1.3.5
commonsLang3Version = 3.17.0
@@ -0,0 +1,122 @@
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 boolean USE_SDL_GPU = Boolean.getBoolean("angelica.sdlgpu.enable");
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
// 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() {}
}
@@ -0,0 +1,76 @@
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,8 +1,12 @@
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;
@@ -15,6 +19,7 @@ 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;
@@ -33,12 +38,12 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
* <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>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>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>
@@ -71,30 +76,59 @@ public class CompatShaderTransformer {
"gl_NormalMatrix", "angelica_NormalMatrix"
);
private static final Path DUMP_DIR;
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
static {
DUMP_DIR = Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false")) ? Paths.get("compat_shaders") : null;
}
private static final int CACHE_SIZE = 32;
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<>(32, 0.75f, true) {
new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
return size() > CACHE_SIZE;
}
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();
}
/**
* Transform a mod shader source for core profile compatibility.
*/
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) {
@@ -103,17 +137,27 @@ public class CompatShaderTransformer {
final CacheKey key = new CacheKey(source, isFragment);
final String cached = cache.get(key);
if (cached != null) {
dumpShader(source, cached, isFragment, needsTransform);
return cached;
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 = 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);
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);
@@ -144,24 +188,24 @@ public class CompatShaderTransformer {
final int targetVersion = Math.max(declaredVersion, RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330);
// Pre-parse reserved word renaming — prevents ANTLR parse failures
// Pre-parse reserved word renaming - prevents ANTLR parse failures
source = GlslTransformUtils.replaceTexture(source);
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source);
final Transformer transformer = new Transformer(parsedShader.full());
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
// 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)
// 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;"
@@ -172,7 +216,7 @@ public class CompatShaderTransformer {
transformer.rename("gl_LightSource", "angelica_LightSource");
}
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
// 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;"
@@ -181,7 +225,7 @@ public class CompatShaderTransformer {
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
}
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
// 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) {
@@ -199,7 +243,7 @@ public class CompatShaderTransformer {
}
}
// gl_TexCoord[N] varying array → per-index in/out declarations
// 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) {
@@ -226,7 +270,7 @@ public class CompatShaderTransformer {
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
final String versionDirective = "#version " + targetVersion + " core\n";
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsed.pre(), "")).replaceFirst("").trim();
// Preserve #define directives
final StringBuilder defines = new StringBuilder();
@@ -242,7 +286,7 @@ public class CompatShaderTransformer {
// Restore pre-parse renames
String output = GlslTransformUtils.restoreReservedWords(result.toString());
// Core profile: attribute → in, varying → out (vertex) / in (fragment)
// Core profile: attribute -> in, varying -> out (vertex) / in (fragment)
output = fixupQualifiers(output, isFragment);
return output;
@@ -310,7 +354,8 @@ public class CompatShaderTransformer {
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 = angelica_FogParameters("
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));");
}
@@ -485,18 +530,19 @@ public class CompatShaderTransformer {
}
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
if (DUMP_DIR == null) return;
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(DUMP_DIR);
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(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
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());
}
@@ -524,10 +570,10 @@ public class CompatShaderTransformer {
return source;
}
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
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.
@@ -548,22 +594,26 @@ public class CompatShaderTransformer {
final StringBuilder varyings = new StringBuilder();
final StringBuilder assignments = new StringBuilder();
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
sb.append("layout(location = 1) in vec4 a_Color;\n");
varyings.append("out vec4 angelica_FrontColor;\n");
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 Set<Integer> texCoordIndices = new HashSet<>();
final Map<Integer, String> texCoordTypes = new HashMap<>();
while (texCoordMatcher.find()) {
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
texCoordTypes.putIfAbsent(Integer.parseInt(texCoordMatcher.group(2)), texCoordMatcher.group(1));
}
for (int i : texCoordIndices) {
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 vec4 a_TexCoord").append(i).append(";\n");
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
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");
}
@@ -581,6 +631,23 @@ public class CompatShaderTransformer {
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);
@@ -7,6 +7,10 @@ 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;
@@ -173,10 +177,10 @@ public class CompatUniformManager {
final int[] locs = pu.locs;
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.mvGeneration;
final int mvLinearGen = GLStateManager.mvLinearGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
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;
@@ -190,14 +194,14 @@ public class CompatUniformManager {
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration;
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.lightingGeneration;
final int litGen = GLStateManager.getLightingGeneration();
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
@@ -207,7 +211,7 @@ public class CompatUniformManager {
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
final int cpGen = GLStateManager.getClipPlaneGeneration();
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizon.gtnhlib.client.model.NormalHelper;
import com.gtnewhorizon.gtnhlib.client.renderer.CallbackTessellator;
@@ -10,6 +12,8 @@ import com.gtnewhorizon.gtnhlib.client.renderer.tessellator.VertexTransformCallb
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VBOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
@@ -52,18 +56,15 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
*/
@UtilityClass
public class DisplayListManager {
// -Dangelica.debugDisplayLists: disable transform collapsing and draw merging
private static final boolean DEBUG_DISPLAY_LISTS;
private static final Tracy.ZoneId Z_GL_LIST_COMPILE = Tracy.zoneId("glListCompile", Tracy.COLOR_FFP);
private static final Tracy.ZoneId Z_GL_LIST_PLAYBACK = Tracy.zoneId("glListPlayback", Tracy.COLOR_FFP);
// -Dangelica.logDisplayListCompilation: log compiled display list commands
private static final boolean LOG_DISPLAY_LIST_COMPILATION;
// Tracy profiling counter
public static long listPlaybacks;
static {
DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debugDisplayLists");
LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.logDisplayListCompilation");
if (LOG_DISPLAY_LIST_COMPILATION) {
GLStateManager.LOGGER.warn("Display list compilation logging ENABLED (-Dangelica.logDisplayListCompilation=true)");
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
GLStateManager.LOGGER.warn("Display list compilation logging ENABLED (-Dangelica.debug.displayLists.compilation=true)");
}
}
@@ -460,7 +461,7 @@ public class DisplayListManager {
currentRecorder.writeHint(target, mode);
}
public static void recordFog(int pname, java.nio.FloatBuffer params) {
public static void recordFog(int pname, FloatBuffer params) {
drawBarrier();
currentRecorder.writeFog(pname, params);
}
@@ -475,7 +476,7 @@ public class DisplayListManager {
currentRecorder.writeLighti(light, pname, param);
}
public static void recordLight(int light, int pname, java.nio.FloatBuffer params) {
public static void recordLight(int light, int pname, FloatBuffer params) {
drawBarrier();
currentRecorder.writeLight(light, pname, params);
}
@@ -490,7 +491,7 @@ public class DisplayListManager {
currentRecorder.writeLightModeli(pname, param);
}
public static void recordLightModel(int pname, java.nio.FloatBuffer params) {
public static void recordLightModel(int pname, FloatBuffer params) {
drawBarrier();
currentRecorder.writeLightModel(pname, params);
}
@@ -500,7 +501,7 @@ public class DisplayListManager {
currentRecorder.writeMaterialf(face, pname, val);
}
public static void recordMaterial(int face, int pname, java.nio.FloatBuffer params) {
public static void recordMaterial(int face, int pname, FloatBuffer params) {
drawBarrier();
currentRecorder.writeMaterial(face, pname, params);
}
@@ -556,7 +557,7 @@ public class DisplayListManager {
currentRecorder.writeDrawBuffers(count, buf);
}
public static void recordDrawBuffers(int count, java.nio.IntBuffer bufs) {
public static void recordDrawBuffers(int count, IntBuffer bufs) {
drawBarrier();
currentRecorder.writeDrawBuffers(count, bufs);
}
@@ -623,7 +624,7 @@ public class DisplayListManager {
pendingTransformOps.add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z));
}
if (DEBUG_DISPLAY_LISTS) {
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix();
}
}
@@ -636,7 +637,7 @@ public class DisplayListManager {
pendingTransformOps.add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z));
}
if (DEBUG_DISPLAY_LISTS) {
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix();
}
}
@@ -654,7 +655,7 @@ public class DisplayListManager {
pendingTransformOps.add(String.format("glRotatef(%.4f, %.4f, %.4f, %.4f)", Math.toDegrees(rad), x, y, z));
}
if (DEBUG_DISPLAY_LISTS) {
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix();
}
}
@@ -676,7 +677,7 @@ public class DisplayListManager {
pendingTransformOps.add("glMultMatrixf(...)");
}
if (DEBUG_DISPLAY_LISTS) {
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix();
}
}
@@ -694,7 +695,7 @@ public class DisplayListManager {
pendingTransformOps.add(String.format("glOrtho(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
}
if (DEBUG_DISPLAY_LISTS) {
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix();
}
}
@@ -709,7 +710,7 @@ public class DisplayListManager {
pendingTransformOps.add(String.format("glFrustum(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
}
if (DEBUG_DISPLAY_LISTS) {
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix();
}
}
@@ -780,10 +781,10 @@ public class DisplayListManager {
currentRecorder = new CommandRecorder(); // Create command recorder
accumulatedDraws = new ArrayList<>(8); // Fewer draws than commands typically
transformCallback = new DisplayListCallback();
compilationStackTrace = LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
// Initialize debug logging fields (only when logging enabled)
if (LOG_DISPLAY_LIST_COMPILATION) {
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>();
multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>();
@@ -804,9 +805,11 @@ public class DisplayListManager {
GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!");
return;
}
if (Tracy.ENABLED) Tracy.beginZone(Z_GL_LIST_COMPILE);
try {
finishCurrentList();
} finally {
if (Tracy.ENABLED) Tracy.endZone();
// Non-null here means finishCurrentList threw before transferring or releasing
// ownership of the recorder.
if (currentRecorder != null) {
@@ -888,7 +891,7 @@ public class DisplayListManager {
}
// Log compilation details if enabled (before context restoration changes glListId)
if (LOG_DISPLAY_LIST_COMPILATION) {
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
logCompiledDisplayList(glListId, compiled, compilationStackTrace);
}
}
@@ -989,7 +992,7 @@ public class DisplayListManager {
tessellator, copyLast
);
accumulatedDraws.add(pendingDraw);
if (DEBUG_DISPLAY_LISTS) {
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
drawBarrier();
}
}
@@ -1017,29 +1020,38 @@ public class DisplayListManager {
}
private static void executeDisplayList(int list) {
if (Tracy.ENABLED) {
listPlaybacks++;
Tracy.beginZone(Z_GL_LIST_PLAYBACK);
}
final boolean locked = GLStateManager.acquireDrawLock();
try {
final CompiledDisplayList compiled = displayListCache.get(list);
if (compiled != null) {
compiled.render(list);
return;
}
// Fall back to VBOManager or legacy display list
if (list < 0) {
// Negative IDs are VBOManager space (entity models, etc.)
final VertexBuffer vbo = VBOManager.get(list);
if (vbo != null) {
vbo.render();
}
// Per OpenGL spec: if list is undefined, glCallList has no effect
}
// An uncached positive-ID list = no-op (per OpenGL spec)
executeDisplayListInner(list);
} finally {
if (locked) GLStateManager.releaseDrawLock();
if (Tracy.ENABLED) Tracy.endZone();
}
}
private static void executeDisplayListInner(int list) {
final CompiledDisplayList compiled = displayListCache.get(list);
if (compiled != null) {
compiled.render(list);
return;
}
// Fall back to VBOManager or legacy display list
if (list < 0) {
// Negative IDs are VBOManager space (entity models, etc.)
final VertexBuffer vbo = VBOManager.get(list);
if (vbo != null) {
vbo.render();
}
// Per OpenGL spec: if list is undefined, glCallList has no effect
}
// An uncached positive-ID list = no-op (per OpenGL spec)
}
/**
* Delete display lists and free their VBO resources.
*/
@@ -76,29 +76,29 @@ public class Feature {
*/
private static void addTextureGenStates(Set<IStateStack<?>> collection) {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
collection.add(GLStateManager.textures.getTexGenSStates(i));
collection.add(GLStateManager.textures.getTexGenTStates(i));
collection.add(GLStateManager.textures.getTexGenRStates(i));
collection.add(GLStateManager.textures.getTexGenQStates(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.alphaTest // GL_ALPHA_TEST enable bit
, GLStateManager.alphaState // Alpha test function and reference value
, GLStateManager.blendMode // GL_BLEND enable bit
, GLStateManager.blendState // Blending source/destination functions, equation, blend color
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
, GLStateManager.ditherState // GL_DITHER enable bit
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
, GLStateManager.logicOpMode // Logic op function
, GLStateManager.colorMask // Color-mode and index-mode writemasks
, GLStateManager.clearColor // Color-mode and index-mode clear values
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.color // Current RGBA color
GLStateManager.getColor()
, ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates
// Current color index
@@ -110,78 +110,78 @@ public class Feature {
// GL_EDGE_FLAG flag
));
attribToFeatures.put(GL11.GL_DEPTH_BUFFER_BIT, ImmutableSet.of(
GLStateManager.depthTest // GL_DEPTH_TEST enable bit
, GLStateManager.depthState // Depth buffer test function
GLStateManager.getDepthTest()
, GLStateManager.getDepthState()
// Depth buffer clear value
// GL_DEPTH_WRITEMASK enable bit
));
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST flag
, GLStateManager.autoNormalState // GL_AUTO_NORMAL flag
, GLStateManager.blendMode // GL_BLEND flag
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP flag
, GLStateManager.colorMaterial // GL_COLOR_MATERIAL
, GLStateManager.cullState // GL_CULL_FACE flag
, GLStateManager.depthTest // GL_DEPTH_TEST flag
, GLStateManager.ditherState // GL_DITHER flag
, GLStateManager.fogMode // GL_FOG flag
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP flag
, GLStateManager.lightStates[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2
, GLStateManager.lightStates[3] // GL_LIGHT3
, GLStateManager.lightStates[4] // GL_LIGHT4
, GLStateManager.lightStates[5] // GL_LIGHT5
, GLStateManager.lightStates[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7
, GLStateManager.lightingState // GL_LIGHTING flag
, GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE flag
, GLStateManager.map1Color4State // GL_MAP1_COLOR_4
, GLStateManager.map1IndexState // GL_MAP1_INDEX
, GLStateManager.map1NormalState // GL_MAP1_NORMAL
, GLStateManager.map1TextureCoord1State // GL_MAP1_TEXTURE_COORD_1
, GLStateManager.map1TextureCoord2State // GL_MAP1_TEXTURE_COORD_2
, GLStateManager.map1TextureCoord3State // GL_MAP1_TEXTURE_COORD_3
, GLStateManager.map1TextureCoord4State // GL_MAP1_TEXTURE_COORD_4
, GLStateManager.map1Vertex3State // GL_MAP1_VERTEX_3
, GLStateManager.map1Vertex4State // GL_MAP1_VERTEX_4
, GLStateManager.map2Color4State // GL_MAP2_COLOR_4
, GLStateManager.map2IndexState // GL_MAP2_INDEX
, GLStateManager.map2NormalState // GL_MAP2_NORMAL
, GLStateManager.map2TextureCoord1State // GL_MAP2_TEXTURE_COORD_1
, GLStateManager.map2TextureCoord2State // GL_MAP2_TEXTURE_COORD_2
, GLStateManager.map2TextureCoord3State // GL_MAP2_TEXTURE_COORD_3
, GLStateManager.map2TextureCoord4State // GL_MAP2_TEXTURE_COORD_4
, GLStateManager.map2Vertex3State // GL_MAP2_VERTEX_3
, GLStateManager.map2Vertex4State // GL_MAP2_VERTEX_4
, GLStateManager.multisampleState // GL_MULTISAMPLE flag
, GLStateManager.normalizeState // GL_NORMALIZE flag
, GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
, GLStateManager.sampleAlphaToCoverageState // GL_SAMPLE_ALPHA_TO_COVERAGE flag
, GLStateManager.sampleAlphaToOneState // GL_SAMPLE_ALPHA_TO_ONE flag
, GLStateManager.sampleCoverageState // GL_SAMPLE_COVERAGE flag
, GLStateManager.scissorTest // GL_SCISSOR_TEST flag
, GLStateManager.stencilTest // GL_STENCIL_TEST flag
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.clipPlaneStates.length; i++) {
enableBits.add(GLStateManager.clipPlaneStates[i]);
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.textures.getTexture1DStates(i));
enableBits.add(GLStateManager.textures.getTextureUnitStates(i));
enableBits.add(GLStateManager.textures.getTexture3DStates(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
@@ -196,8 +196,8 @@ public class Feature {
// GL_AUTO_NORMAL enable bit
));
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
GLStateManager.fogMode // GL_FOG enable bit
, GLStateManager.fogState // Fog color
GLStateManager.getFogMode()
, GLStateManager.getFogState()
// ^^ Fog density
// ^^ Linear fog start
// ^^ Linear fog end
@@ -214,42 +214,42 @@ public class Feature {
// GL_TEXTURE_COMPRESSION_HINT setting
));
attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of(
GLStateManager.colorMaterial // GL_COLOR_MATERIAL enable bit
, GLStateManager.colorMaterialFace // GL_COLOR_MATERIAL_FACE value
, GLStateManager.colorMaterialParameter // Color material parameters that are tracking the current color
, GLStateManager.lightModel // Ambient scene color, GL_LIGHT_MODEL_LOCAL_VIEWER, GL_LIGHT_MODEL_TWO_SIDE
, GLStateManager.lightingState // GL_LIGHTING enable bit
GLStateManager.getColorMaterial()
, GLStateManager.getColorMaterialFace()
, GLStateManager.getColorMaterialParameter()
, GLStateManager.getLightModel()
, GLStateManager.getLightingState()
// Enable bit for each light
, GLStateManager.lightStates[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2
, GLStateManager.lightStates[3] // GL_LIGHT3
, GLStateManager.lightStates[4] // GL_LIGHT4
, GLStateManager.lightStates[5] // GL_LIGHT5
, GLStateManager.lightStates[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7
, 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.lightDataStates[0]
, GLStateManager.lightDataStates[1]
, GLStateManager.lightDataStates[2]
, GLStateManager.lightDataStates[3]
, GLStateManager.lightDataStates[4]
, GLStateManager.lightDataStates[5]
, GLStateManager.lightDataStates[6]
, GLStateManager.lightDataStates[7]
, 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.frontMaterial
, GLStateManager.backMaterial
, GLStateManager.shadeModelState // GL_SHADE_MODEL setting
, GLStateManager.getFrontMaterial()
, GLStateManager.getBackMaterial()
, GLStateManager.getShadeModelState()
));
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE enable bit
, GLStateManager.lineState // Line stipple pattern, repeat counter, and width
GLStateManager.getLineSmoothState()
, GLStateManager.getLineStippleState()
, GLStateManager.getLineState()
));
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
// GL_LIST_BASE setting
@@ -274,31 +274,31 @@ public class Feature {
// GL_READ_BUFFER setting
));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.pointState // Point size
GLStateManager.getPointSmoothState()
, GLStateManager.getPointState()
));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
GLStateManager.cullState // GL_CULL_FACE enable bit
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE enable bit
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonState // GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_OFFSET_FACTOR/UNITS
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.scissorTest // GL_SCISSOR_TEST enable bit
GLStateManager.getScissorTest()
// Scissor box
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.stencilTest // GL_STENCIL_TEST enable bit
, GLStateManager.stencilState // Stencil function, ref, mask, ops, writemask, clear value
GLStateManager.getStencilTest()
, GLStateManager.getStencilState()
));
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
GLStateManager.activeTextureUnit // Active texture unit
GLStateManager.getActiveTextureUnitStack()
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
// Enable bits for the four texture coordinates
@@ -315,8 +315,8 @@ public class Feature {
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.textures.getTexEnvState(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
@@ -326,14 +326,14 @@ public class Feature {
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
// Coefficients of the six clipping planes
// Enable bits for the user-definable clipping planes
GLStateManager.matrixMode
, GLStateManager.normalizeState // GL_NORMALIZE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
GLStateManager.getMatrixMode()
, GLStateManager.getNormalizeState()
, GLStateManager.getRescaleNormalState()
));
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
// Depth range (near and far)
GLStateManager.viewportState
GLStateManager.getViewportState()
));
}
@@ -151,7 +151,7 @@ public class FeedbackManager {
computeMVP();
final int stride = positionStride();
final int elementSize = VAOManager.Attrib.glTypeSizeBytes(type);
final int elementSize = GLTypes.sizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
@@ -0,0 +1,198 @@
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.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
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 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);
}
}
}
@@ -6,6 +6,8 @@
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;
@@ -14,6 +16,10 @@ 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 {
/**
@@ -252,25 +258,6 @@ public final class GLDebug {
};
}
public static String getDataTypeName(int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
case GL11.GL_BYTE -> "BYTE";
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
case GL11.GL_SHORT -> "SHORT";
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
case GL11.GL_INT -> "INT";
case GL11.GL_FLOAT -> "FLOAT";
case GL11.GL_DOUBLE -> "DOUBLE";
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_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
default -> String.format("0x%X", type);
};
}
public static String getLightName(int light) {
return switch (light) {
case GL11.GL_LIGHT0 -> "LIGHT0";
@@ -559,7 +546,9 @@ public final class GLDebug {
}
public static void initDebugState() {
if (BackendManager.RENDER_BACKEND.supportsDebugOutput()) {
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();
@@ -581,6 +570,10 @@ public final class GLDebug {
}
}
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);
@@ -606,4 +599,49 @@ public final class GLDebug {
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;
}
}
@@ -0,0 +1,34 @@
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;
};
}
}
@@ -0,0 +1,86 @@
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
@@ -0,0 +1,57 @@
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,10 +1,19 @@
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;
@@ -79,6 +88,77 @@ public class GlslTransformUtils {
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 = {""};
@@ -90,13 +90,33 @@ public final class QuadConverter {
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();
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
final int quadCount = vertexCount / 4;
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
@@ -184,7 +204,7 @@ public final class QuadConverter {
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex = VAOManager.Attrib.glTypeSizeBytes(type);
final int bytesPerIndex = GLTypes.sizeBytes(type);
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
@@ -18,6 +18,7 @@ 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;
@@ -40,6 +41,7 @@ public class RenderSystem {
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;
@@ -52,6 +54,11 @@ public class RenderSystem {
@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;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
@@ -64,30 +71,38 @@ public class RenderSystem {
public static void initRenderer() {
if (rendererInitialized) return;
rendererInitialized = true;
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) {
final boolean wasDetected = glesDetected;
if (isGLES() && !wasDetected) {
GLStateManager.LOGGER.info("OpenGL ES {}.{} detected", glesVersion / 100, (glesVersion / 10) % 10);
}
try {
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
dsaState = new DSAARB();
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
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) {
}
} catch (NoSuchFieldError ignored) {
}
if (dsaState == null) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
GLStateManager.LOGGER.info("{}", isGLES ? "GLES context, using DSAUnsupported (bind-based) fallback." : "No DSA support detected, falling back to legacy OpenGL.");
}
BackendManager.init();
@@ -99,8 +114,10 @@ public class RenderSystem {
|| 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;
supportsBufferStorage = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage;
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) {
@@ -140,7 +157,10 @@ public class RenderSystem {
GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
GLStateManager.LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
if (GLStateManager.capabilities.OpenGL32) {
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;
@@ -168,6 +188,10 @@ public class RenderSystem {
}
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);
@@ -327,12 +351,16 @@ public class RenderSystem {
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) {
RENDER_BACKEND.bindImageTexture(unit, texture, level, layered, layer, access, format);
GLStateManager.glBindImageTexture(unit, texture, level, layered, layer, access, format);
}
public static int getMaxImageUnits() {
@@ -340,13 +368,49 @@ public class RenderSystem {
}
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 supportsSSBO() {
return supportsSSBO;
}
public static boolean supportsMultiDrawIndirect() {
return supportsMultiDrawIndirect;
}
public static boolean supportsBufferStorage() {
return supportsBufferStorage;
}
@@ -427,9 +491,14 @@ public class RenderSystem {
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) {
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
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() {
@@ -443,7 +512,7 @@ public class RenderSystem {
// TODO: Proper notification of compute support
public static boolean supportsCompute() {
try {
return GLStateManager.capabilities.GL_ARB_compute_shader;
return GLStateManager.capabilities.GL_ARB_compute_shader || GLStateManager.capabilities.OpenGL43;
} catch (Exception ignored) {
return false;
}
@@ -453,6 +522,10 @@ public class RenderSystem {
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);
}
@@ -505,6 +578,7 @@ public class RenderSystem {
}
public static boolean supportsSnormFormats() {
if (isGLES()) return false;
return GLStateManager.capabilities.OpenGL31;
}
@@ -552,9 +626,17 @@ public class RenderSystem {
}
}
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460" */
static String parseGlVersionString(String info) {
final Matcher matcher = SEMVER_PATTERN.matcher(Objects.requireNonNull(info));
/** 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 + "\"");
}
@@ -0,0 +1,5 @@
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,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback;
@@ -7,8 +8,10 @@ import org.lwjgl.opengl.ARBClearTexture;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.ARBDebugOutputCallback;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.ARBTimerQuery;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.EXTDirectStateAccess;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -27,6 +30,8 @@ import org.lwjgl.opengl.GL44;
import org.lwjgl.opengl.GL45;
import org.lwjgl.opengl.KHRDebug;
import org.lwjgl.opengl.KHRDebugCallback;
import org.lwjgl.opengl.Display;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
@@ -96,9 +101,26 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
}
@Override
public boolean isCurrent() {
try {
return Display.isCurrent();
} catch (Exception e) {
return false;
}
}
@Override
public int getMinGLSLVersion() {return 330;}
@Override
public void setVSyncEnabled(boolean enabled) {Display.setVSyncEnabled(enabled);}
@Override
public boolean isAnisotropicSupported() {
return caps != null && caps.GL_EXT_texture_filter_anisotropic;
}
@Override
public void flush() {GL11.glFlush();}
@@ -322,7 +344,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
org.lwjgl.opengl.GL43.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
GL43.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
@Override
@@ -332,8 +354,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
// On GL path, LWJGL3 service dispatches directly. This is only called via VulkanRenderBackend.
throw new UnsupportedOperationException("GLRenderBackend.drawElementsBaseVertex should not be called — LWJGL service handles this");
GL32.glDrawElementsBaseVertex(mode, count, type, indices, baseVertex);
}
@Override
@@ -927,6 +948,32 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL30.glBindBufferBase(target, index, buffer);
}
@Override
public void bindBufferRange(int target, int index, int buffer, long offset, long size) {
GL30.glBindBufferRange(target, index, buffer, offset, size);
}
@Override
public int getUniformBlockIndex(int program, CharSequence name) {
return GL31.glGetUniformBlockIndex(program, name);
}
@Override
public void uniformBlockBinding(int program, int blockIndex, int binding) {
GL31.glUniformBlockBinding(program, blockIndex, binding);
}
@Override
public int getIndexedBufferBinding(int target, int index) {
final int pname = (target == GL43.GL_SHADER_STORAGE_BUFFER) ? GL43.GL_SHADER_STORAGE_BUFFER_BINDING : GL31.GL_UNIFORM_BUFFER_BINDING;
return GL30.glGetInteger(pname, index);
}
@Override
public int getIntegerIndexed(int pname, int index) {
return GL30.glGetInteger(pname, index);
}
@Override
public void bufferData(int target, long size, int usage) {
GL15.glBufferData(target, size, usage);
@@ -1058,6 +1105,16 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return GL15.glIsBuffer(buffer);
}
@Override
public boolean isTexture(int texture) {
return GL11.glIsTexture(texture);
}
@Override public boolean isFramebuffer(int framebuffer) { return GL30.glIsFramebuffer(framebuffer); }
@Override public boolean isRenderbuffer(int renderbuffer) { return GL30.glIsRenderbuffer(renderbuffer); }
@Override public boolean isSampler(int sampler) { return GL33.glIsSampler(sampler); }
@Override public boolean isQuery(int query) { return GL15.glIsQuery(query); }
@Override
public ByteBuffer mapBufferRange(int target, long offset, long length, int access) {
return GL30.glMapBufferRange(target, offset, length, access, null);
@@ -1284,6 +1341,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return ARBDirectStateAccess.glGetTextureParameteri(texture, pname);
}
@Override
public float getTextureParameterf(int texture, int target, int pname) {
return ARBDirectStateAccess.glGetTextureParameterf(texture, pname);
}
@Override
public int getTextureLevelParameteri(int texture, int level, int pname) {
return ARBDirectStateAccess.glGetTextureLevelParameteri(texture, level, pname);
@@ -1350,6 +1412,66 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
LWJGL.glDeleteSync(sync);
}
@Override
public boolean supportsGpuProfiling() {
if (!caps.OpenGL33 && !caps.GL_ARB_timer_query) return false;
return GL15.glGetQuery(GL33.GL_TIMESTAMP, GL15.GL_QUERY_COUNTER_BITS) > 0;
}
@Override
public int genQuery() {
return GL15.glGenQueries();
}
@Override
public void deleteQuery(int query) {
GL15.glDeleteQueries(query);
}
@Override
public void queryCounter(int query) {
if (caps.OpenGL33) {
GL33.glQueryCounter(query, GL33.GL_TIMESTAMP);
} else {
ARBTimerQuery.glQueryCounter(query, GL33.GL_TIMESTAMP);
}
}
@Override
public boolean isQueryResultAvailable(int query) {
return GL15.glGetQueryObjecti(query, GL15.GL_QUERY_RESULT_AVAILABLE) != 0;
}
@Override
public long getQueryResult64(int query) {
return caps.OpenGL33 ? GL33.glGetQueryObjectui64(query, GL15.GL_QUERY_RESULT) : ARBTimerQuery.glGetQueryObjectui64(query, GL15.GL_QUERY_RESULT);
}
@Override
public long getGpuTimestamp() {
return GL32.glGetInteger64(GL33.GL_TIMESTAMP);
}
@Override public void genQueries(IntBuffer ids) { GL15.glGenQueries(ids); }
@Override public int genQueries() { return GL15.glGenQueries(); }
@Override public void deleteQueries(int id) { GL15.glDeleteQueries(id); }
@Override public void beginQuery(int target, int id) { GL15.glBeginQuery(target, id); }
@Override public void endQuery(int target) { GL15.glEndQuery(target); }
@Override public void getQueryObjectui(int id, int pname, IntBuffer params) { GL15.glGetQueryObjectu(id, pname, params); }
@Override public int getQueryObjecti(int id, int pname) { return GL15.glGetQueryObjecti(id, pname); }
@Override public void waitSync(long sync, int flags, long timeout) { LWJGL.glWaitSync(sync, flags, timeout); }
@Override public int getSynci(long sync, int pname, IntBuffer length) { return LWJGL.glGetSynci(sync, pname, length); }
@Override public void queryCounter(int id, int target) {
if (caps.OpenGL33) GL33.glQueryCounter(id, target); else ARBTimerQuery.glQueryCounter(id, target);
}
@Override public long getQueryObjectui64(int id, int pname) {
return caps.OpenGL33 ? GL33.glGetQueryObjectui64(id, pname) : ARBTimerQuery.glGetQueryObjectui64(id, pname);
}
@Override public void uniform3fv(int location, float[] values) { GL20.glUniform3f(location, values[0], values[1], values[2]); }
@Override public void uniform4fv(int location, float[] values) { GL20.glUniform4f(location, values[0], values[1], values[2], values[3]); }
@Override public void bindFragDataLocation(int program, int colorNumber, CharSequence name) { GL30.glBindFragDataLocation(program, colorNumber, name); }
@Override
public void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) {
GL43.glClearBufferSubData(target, internalFormat, offset, size, format, type, data);
@@ -1366,7 +1488,13 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format);
if (GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format);
} else if (GLStateManager.capabilities.GL_EXT_shader_image_load_store) {
EXTShaderImageLoadStore.glBindImageTextureEXT(unit, texture, level, layered, layer, access, format);
} else {
GLStateManager.warnOnce("bindImageTexture", "glBindImageTexture ignored: no image load/store support");
}
}
@Override
@@ -1391,7 +1519,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public int setupDebugOutput(DebugMessageHandler handler) {
if (caps.OpenGL43 || caps.GL_KHR_debug) {
khrCallback = new KHRDebugCallback((source, type, id, severity, message) -> handler.handleMessage(source, type, id, severity, message));
khrCallback = new KHRDebugCallback(handler::handleMessage);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
@@ -1406,7 +1534,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
return 1;
} else if (caps.GL_ARB_debug_output) {
arbCallback = new ARBDebugOutputCallback((source, type, id, severity, message) -> handler.handleMessage(source, type, id, severity, message));
arbCallback = new ARBDebugOutputCallback(handler::handleMessage);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
@@ -1520,4 +1648,76 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
}
@Override
public void debugMessageControl(int source, int type, int severity, IntBuffer ids, boolean enabled) {
if (activeDebugExtension == 1) {
GL43.glDebugMessageControl(source, type, severity, ids, enabled);
}
}
@Override
public void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
GL14.glMultiDrawArrays(mode, firsts, counts);
}
@Override
public void primitiveRestartIndex(int index) {
GL31.glPrimitiveRestartIndex(index);
}
@Override
public void pointParameterf(int pname, float param) {
GL14.glPointParameterf(pname, param);
}
@Override
public void pointParameteri(int pname, int param) {
GL14.glPointParameteri(pname, param);
}
@Override
public void getActiveAttrib(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name) {
GL20.glGetActiveAttrib(program, index, length, size, type, name);
}
@Override
public String getActiveAttrib(int program, int index, int maxLength, IntBuffer sizeType) {
return GL20.glGetActiveAttrib(program, index, maxLength, sizeType);
}
@Override
public int genRenderbuffers() {
return GL30.glGenRenderbuffers();
}
@Override
public void deleteRenderbuffers(int renderbuffer) {
GL30.glDeleteRenderbuffers(renderbuffer);
}
@Override
public void bindRenderbuffer(int target, int renderbuffer) {
GL30.glBindRenderbuffer(target, renderbuffer);
}
@Override
public void renderbufferStorage(int target, int internalformat, int width, int height) {
GL30.glRenderbufferStorage(target, internalformat, width, height);
}
@Override
public void renderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height) {
GL30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
}
@Override
public void framebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer) {
GL30.glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer);
}
@Override
public void clearBufferData(int target, int internalformat, int format, int type, ByteBuffer data) {
GL43.glClearBufferData(target, internalformat, format, type, data);
}
}
@@ -1,8 +1,12 @@
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 me.eigenraven.lwjgl3ify.api.SwapchainInvalidatingChange;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
@@ -16,14 +20,19 @@ import java.util.List;
*/
public abstract class RenderBackend {
public void onPostWindowCreate(long window, boolean debug) {}
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. Safe to call from any thread. */
/** 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; }
@@ -35,14 +44,46 @@ public abstract class RenderBackend {
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();
public void onFrameBegin() {}
public abstract void setVSyncEnabled(boolean enabled);
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(SwapchainInvalidatingChange 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 abstract void enable(int cap);
public abstract void enablei(int cap, int index);
public abstract void disable(int cap);
@@ -60,6 +101,7 @@ public abstract class RenderBackend {
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);
@@ -85,12 +127,24 @@ public abstract class RenderBackend {
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);
@@ -141,6 +195,12 @@ public abstract class RenderBackend {
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);
@@ -158,6 +218,7 @@ public abstract class RenderBackend {
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);
@@ -179,7 +240,10 @@ public abstract class RenderBackend {
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);
@@ -196,6 +260,8 @@ public abstract class RenderBackend {
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);
@@ -224,6 +290,11 @@ public abstract class RenderBackend {
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);
@@ -251,6 +322,16 @@ public abstract class RenderBackend {
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);
@@ -300,10 +381,12 @@ public abstract class RenderBackend {
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 abstract boolean getBoolean(int pname);
@@ -315,8 +398,30 @@ public abstract class RenderBackend {
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);
@@ -342,4 +447,10 @@ public abstract class RenderBackend {
// 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,6 +1,7 @@
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;
@@ -75,6 +76,11 @@ public class DSAARB extends DSAUnsupported {
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);
@@ -87,19 +93,20 @@ public class DSAARB extends DSAUnsupported {
@Override
public void bindTextureToUnit(int unit, int texture) {
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
if (texture == 0) {
super.bindTextureToUnit(unit, texture);
} else {
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture);
}
bindTextureToUnit(0, unit, texture);
}
@Override
public void bindTextureToUnit(int target, int unit, int texture) {
bindTextureToUnit(unit, 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
@@ -23,6 +23,8 @@ public interface DSAAccess {
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);
@@ -90,6 +90,12 @@ public class DSAUnsupported implements DSAAccess {
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();
@@ -0,0 +1,119 @@
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() {}
}
@@ -61,31 +61,12 @@ public final class FragmentShaderGenerator {
}
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
// Samplers for each enabled unit
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
}
}
if (key.alphaTestEnabled()) {
sb.append("uniform float u_AlphaRef;\n");
}
if (key.lineStipple()) {
sb.append("uniform int u_LineStipple;\n");
}
// Per-unit texEnvColor uniforms
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i) && key.unitNeedsEnvColor(i)) {
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
}
}
if (key.overlayEnabled()) {
sb.append("uniform vec4 u_OverlayColor;\n");
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("uniform vec4 u_FogParams;\n");
sb.append("uniform vec4 u_FogColor;\n");
}
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
}
@@ -14,8 +14,8 @@ public final class GeometryShaderGenerator {
sb.append("layout(lines) in;\n");
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
sb.append("uniform vec2 u_ViewportSize;\n");
sb.append("uniform float u_LineWidth;\n\n");
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
sb.append("// Pass-through varyings\n");
sb.append("in vec4 v_Color_gs[];\n");
@@ -0,0 +1,18 @@
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,16 +1,23 @@
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;
@@ -26,180 +33,15 @@ public class Program {
@Getter private final VertexKey vertexKey;
@Getter private final FragmentKey fragmentKey;
// Per-program uniform dirty-tracking
static final class UploadState {
int mvGen = -1, mvLinearGen = -1, projGen = -1, texMatGen = -1;
int lightingGen = -1, fragmentGen = -1, colorGen = -1, normalGen = -1, texCoordGen = -1;
int texGenGen = -1, clipPlaneGen = -1;
float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
int viewportWidth = -1, viewportHeight = -1;
int stippleViewportX = Integer.MIN_VALUE, stippleViewportY = Integer.MIN_VALUE;
int stippleViewportW = -1, stippleViewportH = -1;
int lineStipple = -1;
}
final UploadState uploadState = new UploadState();
// Uniform locations (-1 = not present in this variant)
// Matrices
public int locModelViewMatrix = -1;
public int locProjectionMatrix = -1;
public int locMVPMatrix = -1;
public int locNormalMatrix = -1;
public final int[] locTextureMatrix = { -1, -1, -1, -1 };
// Current state defaults
public int locCurrentNormal = -1;
public int locCurrentColor = -1;
public final int[] locCurrentTexCoord = { -1, -1, -1, -1 };
public int locCurrentLightmapCoord = -1;
public int locLightmapTextureMatrix = -1;
// Lighting - color material path (raw values)
public int locLightModelAmbient = -1;
public int locMaterialEmission = -1;
public int locMaterialAmbient = -1;
public int locMaterialDiffuse = -1;
public int locMaterialSpecular = -1;
public int locMaterialShininess = -1;
public int locLight0Ambient = -1;
public int locLight0Diffuse = -1;
public int locLight0Specular = -1;
public int locLight1Ambient = -1;
public int locLight1Diffuse = -1;
public int locLight1Specular = -1;
// Lighting - pre-computed path
public int locSceneColor = -1;
public int locLightProd0Ambient = -1;
public int locLightProd0Diffuse = -1;
public int locLightProd0Specular = -1;
public int locLightProd1Ambient = -1;
public int locLightProd1Diffuse = -1;
public int locLightProd1Specular = -1;
// Light positions
public int locLight0Position = -1;
public int locLight1Position = -1;
// Normal scale (for rescale normals)
public int locNormalScale = -1;
// TexGen plane uniforms
public int locTexGenObjPlaneS = -1;
public int locTexGenObjPlaneT = -1;
public int locTexGenObjPlaneR = -1;
public int locTexGenObjPlaneQ = -1;
public int locTexGenEyePlaneS = -1;
public int locTexGenEyePlaneT = -1;
public int locTexGenEyePlaneR = -1;
public int locTexGenEyePlaneQ = -1;
// Clip planes
public int locClipPlanes = -1;
// Wide line emulation (geometry shader)
public int locViewportSize = -1;
public int locLineWidth = -1;
// Line stipple emulation
public int locViewport = -1;
public int locLineStipple = -1;
// Fragment uniforms
public final int[] locSampler = { -1, -1, -1, -1 };
public int locAlphaRef = -1;
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
public int locOverlayColor = -1;
public int locFogParams = -1;
public int locFogColor = -1;
Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
this.programId = programId;
this.vertexKey = vertexKey;
this.fragmentKey = fragmentKey;
resolveLocations();
}
private void resolveLocations() {
locModelViewMatrix = loc("u_ModelViewMatrix");
locProjectionMatrix = loc("u_ProjectionMatrix");
locMVPMatrix = loc("u_MVPMatrix");
locNormalMatrix = loc("u_NormalMatrix");
for (int i = 0; i < 4; i++) {
locTextureMatrix[i] = loc("u_TextureMatrix" + i);
locCurrentTexCoord[i] = loc("u_CurrentTexCoord" + i);
locSampler[i] = RENDER_BACKEND.getUniformLocation(programId, "u_Sampler" + i);
}
locCurrentNormal = loc("u_CurrentNormal");
locCurrentColor = loc("u_CurrentColor");
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
// Color material path
locLightModelAmbient = loc("u_LightModelAmbient");
locMaterialEmission = loc("u_MaterialEmission");
locMaterialAmbient = loc("u_MaterialAmbient");
locMaterialDiffuse = loc("u_MaterialDiffuse");
locMaterialSpecular = loc("u_MaterialSpecular");
locMaterialShininess = loc("u_MaterialShininess");
locLight0Ambient = loc("u_Light0Ambient");
locLight0Diffuse = loc("u_Light0Diffuse");
locLight0Specular = loc("u_Light0Specular");
locLight1Ambient = loc("u_Light1Ambient");
locLight1Diffuse = loc("u_Light1Diffuse");
locLight1Specular = loc("u_Light1Specular");
// Pre-computed path
locSceneColor = loc("u_SceneColor");
locLightProd0Ambient = loc("u_LightProd0Ambient");
locLightProd0Diffuse = loc("u_LightProd0Diffuse");
locLightProd0Specular = loc("u_LightProd0Specular");
locLightProd1Ambient = loc("u_LightProd1Ambient");
locLightProd1Diffuse = loc("u_LightProd1Diffuse");
locLightProd1Specular = loc("u_LightProd1Specular");
// Light positions
locLight0Position = loc("u_Light0Position");
locLight1Position = loc("u_Light1Position");
locNormalScale = loc("u_NormalScale");
// TexGen
locTexGenObjPlaneS = loc("u_TexGenObjPlaneS");
locTexGenObjPlaneT = loc("u_TexGenObjPlaneT");
locTexGenObjPlaneR = loc("u_TexGenObjPlaneR");
locTexGenObjPlaneQ = loc("u_TexGenObjPlaneQ");
locTexGenEyePlaneS = loc("u_TexGenEyePlaneS");
locTexGenEyePlaneT = loc("u_TexGenEyePlaneT");
locTexGenEyePlaneR = loc("u_TexGenEyePlaneR");
locTexGenEyePlaneQ = loc("u_TexGenEyePlaneQ");
// Clip planes
locClipPlanes = loc("u_ClipPlane[0]");
// Wide line emulation
locViewportSize = loc("u_ViewportSize");
locLineWidth = loc("u_LineWidth");
// Line stipple emulation
locViewport = loc("u_Viewport");
locLineStipple = loc("u_LineStipple");
// Fragment
for (int i = 0; i < 4; i++) {
locSampler[i] = loc("u_Sampler" + i);
}
locAlphaRef = loc("u_AlphaRef");
for (int i = 0; i < 4; i++) {
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
}
locOverlayColor = loc("u_OverlayColor");
locFogParams = loc("u_FogParams");
locFogColor = loc("u_FogColor");
}
private int loc(String name) {
return RENDER_BACKEND.getUniformLocation(programId, name);
}
public void destroy() {
@@ -241,7 +83,7 @@ public class Program {
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
// 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]);
@@ -250,6 +92,11 @@ public class Program {
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++) {
@@ -268,6 +115,10 @@ public class Program {
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);
@@ -296,4 +147,22 @@ public class Program {
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());
}
}
}
@@ -3,6 +3,7 @@ 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;
@@ -25,6 +26,8 @@ import java.util.IdentityHashMap;
* </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)
@@ -98,18 +101,25 @@ public class ShaderCache {
}
final VertexKey vk = VertexKey.fromPacked(vkPacked);
final String vertSrc = getOrGenerateVertex(vk);
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
String fragSrc = findFragSource(fkPacked, fkLen);
if (fragSrc == null) {
fragSrc = FragmentShaderGenerator.generate(fk);
uniqueFragCount++;
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 String geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
final Program program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
final int insertIdx = findEmptySlot(hash);
final int base = insertIdx * SLOT_STRIDE;
keys[base] = vkPacked;
@@ -1,12 +1,15 @@
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;
@@ -14,7 +17,6 @@ import lombok.Getter;
import org.joml.Vector3f;
import org.joml.Vector4f;
import java.nio.file.Paths;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
@@ -53,14 +55,21 @@ public final class ShaderManager {
@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() {
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) {
cache.setDumpDir(Paths.get("ffp_shaders"));
}
cache.setDumpDir(SystemProperties.shaderDumpDir("ffp"));
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
VAOManager.setCurrentVertexFlags(format.getVertexFlags());
return false;
});
}
public static ShaderManager getInstance() {
@@ -110,6 +119,7 @@ public final class ShaderManager {
lastBoundProgramId = -1;
currentVertexKeyPacked = Long.MIN_VALUE;
currentFKLen = 0;
GLStateManager.forceAttribDefaultsDirty();
}
public void preDraw() {
@@ -133,7 +143,7 @@ public final class ShaderManager {
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);
@@ -152,6 +162,7 @@ public final class ShaderManager {
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) {
if (Tracy.ENABLED) variantSwitches++;
RENDER_BACKEND.useProgram(programId);
lastBoundProgramId = programId;
}
@@ -159,10 +170,17 @@ public final class ShaderManager {
private void uploadUniforms() {
if (currentProgram != null) {
uniforms.upload(currentProgram);
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++;
@@ -186,13 +204,31 @@ public final class ShaderManager {
uniforms.destroy();
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
com.gtnewhorizons.angelica.glsm.QuadConverter.destroy();
QuadConverter.destroy();
active = false;
currentProgram = null;
}
public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount());
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(); }
}
@@ -8,503 +8,434 @@ 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;
/**
* Uploads GLSM cached state to the active FFP program's uniforms.
*
* Follows Mesa's prog_statevars.c patterns:
* - Pre-computed derived state (light products, scene color) when material is static
* - Raw light/material values when color material is active
* - Optimized fog params (pre-divided for single MAD in shader)
* Maintains the shared {@link FFPUniformBlock} image from GLSM cached state and streams it to the UBO ring.
*/
public class Uniforms {
// Reusable temp buffers for uniform upload
private final FloatBuffer mat4Buf = memAllocFloat(16);
private final FloatBuffer mat3Buf = memAllocFloat(9);
private final FloatBuffer vec4Buf = memAllocFloat(4);
private final FloatBuffer vec3Buf = memAllocFloat(3);
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
// Derived matrices (computed on CPU)
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
// Temp vectors for light product and normal scale computation
private final Vector4f tempVec4 = new Vector4f();
private final org.joml.Vector3f tempVec3 = new org.joml.Vector3f();
// Pre-computed constants for fog params (Mesa STATE_FOG_PARAMS_OPTIMIZED)
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
/**
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
* Uses generation counters to skip categories that haven't changed since last upload.
*/
public void upload(Program program) {
final Program.UploadState st = program.uploadState;
public void upload() {
boolean dirty = false;
contentChanged = false;
final int mvGen = GLStateManager.mvGeneration;
final int mvLinearGen = GLStateManager.mvLinearGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = mvGen != st.mvGen;
final boolean mvLinearChanged = mvLinearGen != st.mvLinearGen;
final boolean projChanged = projGen != st.projGen;
final boolean texMatChanged = texMatGen != st.texMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(program, mvChanged, mvLinearChanged, projChanged, texMatChanged);
st.mvGen = mvGen;
st.mvLinearGen = mvLinearGen;
st.projGen = projGen;
st.texMatGen = texMatGen;
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;
}
if (program.getVertexKey().lightingEnabled()) {
final int litGen = GLStateManager.lightingGeneration;
if (litGen != st.lightingGen) {
uploadLighting(program);
st.lightingGen = litGen;
}
final int mvLinG = GLStateManager.getMvLinearGeneration();
if (mvLinG != mvLinearGen) {
stageNormalMatrix();
mvLinearGen = mvLinG;
dirty = true;
}
// Current color/normal/texcoord — skip if generation unchanged
if (!program.getVertexKey().hasVertexColor()) {
final int colorGen = GLStateManager.colorGeneration;
if (colorGen != st.colorGen) {
uploadCurrentColor(program);
st.colorGen = colorGen;
}
final int texMatG = GLStateManager.getTexMatrixGeneration();
if (texMatG != texMatGen) {
stageTextureMatrices();
texMatGen = texMatG;
dirty = true;
}
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
final int normalGen = ShaderManager.getNormalGeneration();
if (normalGen != st.normalGen) {
uploadCurrentNormal(program);
st.normalGen = normalGen;
}
final int litG = GLStateManager.getLightingGeneration();
if (litG != lightingGen) {
stageLighting();
lightingGen = litG;
stagedLighting++;
dirty = true;
}
if (program.getVertexKey().anyUnitTexCoordEnabled()) {
final int texGen = ShaderManager.getTexCoordGeneration();
if (texGen != st.texCoordGen) {
uploadCurrentTexCoords(program);
st.texCoordGen = texGen;
}
final int colG = GLStateManager.getColorGeneration();
if (colG != colorGen) {
stageCurrentColor();
colorGen = colG;
stagedColor++;
dirty = true;
}
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
if (GLSMConfig.lastBrightnessX != st.lightmapX || GLSMConfig.lastBrightnessY != st.lightmapY) {
uploadCurrentLightmapCoord(program);
st.lightmapX = GLSMConfig.lastBrightnessX;
st.lightmapY = GLSMConfig.lastBrightnessY;
}
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;
}
if (program.getVertexKey().texGenEnabled()) {
final int tgGen = GLStateManager.texGenGeneration;
if (tgGen != st.texGenGen) {
uploadTexGen(program);
st.texGenGen = tgGen;
}
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;
}
if (program.getVertexKey().clipPlanesEnabled()) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (cpGen != st.clipPlaneGen) {
uploadClipPlanes(program);
st.clipPlaneGen = cpGen;
}
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 fragGen = GLStateManager.fragmentGeneration;
if (fragGen != st.fragmentGen) {
uploadFragmentUniforms(program);
st.fragmentGen = fragGen;
final int tgG = GLStateManager.getTexGenGeneration();
if (tgG != texGenGen) {
stageTexGen();
texGenGen = tgG;
stagedTexGen++;
dirty = true;
}
// Wide line emulation uniforms
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
uploadWideLineUniforms(program);
final int cpG = GLStateManager.getClipPlaneGeneration();
if (cpG != clipPlaneGen) {
stageClipPlanes();
clipPlaneGen = cpG;
stagedClipPlanes++;
dirty = true;
}
if (program.locLineStipple != -1) {
uploadLineStippleUniforms(program);
final int fragG = GLStateManager.getFragmentGeneration();
if (fragG != fragmentGen) {
stageFragment();
fragmentGen = fragG;
stagedFragment++;
dirty = true;
}
}
private void uploadLineStippleUniforms(Program program) {
final Program.UploadState st = program.uploadState;
final LineState line = GLStateManager.getLineState();
final int factor = line.getStippleFactor() < 1 ? 1 : line.getStippleFactor();
final int packed = (line.getStipplePattern() & 0xFFFF) | (factor << 16);
if (packed != st.lineStipple) {
RENDER_BACKEND.uniform1i(program.locLineStipple, packed);
st.lineStipple = packed;
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 != st.stippleViewportX || vp.y != st.stippleViewportY || vp.width != st.stippleViewportW || vp.height != st.stippleViewportH) {
RENDER_BACKEND.uniform4f(program.locViewport, vp.x, vp.y, vp.width, vp.height);
st.stippleViewportX = vp.x;
st.stippleViewportY = vp.y;
st.stippleViewportW = vp.width;
st.stippleViewportH = vp.height;
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 uploadMatrices(Program program, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
private void stageMatrices(boolean mvChanged, boolean projChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
// ModelView + derived (normal matrix, normal scale)
if (mvChanged) {
if (program.locModelViewMatrix != -1) {
mv.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
}
putMat4(FFPUniformBlock.MODEL_VIEW_MATRIX, mv);
}
if (mvLinearChanged) {
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
mv.normal(normalMatrix);
}
if (program.locNormalMatrix != -1) {
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(program.locNormalMatrix, false, mat3Buf);
}
// Normal scale (for GL_RESCALE_NORMAL without GL_NORMALIZE)
if (program.locNormalScale != -1) {
// Scale factor = 1/length of first column of normal matrix
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
RENDER_BACKEND.uniform1f(program.locNormalScale, scale);
}
}
// Projection
if (projChanged) {
if (program.locProjectionMatrix != -1) {
proj.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locProjectionMatrix, false, mat4Buf);
}
}
// MVP = Projection * ModelView — needs recompute if either input changed
if ((mvChanged || projChanged) && program.locMVPMatrix != -1) {
proj.mul(mv, mvpMatrix);
mvpMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
}
// Texture matrices (per-unit).
if (texMatChanged) {
for (int i = 0; i < 4; i++) {
if (program.locTextureMatrix[i] != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(i);
texMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix[i], false, mat4Buf);
}
}
if (program.locLightmapTextureMatrix != -1) {
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
lmTexMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locLightmapTextureMatrix, false, mat4Buf);
}
putMat4(FFPUniformBlock.PROJECTION_MATRIX, proj);
}
proj.mul(mv, mvpMatrix);
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
}
private void uploadLighting(Program program) {
final VertexKey vk = program.getVertexKey();
if (vk.colorMaterialEnabled()) {
uploadLightingColorMaterial(program, vk);
} else {
uploadLightingPreComputed(program, vk);
}
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);
}
/**
* Color material active: upload raw light and material values.
* The shader multiplies them with vertex color at runtime.
*/
private void uploadLightingColorMaterial(Program program, VertexKey vk) {
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];
uploadVec4(program.locLightModelAmbient, GLStateManager.getLightModel().ambient);
uploadVec4(program.locMaterialEmission, mat.emission);
uploadVec4(program.locMaterialAmbient, mat.ambient);
uploadVec4(program.locMaterialDiffuse, mat.diffuse);
uploadVec4(program.locMaterialSpecular, mat.specular);
if (program.locMaterialShininess != -1) {
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
}
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);
if (vk.light0Enabled()) {
final LightState light0 = GLStateManager.getLightDataStates()[0];
uploadVec4(program.locLight0Ambient, light0.ambient);
uploadVec4(program.locLight0Diffuse, light0.diffuse);
uploadVec4(program.locLight0Specular, light0.specular);
uploadVec4(program.locLight0Position, light0.position);
}
if (vk.light1Enabled()) {
final LightState light1 = GLStateManager.getLightDataStates()[1];
uploadVec4(program.locLight1Ambient, light1.ambient);
uploadVec4(program.locLight1Diffuse, light1.diffuse);
uploadVec4(program.locLight1Specular, light1.specular);
uploadVec4(program.locLight1Position, 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);
}
/**
* No color material: upload pre-computed light products and scene color.
* Fewer shader multiplies since material is static.
*/
private void uploadLightingPreComputed(Program program, VertexKey vk) {
final MaterialState mat = GLStateManager.getFrontMaterial();
// Scene color = emission + ambient * lightModel.ambient
// Alpha = material.diffuse.a
if (program.locSceneColor != -1) {
tempVec4.set(
mat.emission.x + mat.ambient.x * GLStateManager.getLightModel().ambient.x,
mat.emission.y + mat.ambient.y * GLStateManager.getLightModel().ambient.y,
mat.emission.z + mat.ambient.z * GLStateManager.getLightModel().ambient.z,
mat.diffuse.w // alpha from diffuse
);
uploadVec4(program.locSceneColor, tempVec4);
}
if (program.locMaterialShininess != -1) {
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
}
// Per-light products
if (vk.light0Enabled()) {
final LightState light0 = GLStateManager.getLightDataStates()[0];
uploadVec4(program.locLight0Position, light0.position);
uploadLightProduct(program.locLightProd0Ambient, light0.ambient, mat.ambient);
uploadLightProduct(program.locLightProd0Diffuse, light0.diffuse, mat.diffuse);
uploadLightProduct(program.locLightProd0Specular, light0.specular, mat.specular);
}
if (vk.light1Enabled()) {
final LightState light1 = GLStateManager.getLightDataStates()[1];
uploadVec4(program.locLight1Position, light1.position);
uploadLightProduct(program.locLightProd1Ambient, light1.ambient, mat.ambient);
uploadLightProduct(program.locLightProd1Diffuse, light1.diffuse, mat.diffuse);
uploadLightProduct(program.locLightProd1Specular, 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);
}
/**
* Upload pre-multiplied light product: light.X * material.X (RGB only).
*/
private void uploadLightProduct(int loc, Vector4f lightVal, Vector4f materialVal) {
if (loc == -1) return;
vec3Buf.clear();
vec3Buf.put(lightVal.x * materialVal.x);
vec3Buf.put(lightVal.y * materialVal.y);
vec3Buf.put(lightVal.z * materialVal.z);
vec3Buf.flip();
RENDER_BACKEND.uniform3(loc, vec3Buf);
}
private void uploadCurrentColor(Program program) {
if (program.locCurrentColor == -1) return;
// Upload the current color from GLSM
private void stageCurrentColor() {
final var color = GLStateManager.getColor();
tempVec4.set(
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()));
vec4Buf.clear();
tempVec4.get(vec4Buf);
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
}
private void uploadCurrentTexCoords(Program program) {
for (int i = 0; i < 4; i++) {
if (program.locCurrentTexCoord[i] != -1) {
uploadVec4(program.locCurrentTexCoord[i], ShaderManager.getCurrentTexCoord(i));
}
}
}
private void uploadCurrentLightmapCoord(Program program) {
if (program.locCurrentLightmapCoord == -1) return;
RENDER_BACKEND.uniform2f(program.locCurrentLightmapCoord,
GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY);
}
private void uploadCurrentNormal(Program program) {
if (program.locCurrentNormal == -1) return;
// Upload current normal from FFPShaderManager's tracked normal
final var normal = ShaderManager.getCurrentNormal();
vec3Buf.clear();
normal.get(vec3Buf);
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
}
private void uploadTexGen(Program program) {
final VertexKey vk = program.getVertexKey();
private void stageTexGen() {
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
if (vk.texGenModeS() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneS, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_S));
} else if (vk.texGenModeS() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneS, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_S));
}
if (vk.texGenModeT() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneT, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_T));
} else if (vk.texGenModeT() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneT, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_T));
}
if (vk.texGenModeR() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneR, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_R));
} else if (vk.texGenModeR() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneR, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_R));
}
if (vk.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneQ, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_Q));
} else if (vk.texGenModeQ() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneQ, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_Q));
}
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 uploadPlane(int loc, float[] plane) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(plane, 0, 4);
vec4Buf.flip();
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
private void uploadClipPlanes(Program program) {
if (program.locClipPlanes == -1) return;
private void stageClipPlanes() {
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(program.locClipPlanes, 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 uploadFragmentUniforms(Program program) {
final FragmentKey fk = program.getFragmentKey();
private void stageFragment() {
putFloat(FFPUniformBlock.ALPHA_REF, GLStateManager.getAlphaState().getReference());
// Alpha test reference
if (fk.alphaTestEnabled() && program.locAlphaRef != -1) {
RENDER_BACKEND.uniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
}
// Per-unit tex env color
for (int i = 0; i < 4; i++) {
if (program.locTexEnvColor[i] != -1) {
final var envState = GLStateManager.getTextures().getTexEnvState(i);
vec4Buf.clear();
vec4Buf.put(envState.envColorR);
vec4Buf.put(envState.envColorG);
vec4Buf.put(envState.envColorB);
vec4Buf.put(envState.envColorA);
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locTexEnvColor[i], vec4Buf);
}
final var envState = GLStateManager.getTextures().getTexEnvState(i);
putVec4(FFPUniformBlock.TEX_ENV_COLOR_0 + i * 16,
envState.envColorR, envState.envColorG, envState.envColorB, envState.envColorA);
}
if (fk.overlayEnabled() && program.locOverlayColor != -1) {
vec4Buf.clear();
vec4Buf.put(GLStateManager.getOverlayR());
vec4Buf.put(GLStateManager.getOverlayG());
vec4Buf.put(GLStateManager.getOverlayB());
vec4Buf.put(GLStateManager.getOverlayA());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locOverlayColor, vec4Buf);
}
putVec4(FFPUniformBlock.OVERLAY_COLOR,
GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
// Fog
if (fk.fogMode() != FragmentKey.FOG_NONE) {
uploadFog(program);
}
}
/**
* Upload optimized fog params (following Mesa's STATE_FOG_PARAMS_OPTIMIZED):
*/
private void uploadFog(Program program) {
// Mesa STATE_FOG_PARAMS_OPTIMIZED
final FogState fog = GLStateManager.getFogState();
if (program.locFogParams != -1) {
final float start = fog.getStart();
final float end = fog.getEnd();
final float density = fog.getDensity();
final float range = end - start;
vec4Buf.clear();
vec4Buf.put(range != 0.0f ? -1.0f / range : 0.0f); // [0]: -1/(end-start)
vec4Buf.put(range != 0.0f ? end / range : 1.0f); // [1]: end/(end-start)
vec4Buf.put((float)(density / LN2)); // [2]: density/ln(2)
vec4Buf.put((float)(density / SQRT_LN2)); // [3]: density/sqrt(ln(2))
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locFogParams, vec4Buf);
}
if (program.locFogColor != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x);
vec4Buf.put((float) fog.getFogColor().y);
vec4Buf.put((float) fog.getFogColor().z);
vec4Buf.put(fog.getFogAlpha());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locFogColor, vec4Buf);
}
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 uploadWideLineUniforms(Program program) {
final Program.UploadState st = program.uploadState;
final float lineWidth = GLStateManager.getLineState().getWidth();
if (lineWidth != st.lineWidth) {
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth);
st.lineWidth = lineWidth;
}
final int vw = GLStateManager.getViewportState().width;
final int vh = GLStateManager.getViewportState().height;
if (vw != st.viewportWidth || vh != st.viewportHeight) {
RENDER_BACKEND.uniform2f(program.locViewportSize, vw, vh);
st.viewportWidth = vw;
st.viewportHeight = vh;
}
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 uploadVec4(int loc, Vector4f v) {
if (loc == -1) return;
vec4Buf.clear();
v.get(vec4Buf);
RENDER_BACKEND.uniform4(loc, vec4Buf);
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(mat4Buf);
memFree(mat3Buf);
memFree(vec4Buf);
memFree(vec3Buf);
memFree(staging);
memFree(clipPlaneBuf);
if (ring != null) {
ring.destroy();
ring = null;
}
bound = false;
}
}
@@ -1,6 +1,7 @@
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;
@@ -315,17 +316,7 @@ public final class VAOManager {
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
return GLTypes.sizeBytes(type);
}
public float readComponent(ByteBuffer buf, int base, int component) {
@@ -51,6 +51,7 @@ public final class VertexKey {
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;
@@ -100,6 +101,7 @@ public final class VertexKey {
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public boolean instancedDraw() { return bit(BIT_INSTANCED); }
public 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; }
@@ -233,6 +235,10 @@ public final class VertexKey {
bits |= (1L << BIT_LINE_STIPPLE);
}
if (GLStateManager.instancedFfpDrawActive) {
bits |= (1L << BIT_INSTANCED);
}
return bits;
}
@@ -258,12 +264,12 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
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());
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancedDraw());
}
}
@@ -69,6 +69,14 @@ public final class VertexShaderGenerator {
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');
}
@@ -78,119 +86,10 @@ public final class VertexShaderGenerator {
}
private static void emitUniforms(StringBuilder sb, VertexKey key) {
sb.append("// Matrices\n");
sb.append("uniform mat4 u_ModelViewMatrix;\n");
sb.append("uniform mat4 u_ProjectionMatrix;\n");
sb.append("uniform mat4 u_MVPMatrix;\n");
if (key.lightingEnabled()) {
sb.append("uniform mat3 u_NormalMatrix;\n");
}
if (key.lineStipple()) {
sb.append("uniform vec4 u_Viewport;\n");
}
for (int i = 0; i < VertexKey.MAX_UNITS; i++) {
if (i == 1) continue;
if (key.unitTexMatEnabled(i)) {
sb.append("uniform mat4 u_TextureMatrix").append(i).append(";\n");
}
}
// TexGen plane uniforms (unit 0 only - TODO: per-unit)
if (key.texGenModeS() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneS;\n");
if (key.texGenModeS() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneS;\n");
if (key.texGenModeT() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneT;\n");
if (key.texGenModeT() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneT;\n");
if (key.texGenModeR() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneR;\n");
if (key.texGenModeR() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneR;\n");
if (key.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneQ;\n");
if (key.texGenModeQ() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneQ;\n");
if (!key.hasVertexNormal() && key.lightingEnabled()) {
sb.append("uniform vec3 u_CurrentNormal;\n");
}
if (!key.hasVertexColor()) {
sb.append("uniform vec4 u_CurrentColor;\n");
}
if (key.unitTexCoordEnabled(0) && !key.hasVertexTexCoord()) {
sb.append("uniform vec4 u_CurrentTexCoord0;\n");
}
if (key.unitTexCoordEnabled(2) && !key.unit23UvFromUnit0()) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
if (key.unitTexCoordEnabled(3) && !key.unit23UvFromUnit0()) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
}
if (key.lightmapEnabled()) {
sb.append("uniform mat4 u_LightmapTextureMatrix;\n");
}
if (key.lightingEnabled()) {
emitLightingUniforms(sb, key);
}
if (key.lightingEnabled() && (key.normalizeEnabled() || key.rescaleNormalsEnabled())) {
// normalize is done in shader; rescale uses normalScale
if (key.rescaleNormalsEnabled() && !key.normalizeEnabled()) {
sb.append("uniform float u_NormalScale;\n");
}
}
if (key.clipPlanesEnabled()) {
sb.append("uniform vec4 u_ClipPlane[8];\n");
}
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
}
private static void emitLightingUniforms(StringBuilder sb, VertexKey key) {
sb.append("\n// Lighting\n");
if (key.colorMaterialEnabled()) {
// When color material is active, we can't pre-compute light products so we upload raw light and material values
sb.append("uniform vec4 u_LightModelAmbient;\n");
sb.append("uniform vec4 u_MaterialEmission;\n");
sb.append("uniform vec4 u_MaterialAmbient;\n");
sb.append("uniform vec4 u_MaterialDiffuse;\n");
sb.append("uniform vec4 u_MaterialSpecular;\n");
sb.append("uniform float u_MaterialShininess;\n");
if (key.light0Enabled()) {
sb.append("uniform vec4 u_Light0Ambient;\n");
sb.append("uniform vec4 u_Light0Diffuse;\n");
sb.append("uniform vec4 u_Light0Specular;\n");
sb.append("uniform vec4 u_Light0Position;\n");
}
if (key.light1Enabled()) {
sb.append("uniform vec4 u_Light1Ambient;\n");
sb.append("uniform vec4 u_Light1Diffuse;\n");
sb.append("uniform vec4 u_Light1Specular;\n");
sb.append("uniform vec4 u_Light1Position;\n");
}
} else {
// Pre-computed: sceneColor + lightProducts
sb.append("uniform vec4 u_SceneColor;\n");
if (key.light0Enabled()) {
sb.append("uniform vec4 u_Light0Position;\n");
sb.append("uniform vec3 u_LightProd0Ambient;\n");
sb.append("uniform vec3 u_LightProd0Diffuse;\n");
sb.append("uniform vec3 u_LightProd0Specular;\n");
}
if (key.light1Enabled()) {
sb.append("uniform vec4 u_Light1Position;\n");
sb.append("uniform vec3 u_LightProd1Ambient;\n");
sb.append("uniform vec3 u_LightProd1Diffuse;\n");
sb.append("uniform vec3 u_LightProd1Specular;\n");
}
sb.append("uniform float u_MaterialShininess;\n");
}
}
private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n");
@@ -218,22 +117,30 @@ public final class VertexShaderGenerator {
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Position transform\n");
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
sb.append(" gl_Position = u_MVPMatrix * pos4;\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");
// 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");
if (key.hasVertexNormal()) {
sb.append(" vec3 normal = u_NormalMatrix * a_Normal;\n");
} else {
sb.append(" vec3 normal = u_NormalMatrix * u_CurrentNormal;\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");
@@ -250,6 +157,9 @@ public final class VertexShaderGenerator {
// 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
@@ -345,10 +255,11 @@ public final class VertexShaderGenerator {
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
sb.append(" // Color passthrough (no lighting)\n");
if (key.hasVertexColor()) {
sb.append(" v_Color = a_Color;\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 = u_CurrentColor;\n");
sb.append(" v_Color = ").append(src).append(";\n");
}
sb.append('\n');
}
@@ -369,7 +280,9 @@ public final class VertexShaderGenerator {
}
if (key.lightmapEnabled()) {
if (key.hasVertexLightmap()) {
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");
@@ -8,5 +8,7 @@ public final class GLSMConfig {
public static boolean hudCacheOverride;
public static volatile boolean expandVertexFormats;
private GLSMConfig() {}
}
@@ -9,28 +9,36 @@ public final class GLSMHooks {
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() {}
}
@@ -2,19 +2,20 @@ 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 {
private final boolean lwjglDebug;
private final StreamingUploader.UploadStrategy streamingUploadStrategy;
private final Consumer<DirectTessellator> directDrawer;
private final Runnable streamingDrawerDestroy;
private final int displayWidth;
private final int displayHeight;
private final Runnable postInitCallback;
private final boolean enableDSA;
private final boolean noErrorChecks;
@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;
@@ -25,23 +26,13 @@ public final class GLSMInitConfig {
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
this.enableDSA = builder.enableDSA;
this.DSAEnabled = builder.enableDSA;
}
public static Builder builder() {
return new Builder();
}
public boolean isLwjglDebug() { return lwjglDebug; }
public StreamingUploader.UploadStrategy getStreamingUploadStrategy() { return streamingUploadStrategy; }
public Consumer<DirectTessellator> getDirectDrawer() { return directDrawer; }
public Runnable getStreamingDrawerDestroy() { return streamingDrawerDestroy; }
public int getDisplayWidth() { return displayWidth; }
public int getDisplayHeight() { return displayHeight; }
public Runnable getPostInitCallback() { return postInitCallback; }
public boolean isDSAEnabled() { return enableDSA; }
public boolean noErrorChecks() { return noErrorChecks; }
public static final class Builder {
private boolean lwjglDebug = false;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
@@ -0,0 +1,18 @@
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();
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
@FunctionalInterface
public interface ShaderTransformPostProcessor {
void onTransformed(String transformedSource, ShaderType shaderType);
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public interface ShaderWorkSubmitter {
<T> CompletableFuture<T> submit(Supplier<T> work);
}
@@ -0,0 +1,8 @@
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;
}
@@ -0,0 +1,7 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ProgramDeleteEvent extends MutableEvent {
public int program;
}
@@ -0,0 +1,422 @@
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;
}
}
@@ -0,0 +1,36 @@
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();
}
@@ -0,0 +1,87 @@
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;
}
}
@@ -2,6 +2,7 @@ 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;
@@ -10,16 +11,24 @@ 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 * VAOManager.Attrib.glTypeSizeBytes(type);
return stride != 0 ? stride : size * GLTypes.sizeBytes(type);
}
public int typeSizeBytes() {
return VAOManager.Attrib.glTypeSizeBytes(type);
return GLTypes.sizeBytes(type);
}
}
@@ -59,6 +68,7 @@ public final class AttribSnapshot {
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
boolean success = false;
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
int rangeSoleVboId = 0;
@@ -71,7 +81,7 @@ public final class AttribSnapshot {
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 * VAOManager.Attrib.glTypeSizeBytes(a.type);
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;
@@ -125,8 +135,8 @@ public final class AttribSnapshot {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) {
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
continue;
warnOnce("vbo-orphaned", "[AttribSnapshot] GL_BUFFER_SIZE={} for VBO {} -- skipping capture", bufferSize, a.vboId);
return null;
}
final long rStart;
@@ -139,9 +149,16 @@ public final class AttribSnapshot {
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) (Math.min(bufferSize, rEnd) - ro);
if (rs <= 0) continue;
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);
@@ -169,10 +186,13 @@ public final class AttribSnapshot {
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);
}
}
return new AttribSnapshot(out, allocated);
}
}
@@ -1,6 +1,7 @@
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.
@@ -37,12 +38,14 @@ public final class DisplayListVBO {
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) {
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() {
@@ -62,11 +65,12 @@ public final class DisplayListVBO {
}
public void render() {
if (vao == null) return;
vao.bind();
if (count <= 0) { vao.unbind(); return; }
vao.draw(drawMode, start, count);
vao.unbind();
if (vao == null) return;
vao.bind();
if (count <= 0) { vao.unbind(); return; }
VAOManager.setCurrentVertexFlags(vertexFlags);
vao.draw(drawMode, start, count);
vao.unbind();
}
@@ -72,17 +72,18 @@ public final class DisplayListVBOBuilder {
}
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);
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);
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);
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);
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount, flags);
}
@@ -203,6 +203,7 @@ public final class ImmediateModeRecorder {
*/
public static DirectTessellator processDrawArraysFromAttribs(int mode, int first, int count) {
final AttribSnapshot snap = AttribSnapshot.snapshot(first, count);
if (snap == null) return null;
try {
prepareReaders(snap);
if (positionReaderIndex < 0) return null;
@@ -307,36 +308,4 @@ public final class ImmediateModeRecorder {
return VAOManager.Attrib.readComponent(r.type, r.normalized, r.data, 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,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import java.util.Arrays;
@@ -24,7 +24,7 @@ public final class AttribLayoutKey {
int base = 0;
int maxAlign = 1;
for (int i = 0; i < n; i++) {
final int typeSize = VAOManager.Attrib.glTypeSizeBytes(types[i]);
final int typeSize = GLTypes.sizeBytes(types[i]);
if (typeSize > maxAlign) maxAlign = typeSize;
base = (base + typeSize - 1) & -typeSize;
off[i] = base;
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.AttribSnapshot;
@@ -60,7 +61,7 @@ public final class IndexedDrawCapture {
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
return null;
}
final int srcIndexSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
final int srcIndexSize = GLTypes.sizeBytes(srcIndexType);
if (srcIndexSize <= 0) return null;
final int prevEBO = GLStateManager.getBoundEBO();
@@ -95,7 +96,7 @@ public final class IndexedDrawCapture {
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 - skipping", indicesCount);
return null;
}
final int idxSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
final int idxSize = GLTypes.sizeBytes(srcIndexType);
if (idxSize <= 0) return null;
if ((long) indicesCount * idxSize > srcByteLength) {
GLStateManager.warnOnce("client-idx-range", "[IndexedDrawCapture] index count {} ({} bytes) exceeds client buffer {} bytes - skipping", indicesCount, (long) indicesCount * idxSize, srcByteLength);
@@ -120,6 +121,7 @@ public final class IndexedDrawCapture {
final int vertexCount = maxVtx - minVtx + 1;
snap = AttribSnapshot.snapshot(minVtx, vertexCount);
if (snap == null) return null;
int enabledCount = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -78,6 +79,6 @@ public record TexImage2DCmd(
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
width, height, border, GLDebug.getTextureFormatName(format), GLTypes.name(type), dataStr, unpack);
}
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -78,6 +79,6 @@ public record TexSubImage2DCmd(
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
GLDebug.getTextureFormatName(format), GLTypes.name(type), dataStr, unpack);
}
}
@@ -2,8 +2,11 @@ package com.gtnewhorizons.angelica.glsm.redirect;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
import com.gtnewhorizons.angelica.config.SystemProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
@@ -21,6 +24,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Core GL call redirector for GLSM. Rewrites bytecode so that GL calls go through {@code GLStateManager} for state tracking and backend abstraction.
@@ -30,12 +34,107 @@ import java.util.Set;
*/
public class GLSMRedirector {
private static final boolean ASSERT_MAIN_THREAD = Boolean.getBoolean("angelica.assertMainThread");
private static final boolean LOG_SPAM = Boolean.getBoolean("angelica.redirectorLogspam");
private static final boolean LOG_SPAM = SystemProperties.REDIRECTOR_LOGSPAM;
private static final Logger LOGGER = LogManager.getLogger("GLSMRedirector");
private static final String LWJGL_GL_PACKAGE = "org/lwjgl/opengl/";
private static final Set<String> DEBUG_OWNERS = ImmutableSet.of(
"org/lwjgl/opengl/ARBDebugOutput",
"org/lwjgl/opengl/AMDDebugOutput"
);
private static final Set<String> DEBUG_METHODS = ImmutableSet.of(
"glDebugMessageCallback",
"glDebugMessageControl",
"glDebugMessageInsert",
"glGetDebugMessageLog",
"glObjectLabel",
"glGetObjectLabel",
"glObjectPtrLabel",
"glGetObjectPtrLabel",
"glPushDebugGroup",
"glPopDebugGroup"
);
private static final Set<String> UNMAPPED_SEEN = ConcurrentHashMap.newKeySet();
private static final Set<String> DEBUG_SEEN = ConcurrentHashMap.newKeySet();
private static boolean detectionEnabled() {
return SystemProperties.UNMAPPED_GL.detects();
}
private static final String GLSYNC_TYPE = "Lorg/lwjgl/opengl/GLSync;";
private static boolean isDebugCall(String owner, String name) {
if (!owner.startsWith(LWJGL_GL_PACKAGE)) return false;
return DEBUG_OWNERS.contains(owner) || DEBUG_METHODS.contains(name);
}
private static boolean isUnmappedGlCall(String owner, String name, String desc) {
if (!owner.startsWith(LWJGL_GL_PACKAGE)) return false;
if (!name.startsWith("gl") && !name.startsWith("ngl")) return false;
if (desc.contains(GLSYNC_TYPE)) return false;
return true;
}
private static boolean hasLwjglTypedDescriptor(String desc) {
return desc.contains("Lorg/lwjgl/");
}
private static final Set<String> AWARE_NON_DISPATCHING = ImmutableSet.of(
"org/lwjgl/opengl/GL getCapabilities",
"org/lwjgl/opengl/GL setCapabilities",
"org/lwjgl/opengl/GL getFunctionProvider",
"org/lwjgl/opengl/GL create",
"org/lwjgl/opengl/GLCapabilities getAddressBuffer",
"org/lwjgl/opengl/GLCapabilities initialize",
"org/lwjgl/opengl/GLDebugMessageCallback create",
"org/lwjgl/opengl/GLDebugMessageCallback createSafe",
"org/lwjgl/opengl/GLDebugMessageCallback getMessage"
);
private static boolean isAwareNonDispatching(String owner, String name) {
return AWARE_NON_DISPATCHING.contains(owner + " " + name);
}
private static void reportAwareUnroutable(String className, String owner, String name, String desc) {
if (!detectionEnabled()) return;
if (UNMAPPED_SEEN.add("aware " + owner + "." + name + desc)) {
LOGGER.warn("Aware-unroutable GL call {}.{}{} in {}; needs an awareOwnerRedirects entry (see AwareGLDebugShim)", owner, name, desc, className);
}
if (SystemProperties.UNMAPPED_GL.failsAwareUnroutable()) {
throw new IllegalStateException("Aware-unroutable GL call " + owner + "." + name + desc + " in " + className);
}
}
private static void reportUnmapped(String className, String owner, String name, String desc) {
if (!detectionEnabled()) return;
if (UNMAPPED_SEEN.add(owner + "." + name + desc)) {
LOGGER.warn("Unmapped GL call {}.{}{} in {} reached the redirector; on a non-GL backend this will crash at runtime.", owner, name, desc, className);
}
if (SystemProperties.UNMAPPED_GL.failsUnmapped()) {
throw new IllegalStateException("Unmapped GL call " + owner + "." + name + desc + " in " + className);
}
}
private static void reportDebugPassthrough(String className, String owner, String name, String desc) {
if (!detectionEnabled()) return;
if (DEBUG_SEEN.add(owner + "." + name + desc)) {
LOGGER.info("Debug-output call {}.{}{} in {} bypasses the backend (reaches real GL; unsupported on the SDL backend).", owner, name, desc, className);
}
}
private static final String Drawable = "org/lwjgl/opengl/Drawable";
private static final String SharedDrawable = "org/lwjgl/opengl/SharedDrawable";
private static final String Display = "org/lwjgl/opengl/Display";
private static boolean isDrawableOwner(String owner) {
return owner.startsWith(Drawable) || owner.equals(SharedDrawable);
}
private static final String GLStateManager = "com/gtnewhorizons/angelica/glsm/GLStateManager";
private static final String AwareGLDebugShim = "com/gtnewhorizons/angelica/sdlgpu/compat/AwareGLDebugShim";
private static final Map<String, String> awareOwnerRedirects = new HashMap<>();
private static final String GL_PREFIX = "org/lwjgl/opengl/GL";
private static final String ARBVertexArrayObject = "org/lwjgl/opengl/ARBVertexArrayObject";
private static final String APPLEVertexArrayObject = "org/lwjgl/opengl/APPLEVertexArrayObject";
@@ -53,19 +152,23 @@ public class GLSMRedirector {
private static final String ARBBufferStorage = "org/lwjgl/opengl/ARBBufferStorage";
private static final String ARBCopyBuffer = "org/lwjgl/opengl/ARBCopyBuffer";
private static final String ARBMapBufferRange = "org/lwjgl/opengl/ARBMapBufferRange";
private static final String ARBFramebufferObject = "org/lwjgl/opengl/ARBFramebufferObject";
private static final String EXTFramebufferObject = "org/lwjgl/opengl/EXTFramebufferObject";
private static final String ARBVertexShader = "org/lwjgl/opengl/ARBVertexShader";
private static final String ARBOcclusionQuery = "org/lwjgl/opengl/ARBOcclusionQuery";
private static final String ARBDrawElementsBaseVertex = "org/lwjgl/opengl/ARBDrawElementsBaseVertex";
private static final String ARBMultiDrawIndirect = "org/lwjgl/opengl/ARBMultiDrawIndirect";
private static final String ARBUniformBufferObject = "org/lwjgl/opengl/ARBUniformBufferObject";
private static final String EXTGpuShader4 = "org/lwjgl/opengl/EXTGpuShader4";
private static final String EXTGPUShader4 = "org/lwjgl/opengl/EXTGPUShader4";
private static final String ARBTimerQuery = "org/lwjgl/opengl/ARBTimerQuery";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
private static final String MinecraftClient = "net.minecraft.client";
private static final String SplashProgress = "cpw.mods.fml.client.SplashProgress";
private static final Set<String> ExcludedMinecraftMainThreadChecks = ImmutableSet.of(
"startGame", "func_71384_a",
"initializeTextures", "func_77474_a"
);
private static final Map<String, Map<String, String>> methodRedirects = new HashMap<>(32);
private static final Map<String, String> glMethodRedirects = new HashMap<>(256);
private static final Map<String, String> glDescRedirects = new HashMap<>();
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
private static final Map<String, String> typeRedirects = new HashMap<>();
private static final ClassConstantPoolParser cstPoolParser;
@@ -78,15 +181,15 @@ public class GLSMRedirector {
};
static {
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_ALPHA_TEST, "AlphaTest");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_BLEND, "Blend");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_DEPTH_TEST, "DepthTest");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_CULL_FACE, "Cull");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_LIGHTING, "Lighting");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_TEXTURE_2D, "Texture");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_FOG, "Fog");
glCapRedirects.put(org.lwjgl.opengl.GL12.GL_RESCALE_NORMAL, "RescaleNormal");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_SCISSOR_TEST, "ScissorTest");
glCapRedirects.put(GL11.GL_ALPHA_TEST, "AlphaTest");
glCapRedirects.put(GL11.GL_BLEND, "Blend");
glCapRedirects.put(GL11.GL_DEPTH_TEST, "DepthTest");
glCapRedirects.put(GL11.GL_CULL_FACE, "Cull");
glCapRedirects.put(GL11.GL_LIGHTING, "Lighting");
glCapRedirects.put(GL11.GL_TEXTURE_2D, "Texture");
glCapRedirects.put(GL11.GL_FOG, "Fog");
glCapRedirects.put(GL12.GL_RESCALE_NORMAL, "RescaleNormal");
glCapRedirects.put(GL11.GL_SCISSOR_TEST, "ScissorTest");
final var gl11 = RedirectMap.newMap()
// glEnable/Disable - Special cased in GLSMRedirector, but included here for the external API
@@ -117,6 +220,7 @@ public class GLSMRedirector {
.add("glGenLists")
.add("glGenTextures")
.add("glIsList")
.add("glIsTexture")
.add("glDeleteTextures")
.add("glDepthFunc")
.add("glDepthMask")
@@ -135,6 +239,11 @@ public class GLSMRedirector {
.add("glGetFloat")
.add("glGetFloatv", "glGetFloat")
.add("glGetInteger")
.add("glGetIntegerv", "glGetInteger")
.add("glGetIntegeri")
.add("glGetIntegeri_v")
.add("glGetBooleani")
.add("glGetBooleani_v")
.add("glGetLight")
.add("glGetMaterial")
.add("glGetTexLevelParameteri")
@@ -155,6 +264,7 @@ public class GLSMRedirector {
.add("glListBase")
.add("glLoadIdentity")
.add("glLoadMatrix")
.add("glLoadMatrixf", "glLoadMatrix")
.add("glLogicOp")
.add("glMatrixMode")
.add("glMultMatrix")
@@ -236,6 +346,7 @@ public class GLSMRedirector {
.add("glPolygonStipple")
.add("glAccum")
.add("glReadBuffer")
.add("glReadPixels")
.add("glSampleCoverage")
.add("glScissor")
.add("glStencilFunc")
@@ -273,7 +384,8 @@ public class GLSMRedirector {
final var gl12 = RedirectMap.newMap()
.add("glTexImage3D")
.add("glTexSubImage3D")
.add("glCopyTexSubImage3D");
.add("glCopyTexSubImage3D")
.add("glDrawRangeElements");
final var gl13 = RedirectMap.newMap()
.add("glActiveTexture")
.add("glSampleCoverage")
@@ -284,7 +396,10 @@ public class GLSMRedirector {
final var gl14 = RedirectMap.newMap()
.add("glBlendFuncSeparate", "tryBlendFuncSeparate")
.add("glBlendColor")
.add("glBlendEquation");
.add("glBlendEquation")
.add("glMultiDrawArrays")
.add("glPointParameterf")
.add("glPointParameteri");
final var gl15 = RedirectMap.newMap()
.add("glGenBuffers")
.add("glBindBuffer")
@@ -300,7 +415,14 @@ public class GLSMRedirector {
.add("glGetBufferParameteri")
.add("glGetBufferParameter")
.add("glGetBufferParameteriv")
.add("glIsBuffer");
.add("glIsBuffer")
.add("glIsQuery")
.add("glGenQueries")
.add("glDeleteQueries")
.add("glBeginQuery")
.add("glEndQuery")
.add("glGetQueryObjectui")
.add("glGetQueryObjecti");
final var gl20 = RedirectMap.newMap()
.add("glBlendEquationSeparate")
.add("glDrawBuffers")
@@ -320,6 +442,8 @@ public class GLSMRedirector {
.add("glValidateProgram")
.add("glGetUniformLocation")
.add("glGetAttribLocation")
.add("glIsShader")
.add("glIsProgram")
.add("glUniform1f")
.add("glUniform2f")
.add("glUniform3f")
@@ -332,9 +456,13 @@ public class GLSMRedirector {
.add("glUniform2")
.add("glUniform3")
.add("glUniform4")
.add("glUniform1fv", "glUniform1")
.add("glUniform3fv", "glUniform3")
.add("glUniform4fv", "glUniform4")
.add("glUniformMatrix2")
.add("glUniformMatrix3")
.add("glUniformMatrix4")
.add("glUniformMatrix3fv", "glUniformMatrix3")
.add("glUniformMatrix4fv", "glUniformMatrix4")
.add("glDeleteShader")
.add("glGetShaderi")
@@ -352,12 +480,14 @@ public class GLSMRedirector {
.add("glGetUniform")
.add("glVertexAttribPointer")
.add("glEnableVertexAttribArray")
.add("glDisableVertexAttribArray");
.add("glDisableVertexAttribArray")
.add("glGetActiveAttrib");
final var gl30 = RedirectMap.newMap()
.add("glGenVertexArrays")
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glIsVertexArray")
.add("glBindFragDataLocation")
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
@@ -368,20 +498,37 @@ public class GLSMRedirector {
.add("glGetFramebufferAttachmentParameteri")
.add("glBlitFramebuffer")
.add("glMapBufferRange")
.add("glFlushMappedBufferRange");
.add("glFlushMappedBufferRange")
.add("glGenRenderbuffers")
.add("glDeleteRenderbuffers")
.add("glBindRenderbuffer")
.add("glRenderbufferStorage")
.add("glRenderbufferStorageMultisample")
.add("glFramebufferRenderbuffer")
.add("glIsFramebuffer")
.add("glIsRenderbuffer")
.add("glBindBufferBase");
final var gl31 = RedirectMap.newMap()
.add("glDrawElementsInstanced")
.add("glDrawArraysInstanced")
.add("glCopyBufferSubData");
.add("glCopyBufferSubData")
.add("glPrimitiveRestartIndex")
.add("glGetUniformBlockIndex")
.add("glUniformBlockBinding");
final var gl32 = RedirectMap.newMap()
.add("glFramebufferTexture");
.add("glFramebufferTexture")
.add("glDrawRangeElementsBaseVertex")
.add("glDrawElementsBaseVertex");
final var gl33 = RedirectMap.newMap()
.add("glGenSamplers")
.add("glDeleteSamplers")
.add("glIsSampler")
.add("glBindSampler")
.add("glSamplerParameteri")
.add("glSamplerParameterf")
.add("glVertexAttribDivisor");
.add("glVertexAttribDivisor")
.add("glQueryCounter")
.add("glGetQueryObjectui64");
final var gl42 = RedirectMap.newMap()
.add("glBindImageTexture")
.add("glMemoryBarrier")
@@ -389,10 +536,12 @@ public class GLSMRedirector {
final var gl43 = RedirectMap.newMap()
.add("glDispatchCompute")
.add("glClearBufferSubData")
.add("glClearBufferData")
.add("glBindVertexBuffer")
.add("glVertexAttribFormat")
.add("glVertexAttribIFormat")
.add("glVertexAttribBinding");
.add("glVertexAttribBinding")
.add("glMultiDrawElementsIndirect");
final var gl44 = RedirectMap.newMap()
.add("glBufferStorage")
.add("glClearTexImage");
@@ -400,8 +549,12 @@ public class GLSMRedirector {
.add("glCreateBuffers")
.add("glNamedBufferData")
.add("glNamedBufferSubData");
final var gl46 = RedirectMap.newMap()
.add("glPolygonOffsetClamp")
.add("glMultiDrawArraysIndirectCount")
.add("glMultiDrawElementsIndirectCount")
.add("glSpecializeShader");
// Merge all GL version maps into the flat lookup
glMethodRedirects.putAll(gl11);
glMethodRedirects.putAll(gl12);
glMethodRedirects.putAll(gl13);
@@ -416,6 +569,7 @@ public class GLSMRedirector {
glMethodRedirects.putAll(gl43);
glMethodRedirects.putAll(gl44);
glMethodRedirects.putAll(gl45);
glMethodRedirects.putAll(gl46);
// MINECRAFT
methodRedirects.put(OpenGlHelper, RedirectMap.newMap()
@@ -435,10 +589,73 @@ public class GLSMRedirector {
// ARB
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
.add("glActiveTextureARB")
.add("glClientActiveTextureARB", "glClientActiveTexture")
.add("glMultiTexCoord2fARB", "glMultiTexCoord2f")
);
methodRedirects.put(ARBVertexShader, RedirectMap.newMap()
.add("glGetAttribLocationARB", "glGetAttribLocation")
);
methodRedirects.put(ARBFramebufferObject, RedirectMap.newMap()
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
.add("glCheckFramebufferStatus")
.add("glFramebufferTexture2D")
.add("glBindRenderbuffer")
.add("glDeleteRenderbuffers")
.add("glGenRenderbuffers")
.add("glRenderbufferStorage")
.add("glFramebufferRenderbuffer")
);
methodRedirects.put(EXTFramebufferObject, RedirectMap.newMap()
.add("glBindFramebufferEXT", "glBindFramebuffer")
.add("glDeleteFramebuffersEXT", "glDeleteFramebuffers")
.add("glGenFramebuffersEXT", "glGenFramebuffers")
.add("glCheckFramebufferStatusEXT", "glCheckFramebufferStatus")
.add("glFramebufferTexture2DEXT", "glFramebufferTexture2D")
.add("glBindRenderbufferEXT", "glBindRenderbuffer")
.add("glDeleteRenderbuffersEXT", "glDeleteRenderbuffers")
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
.add("glRenderbufferStorageEXT", "glRenderbufferStorage")
.add("glFramebufferRenderbufferEXT", "glFramebufferRenderbuffer")
);
methodRedirects.put(ARBOcclusionQuery, RedirectMap.newMap()
.add("glGenQueriesARB", "glGenQueries")
.add("glBeginQueryARB", "glBeginQuery")
.add("glEndQueryARB", "glEndQuery")
.add("glGetQueryObjectuARB", "glGetQueryObjectui")
);
methodRedirects.put(ARBTimerQuery, RedirectMap.newMap()
.add("glQueryCounter")
.add("glGetQueryObjectui64")
);
glDescRedirects.put("org/lwjgl/opengl/GL32C glFenceSync(II)J", "glFenceSync");
glDescRedirects.put("org/lwjgl/opengl/GL32C glClientWaitSync(JIJ)I", "glClientWaitSync");
glDescRedirects.put("org/lwjgl/opengl/GL32C glWaitSync(JIJ)V", "glWaitSync");
glDescRedirects.put("org/lwjgl/opengl/GL32C glGetSynci(JILjava/nio/IntBuffer;)I", "glGetSynci");
glDescRedirects.put("org/lwjgl/opengl/GL32C glDeleteSync(J)V", "glDeleteSync");
glDescRedirects.put("org/lwjgl/opengl/GL15C nglMapBuffer(II)J", "nglMapBuffer");
glDescRedirects.put("org/lwjgl/opengl/GL32C nglMultiDrawElementsBaseVertex(IJIJIJ)V", "nglMultiDrawElementsBaseVertex");
for (String o : new String[]{"org/lwjgl/opengl/KHRDebug", "org/lwjgl/opengl/GL43", "org/lwjgl/opengl/GL43C"}) {
glDescRedirects.put(o + " glObjectLabel(IILjava/lang/CharSequence;)V", "glObjectLabel");
glDescRedirects.put(o + " glGetObjectLabel(III)Ljava/lang/String;", "glGetObjectLabel");
glDescRedirects.put(o + " glPushDebugGroup(IILjava/lang/CharSequence;)V", "glPushDebugGroup");
glDescRedirects.put(o + " glPopDebugGroup()V", "glPopDebugGroup");
glDescRedirects.put(o + " glDebugMessageInsert(IIIILjava/lang/CharSequence;)V", "glDebugMessageInsert");
glDescRedirects.put(o + " glDebugMessageControl(IIILjava/nio/IntBuffer;Z)V", "glDebugMessageControl");
glDescRedirects.put(o + " glGetDebugMessageLog(ILjava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/ByteBuffer;)I", "glGetDebugMessageLog");
glDescRedirects.put(o + " glDebugMessageCallback(Lorg/lwjgl/opengl/KHRDebugCallback;)V", "glDebugMessageCallback");
awareOwnerRedirects.put(o + " glDebugMessageCallback(Lorg/lwjgl/opengl/GLDebugMessageCallbackI;J)V", AwareGLDebugShim);
}
awareOwnerRedirects.put("org/lwjgl/opengl/GLUtil setupDebugMessageCallback()Lorg/lwjgl/system/Callback;", AwareGLDebugShim);
awareOwnerRedirects.put("org/lwjgl/opengl/GLUtil setupDebugMessageCallback(Ljava/io/PrintStream;)Lorg/lwjgl/system/Callback;", AwareGLDebugShim);
methodRedirects.put(ARBShaderObjects, RedirectMap.newMap()
.add("glUseProgramObjectARB", "glUseProgram")
.add("glShaderSourceARB", "glShaderSource")
.add("nglShaderSourceARB", "nglShaderSource")
.add("glLinkProgramARB", "glLinkProgram")
.add("glCreateShaderObjectARB", "glCreateShader")
.add("glCompileShaderARB", "glCompileShader")
@@ -476,7 +693,23 @@ public class GLSMRedirector {
methodRedirects.put(ARBVertexArrayObject, RedirectMap.newMap()
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glGenVertexArrays")
);
methodRedirects.put(ARBDrawElementsBaseVertex, RedirectMap.newMap()
.add("glDrawElementsBaseVertex")
);
methodRedirects.put(ARBMultiDrawIndirect, RedirectMap.newMap()
.add("glMultiDrawElementsIndirect")
);
final var uniformBufferObject = RedirectMap.newMap()
.add("glBindBufferBase")
.add("glGetUniformBlockIndex")
.add("glUniformBlockBinding");
methodRedirects.put(ARBUniformBufferObject, uniformBufferObject);
final var gpuShader4 = RedirectMap.newMap()
.add("glVertexAttribIPointerEXT", "glVertexAttribIPointer");
methodRedirects.put(EXTGpuShader4, gpuShader4);
methodRedirects.put(EXTGPUShader4, gpuShader4);
methodRedirects.put(ARBInstancedArrays, RedirectMap.newMap()
.add("glVertexAttribDivisorARB")
);
@@ -517,6 +750,8 @@ public class GLSMRedirector {
// APPLE
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
.add("glBindVertexArrayAPPLE", "glBindVertexArray")
.add("glGenVertexArraysAPPLE", "glGenVertexArrays")
.add("glDeleteVertexArraysAPPLE", "glDeleteVertexArrays")
);
// GTNHLib VAO
@@ -530,12 +765,15 @@ public class GLSMRedirector {
.add("gluPerspective")
.add("gluLookAt")
.add("gluPickMatrix")
.add("gluUnProject")
);
methodRedirects.put(GLU, RedirectMap.newMap()
.add("gluPerspective")
.add("gluLookAt")
.add("gluOrtho2D")
.add("gluPickMatrix")
.add("gluBuild2DMipmaps")
.add("gluErrorString")
);
// Quadric type replacements
@@ -544,33 +782,30 @@ public class GLSMRedirector {
typeRedirects.put("org/lwjgl/util/glu/Disk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaDisk");
typeRedirects.put("org/lwjgl/util/glu/PartialDisk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaPartialDisk");
// Build constant pool scanner
final List<String> stringsToSearch = new ArrayList<>(32);
stringsToSearch.add(GL_PREFIX);
stringsToSearch.add(LWJGL_GL_PACKAGE);
stringsToSearch.add(Drawable);
stringsToSearch.add(SharedDrawable);
stringsToSearch.add(Display);
stringsToSearch.addAll(typeRedirects.keySet());
for (String key : methodRedirects.keySet()) {
stringsToSearch.add(key);
}
stringsToSearch.addAll(methodRedirects.keySet());
cstPoolParser = new ClassConstantPoolParser(stringsToSearch.toArray(new String[0]));
}
/** Core exclusions that GLSM always requires. */
public String[] getCoreExclusions() {
return CORE_EXCLUSIONS.clone();
}
/** The internal-name prefix used to match GL version classes (e.g., {@code "org/lwjgl/opengl/GL"}). */
public static String getGLPrefix() { return GL_PREFIX; }
/** The internal name of the redirect target class. */
public static String getTargetClassName() { return GLStateManager; }
/** Method redirects for GL-prefix classes. Key: original method name, Value: GLStateManager method name. */
/** Key: original method name, value: GLStateManager method name. */
public static Map<String, String> getGLPrefixMethodRedirects() {
return Collections.unmodifiableMap(glMethodRedirects);
}
/** Method redirects for named (non-GL-prefix) classes. Key: internal class name, Value: per-method redirect map. */
/** Key: internal class name, value: per-method redirect map. */
public static Map<String, Map<String, String>> getNamedClassMethodRedirects() {
return Collections.unmodifiableMap(methodRedirects);
}
@@ -579,8 +814,11 @@ public class GLSMRedirector {
return cstPoolParser.find(basicClass, true);
}
/** @return Was the class changed? */
public boolean transformClassNode(String transformedName, ClassNode cn) {
return transformClassNode(transformedName, cn, false);
}
public boolean transformClassNode(String transformedName, ClassNode cn, boolean lwjgl3Aware) {
boolean changed = false;
final boolean isOpenGlHelper = transformedName.equals("net.minecraft.client.renderer.OpenGlHelper");
@@ -588,10 +826,8 @@ public class GLSMRedirector {
if (isOpenGlHelper && (mn.name.equals("glBlendFunc") || mn.name.equals("func_148821_a"))) {
continue;
}
boolean redirectInMethod = false;
for (AbstractInsnNode node : mn.instructions.toArray()) {
if (node instanceof TypeInsnNode tNode) {
// Redirect NEW and CHECKCAST for quadric classes
if (tNode.getOpcode() == Opcodes.NEW || tNode.getOpcode() == Opcodes.CHECKCAST) {
final String redirect = typeRedirects.get(tNode.desc);
if (redirect != null) {
@@ -633,8 +869,7 @@ public class GLSMRedirector {
mn.instructions.remove(prevNode);
}
changed = true;
redirectInMethod = true;
} else if (mNode.name.equals("makeCurrent") && mNode.owner.startsWith(Drawable)) {
} else if (mNode.name.equals("makeCurrent") && isDrawableOwner(mNode.owner)) {
mNode.setOpcode(Opcodes.INVOKESTATIC);
mNode.owner = GLStateManager;
mNode.desc = "(L" + Drawable + ";)V";
@@ -643,10 +878,48 @@ public class GLSMRedirector {
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.makeCurrent()", transformedName);
}
} else if (mNode.name.equals("releaseContext") && isDrawableOwner(mNode.owner)) {
mNode.setOpcode(Opcodes.INVOKESTATIC);
mNode.owner = GLStateManager;
mNode.desc = "(L" + Drawable + ";)V";
mNode.itf = false;
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.releaseContext()", transformedName);
}
} else if (mNode.name.equals("swapBuffers") && mNode.owner.equals(Display) && mNode.desc.equals("()V")) {
mNode.owner = GLStateManager;
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.swapBuffers()", transformedName);
}
} else if (mNode.name.equals("update") && mNode.owner.equals(Display) && (mNode.desc.equals("()V") || mNode.desc.equals("(Z)V"))) {
mNode.owner = GLStateManager;
mNode.name = "updateDisplay";
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.updateDisplay{}", transformedName, mNode.desc);
}
} else if (mNode.name.equals("isCurrent") && mNode.owner.equals(Display) && mNode.desc.equals("()Z")) {
mNode.owner = GLStateManager;
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.isCurrent()", transformedName);
}
} else {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
final String glsmName = redirects != null ? redirects.get(mNode.name) : null;
if (glsmName != null) {
String glsmName = glDescRedirects.get(mNode.owner + " " + mNode.name + mNode.desc);
if (glsmName == null) {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
glsmName = redirects != null ? redirects.get(mNode.name) : null;
}
final String awareOwner = lwjgl3Aware ? awareOwnerRedirects.get(mNode.owner + " " + mNode.name + mNode.desc) : null;
if (awareOwner != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting aware call in {} from {}.{}{} to {}.{}", transformedName, mNode.owner, mNode.name, mNode.desc, awareOwner, mNode.name);
}
mNode.owner = awareOwner;
changed = true;
} else if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
@@ -654,10 +927,14 @@ public class GLSMRedirector {
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
} else if (lwjgl3Aware && mNode.owner.startsWith(LWJGL_GL_PACKAGE) && hasLwjglTypedDescriptor(mNode.desc) && !isAwareNonDispatching(mNode.owner, mNode.name)) {
reportAwareUnroutable(transformedName, mNode.owner, mNode.name, mNode.desc);
} else if (isDebugCall(mNode.owner, mNode.name)) {
reportDebugPassthrough(transformedName, mNode.owner, mNode.name, mNode.desc);
} else if (isUnmappedGlCall(mNode.owner, mNode.name, mNode.desc)) {
reportUnmapped(transformedName, mNode.owner, mNode.name, mNode.desc);
}
}
// Redirect <init> calls for quadric classes
if (mNode.getOpcode() == Opcodes.INVOKESPECIAL && mNode.name.equals("<init>")) {
final String redirect = typeRedirects.get(mNode.owner);
if (redirect != null) {
@@ -672,22 +949,28 @@ public class GLSMRedirector {
// Redirect method handles in invokedynamic bootstrap arguments
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
final String glsmName = redirects != null ? redirects.get(handle.getName()) : null;
if (glsmName == null) continue;
String glsmName = glDescRedirects.get(handle.getOwner() + " " + handle.getName() + handle.getDesc());
if (glsmName == null) {
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
glsmName = redirects != null ? redirects.get(handle.getName()) : null;
}
if (glsmName == null) {
if (isDebugCall(handle.getOwner(), handle.getName())) {
reportDebugPassthrough(transformedName, handle.getOwner(), handle.getName(), handle.getDesc());
} else if (isUnmappedGlCall(handle.getOwner(), handle.getName(), handle.getDesc())) {
reportUnmapped(transformedName, handle.getOwner(), handle.getName(), handle.getDesc());
}
continue;
}
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
LOGGER.info("Redirecting invokedynamic handle in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, handle.getName(), handle.getDesc(), glsmName, handle.getDesc());
}
dynNode.bsmArgs[i] = new Handle(handle.getTag(), GLStateManager, glsmName, handle.getDesc());
changed = true;
redirectInMethod = true;
}
}
}
if (ASSERT_MAIN_THREAD && redirectInMethod && !transformedName.startsWith(SplashProgress) && !(transformedName.startsWith(MinecraftClient) && ExcludedMinecraftMainThreadChecks.contains(mn.name))) {
mn.instructions.insert(new MethodInsnNode(Opcodes.INVOKESTATIC, GLStateManager, "assertMainThread", "()V", false));
}
}
return changed;
@@ -0,0 +1,306 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL20;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLPreParser;
import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public final class GlslVulkanPreprocess {
private static final Logger LOGGER = LogManager.getLogger("GlslVulkanPreprocess");
public static final String SAMPLER_RENAMED = "angelica_sampler_renamed";
public static final String SAMPLERLESS_EXTENSION = "#extension GL_EXT_samplerless_texture_functions : require";
private static final int MAX_VS_INPUT_LOCATIONS = 16;
private static final int CACHE_MAX = 256;
private static final Object2ObjectLinkedOpenHashMap<CacheKey, Result> CACHE = new Object2ObjectLinkedOpenHashMap<>();
private GlslVulkanPreprocess() {}
private record CacheKey(String source, int glShaderType, boolean separateReadOnlyImages) {}
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs) {}
/** Replace {@code [startIdx, stopIdx]} with {@code replacement}. */
public record Edit(int startIdx, int stopIdx, String replacement) {}
public static int startIdx(ParserRuleContext ctx) {
return ctx.getStart().getStartIndex();
}
public static int stopIdx(ParserRuleContext ctx) {
return ctx.getStop().getStopIndex();
}
/** Apply non-overlapping edits to the source in one pass. */
public static String applyEdits(String src, List<Edit> edits) {
if (edits.isEmpty()) return src;
final List<Edit> sorted;
if (edits.size() == 1) {
sorted = edits;
} else {
sorted = new ArrayList<>(edits);
sorted.sort(Comparator.comparingInt(Edit::startIdx).thenComparingInt(Edit::stopIdx));
}
final StringBuilder sb = new StringBuilder(src.length());
int cursor = 0;
for (Edit e : sorted) {
if (e.startIdx < cursor) continue;
sb.append(src, cursor, e.startIdx);
sb.append(e.replacement);
cursor = e.stopIdx + 1;
}
sb.append(src, cursor, src.length());
return sb.toString();
}
/** Returns {@code null} on parse failure — callers treat that the same as a shaderc compile failure. */
public static @Nullable Result run(String source, int glShaderType, String debugName, boolean separateReadOnlyImages) {
final CacheKey key = new CacheKey(source, glShaderType, separateReadOnlyImages);
synchronized (CACHE) {
final Result hit = CACHE.getAndMoveToFirst(key);
if (hit != null) return hit;
}
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(source);
} catch (Exception e) {
LOGGER.warn("glsl-transformation-lib parse failed for '{}': {}", debugName, e.getMessage());
return null;
}
final List<Edit> edits = new ArrayList<>();
final Metadata meta = collectEdits(source, root, glShaderType, debugName, separateReadOnlyImages, edits);
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs());
synchronized (CACHE) {
CACHE.putAndMoveToFirst(key, out);
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
}
return out;
}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs) {}
public static Metadata collectEdits(String source, GLSLParser.Translation_unitContext root, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
final Set<String> bools = new HashSet<>();
final Set<String> explicitInputs = new HashSet<>();
final Set<String> readOnlyImages = new HashSet<>();
final boolean[] needsSamplerless = { false };
final int[] maxExplicitLoc = { -1 };
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
@Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.fully_specified_type() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
final GLSLParser.Type_qualifierContext tq = fst.type_qualifier();
if (tq == null) return;
boolean hasUniform = false, hasIn = false, hasLocation = false, hasReadonly = false;
int locValue = -1;
int bindingValue = -1;
for (GLSLParser.Single_type_qualifierContext stq : tq.single_type_qualifier()) {
if (stq.storage_qualifier() != null) {
final String s = stq.storage_qualifier().getText();
if ("uniform".equals(s)) hasUniform = true;
else if ("in".equals(s)) hasIn = true;
else if ("readonly".equals(s)) hasReadonly = true;
} else if (stq.layout_qualifier() != null) {
for (GLSLParser.Layout_qualifier_idContext id : stq.layout_qualifier().layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() == null) continue;
final String qualifier = id.IDENTIFIER().getText();
if ("location".equals(qualifier)) {
hasLocation = true;
if (id.constant_expression() != null) {
try { locValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
} else if ("binding".equals(qualifier) && id.constant_expression() != null) {
try { bindingValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
}
}
}
final boolean boolDecl = hasUniform && isBool(fst);
final boolean explicitInputDecl = isVertex && hasIn && hasLocation;
if (single.typeless_declaration() != null) {
handleDeclarator(single.typeless_declaration(), hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
handleDeclarator(td, hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
if (separateReadOnlyImages && hasUniform && fst.type_specifier() != null) {
final String typeText = fst.type_specifier().getText();
if (separateTextureType(typeText) != null) {
needsSamplerless[0] = true;
} else if (hasReadonly) {
final String asTexture = separateTextureTypeForImage(typeText);
if (asTexture != null) {
needsSamplerless[0] = true;
collectDeclaredNames(single, ctx, readOnlyImages);
final String binding = bindingValue >= 0 ? "layout(binding = " + bindingValue + ") " : "";
edits.add(new Edit(startIdx(fst), stopIdx(fst), binding + "uniform " + asTexture));
}
}
}
if (isVertex && hasIn) {
if (hasLocation && locValue >= 0) {
if (locValue > maxExplicitLoc[0]) maxExplicitLoc[0] = locValue;
} else if (!hasLocation) {
unlocatedVsInputStarts.add(ctx.getStart().getStartIndex());
}
}
}
@Override
public void enterPostfix_expression(GLSLParser.Postfix_expressionContext ctx) {
if (readOnlyImages.isEmpty() || ctx.LEFT_PAREN() == null || ctx.RIGHT_PAREN() == null) return;
final GLSLParser.Function_call_parametersContext params = ctx.function_call_parameters();
if (params == null || params.assignment_expression() == null || params.assignment_expression().isEmpty()) return;
if (!readOnlyImages.contains(params.assignment_expression(0).getText())) return;
final ParseTree callee = ctx.getChild(0);
if (!(callee instanceof ParserRuleContext calleeCtx)) return;
final String replacement = switch (calleeCtx.getText()) {
case "imageLoad" -> "texelFetch";
case "imageSize" -> "textureSize";
default -> null;
};
if (replacement == null) return;
edits.add(new Edit(startIdx(calleeCtx), stopIdx(calleeCtx), replacement));
final int rparen = ctx.RIGHT_PAREN().getSymbol().getStartIndex();
edits.add(new Edit(rparen, rparen - 1, ", 0"));
}
@Override
public void visitTerminal(TerminalNode node) {
final Token tok = node.getSymbol();
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
final String text = tok.getText();
switch (text) {
case "sampler" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), SAMPLER_RENAMED));
case "gl_VertexID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_VertexIndex"));
case "gl_InstanceID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_InstanceIndex"));
default -> {}
}
}
}, root);
if (needsSamplerless[0]) {
final int at = versionDirectiveEnd(source);
if (at >= 0) edits.add(new Edit(at, at - 1, "\n" + SAMPLERLESS_EXTENSION));
}
if (maxExplicitLoc[0] >= 0 && !unlocatedVsInputStarts.isEmpty()) {
int next = maxExplicitLoc[0] + 1;
int dropped = 0;
for (int start : unlocatedVsInputStarts) {
if (next >= MAX_VS_INPUT_LOCATIONS) { dropped++; continue; }
edits.add(new Edit(start, start - 1, "layout(location = " + next + ") "));
next++;
}
if (dropped > 0) {
LOGGER.warn("'{}': dropped {} VS input location-conflict fixes (would exceed {} attribute slots)", debugName, dropped, MAX_VS_INPUT_LOCATIONS);
}
}
return new Metadata(bools, explicitInputs);
}
public static void clearCache() {
synchronized (CACHE) { CACHE.clear(); }
}
private static void collectDeclaredNames(GLSLParser.Single_declarationContext single, GLSLParser.Init_declarator_listContext ctx, Set<String> out) {
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
out.add(single.typeless_declaration().IDENTIFIER().getText());
}
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
if (td.IDENTIFIER() != null) out.add(td.IDENTIFIER().getText());
}
}
private static @Nullable String separateTextureTypeForImage(String typeText) {
final String dim = switch (typeText) {
case "image1D", "iimage1D", "uimage1D" -> "1D";
case "image2D", "iimage2D", "uimage2D" -> "2D";
case "image3D", "iimage3D", "uimage3D" -> "3D";
default -> null;
};
if (dim == null) return null;
return typeText.charAt(0) == 'i' ? "itexture" + dim : typeText.charAt(0) == 'u' ? "utexture" + dim : "texture" + dim;
}
private static @Nullable String separateTextureType(String typeText) {
return switch (typeText) {
case "texture1D", "itexture1D", "utexture1D",
"texture2D", "itexture2D", "utexture2D",
"texture3D", "itexture3D", "utexture3D" -> typeText;
default -> null;
};
}
private static int versionDirectiveEnd(String source) {
final GLSLPreParser.Translation_unitContext pre;
try {
pre = GlslTransformUtils.parsePreQuiet(source);
} catch (Exception e) {
return -1;
}
final GLSLPreParser.Version_directiveContext ctx = firstVersionDirective(pre);
return ctx == null || ctx.getStop() == null ? -1 : ctx.getStop().getStopIndex() + 1;
}
private static GLSLPreParser.@Nullable Version_directiveContext firstVersionDirective(ParseTree node) {
if (node instanceof GLSLPreParser.Version_directiveContext v) return v;
for (int i = 0; i < node.getChildCount(); i++) {
final GLSLPreParser.Version_directiveContext found = firstVersionDirective(node.getChild(i));
if (found != null) return found;
}
return null;
}
private static void handleDeclarator(GLSLParser.Typeless_declarationContext td, boolean hasUniform, boolean asBool, boolean asInput, Set<String> bools, Set<String> inputs, List<Edit> edits) {
if (td.IDENTIFIER() == null) return;
final String name = td.IDENTIFIER().getText();
if (asBool) bools.add(name);
if (asInput) inputs.add(name);
if (hasUniform && td.EQUAL() != null && td.initializer() != null) {
edits.add(new Edit(td.EQUAL().getSymbol().getStartIndex(), td.initializer().getStop().getStopIndex(), ""));
}
}
private static boolean isBool(GLSLParser.Fully_specified_typeContext fst) {
return fst.type_specifier() != null && fst.type_specifier().type_specifier_nonarray() != null && "bool".equals(fst.type_specifier().type_specifier_nonarray().getText());
}
}
@@ -1,6 +1,6 @@
// This file is based on code from Sodium by JellySquid, licensed under the LGPLv3 license.
package net.coderbot.iris.gl.shader;
package com.gtnewhorizons.angelica.glsm.shader;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
@@ -0,0 +1,183 @@
package com.gtnewhorizons.angelica.glsm.shader;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.util.shaderc.Shaderc;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicInteger;
import static org.lwjgl.system.MemoryUtil.memFree;
import static org.lwjgl.system.MemoryUtil.memUTF8;
/** GLSL -> SPIR-V via shaderc */
@Lwjgl3Aware
public final class SpirvCompiler {
private static final Logger LOGGER = LogManager.getLogger("SpirvCompiler");
private static final ThreadLocal<Long> COMPILER = ThreadLocal.withInitial(() -> {
final long c = Shaderc.shaderc_compiler_initialize();
if (c == 0L) throw new IllegalStateException("shaderc_compiler_initialize failed");
return c;
});
private static final Path FAILURE_DIR = Paths.get("shaderc_failures");
private static final AtomicInteger FAILURE_COUNTER = new AtomicInteger();
private static final int CACHE_MAX = 256;
private static final Object2ObjectLinkedOpenHashMap<CacheKey, byte[]> CACHE = new Object2ObjectLinkedOpenHashMap<>();
private record CacheKey(String source, int shaderKind, Options opts) {}
private SpirvCompiler() {}
public record Options(int targetEnv, int envVersion, boolean vulkanRulesRelaxed, boolean autoBindUniforms, boolean autoMapLocations, boolean autoCombinedImageSampler, int forcedVersion, int forcedProfile) {
public static Options vulkanRelaxed() {
return new Options(Shaderc.shaderc_target_env_vulkan, Shaderc.shaderc_env_version_vulkan_1_0, true, true, true, true, 0, 0);
}
public static Options vulkanForced460Core() {
return new Options(Shaderc.shaderc_target_env_vulkan, Shaderc.shaderc_env_version_vulkan_1_0, true, true, true, false, 460, Shaderc.shaderc_profile_core);
}
}
public record Result(@Nullable ByteBuffer spirv, @Nullable String error, @Nullable Path dumpPath) {}
public static Result compile(String source, int shaderKind, String debugName, Options opts) {
final String cleanSrc = source.indexOf('\0') >= 0 ? source.replace("\0", "") : source;
final CacheKey key = new CacheKey(cleanSrc, shaderKind, opts);
final byte[] cached;
synchronized (CACHE) {
cached = CACHE.getAndMoveToFirst(key);
}
if (cached != null) {
final ByteBuffer copy = MemoryUtil.memAlloc(cached.length);
copy.put(cached).flip();
return new Result(copy, null, null);
}
final long compiler = COMPILER.get();
final long optionsHandle = Shaderc.shaderc_compile_options_initialize();
if (optionsHandle == 0L) {
return new Result(null, "shaderc_compile_options_initialize failed", null);
}
try {
configureOptions(optionsHandle, opts);
ByteBuffer srcBuf = null, nameBuf = null, entryBuf = null;
long result = 0L;
try {
srcBuf = memUTF8(cleanSrc, false);
nameBuf = memUTF8(debugName == null ? "shader" : debugName);
entryBuf = memUTF8("main");
result = Shaderc.shaderc_compile_into_spv(compiler, srcBuf, shaderKind, nameBuf, entryBuf, optionsHandle);
if (result == 0L) {
return new Result(null, "shaderc_compile_into_spv returned null", null);
}
final int status = Shaderc.shaderc_result_get_compilation_status(result);
if (status != Shaderc.shaderc_compilation_status_success) {
final String msg = Shaderc.shaderc_result_get_error_message(result);
final Path dumpPath = writeFailureDump(debugName, shaderKind, msg, cleanSrc);
return new Result(null, msg == null ? "(no error message)" : msg, dumpPath);
}
final ByteBuffer spirv = Shaderc.shaderc_result_get_bytes(result);
if (spirv == null || spirv.remaining() == 0) {
return new Result(null, "shaderc returned empty SPIR-V", null);
}
final ByteBuffer copy = MemoryUtil.memAlloc(spirv.remaining());
copy.put(spirv);
copy.flip();
final byte[] heap = new byte[copy.remaining()];
copy.duplicate().get(heap);
synchronized (CACHE) {
CACHE.putAndMoveToFirst(key, heap);
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
}
return new Result(copy, null, null);
} finally {
if (result != 0L) Shaderc.shaderc_result_release(result);
if (entryBuf != null) memFree(entryBuf);
if (nameBuf != null) memFree(nameBuf);
if (srcBuf != null) memFree(srcBuf);
}
} finally {
Shaderc.shaderc_compile_options_release(optionsHandle);
}
}
public static String preprocess(String source, int shaderKind, String debugName, Options opts) {
final String cleanSrc = source.indexOf('\0') >= 0 ? source.replace("\0", "") : source;
final long compiler = COMPILER.get();
final long optionsHandle = Shaderc.shaderc_compile_options_initialize();
if (optionsHandle == 0L) return null;
try {
configureOptions(optionsHandle, opts);
ByteBuffer srcBuf = null, nameBuf = null, entryBuf = null;
long result = 0L;
try {
srcBuf = memUTF8(cleanSrc, false);
nameBuf = memUTF8(debugName == null ? "shader" : debugName);
entryBuf = memUTF8("main");
result = Shaderc.shaderc_compile_into_preprocessed_text(compiler, srcBuf, shaderKind, nameBuf, entryBuf, optionsHandle);
if (result == 0L) return null;
if (Shaderc.shaderc_result_get_compilation_status(result) != Shaderc.shaderc_compilation_status_success) {
return null;
}
final ByteBuffer text = Shaderc.shaderc_result_get_bytes(result);
if (text == null || text.remaining() == 0) return null;
return MemoryUtil.memUTF8(text);
} finally {
if (result != 0L) Shaderc.shaderc_result_release(result);
if (entryBuf != null) memFree(entryBuf);
if (nameBuf != null) memFree(nameBuf);
if (srcBuf != null) memFree(srcBuf);
}
} finally {
Shaderc.shaderc_compile_options_release(optionsHandle);
}
}
private static void configureOptions(long h, Options o) {
Shaderc.shaderc_compile_options_set_target_env(h, o.targetEnv(), o.envVersion());
Shaderc.shaderc_compile_options_set_source_language(h, Shaderc.shaderc_source_language_glsl);
if (o.vulkanRulesRelaxed()) Shaderc.shaderc_compile_options_set_vulkan_rules_relaxed(h, true);
if (o.autoBindUniforms()) Shaderc.shaderc_compile_options_set_auto_bind_uniforms(h, true);
if (o.autoMapLocations()) Shaderc.shaderc_compile_options_set_auto_map_locations(h, true);
if (o.autoCombinedImageSampler()) Shaderc.shaderc_compile_options_set_auto_combined_image_sampler(h, true);
if (o.forcedVersion() != 0) {
Shaderc.shaderc_compile_options_set_forced_version_profile(h, o.forcedVersion(), o.forcedProfile());
}
}
public static void clearCache() {
synchronized (CACHE) { CACHE.clear(); }
}
private static @Nullable Path writeFailureDump(String debugName, int shaderKind, String msg, String source) {
try {
Files.createDirectories(FAILURE_DIR);
final int n = FAILURE_COUNTER.getAndIncrement();
final Path p = FAILURE_DIR.resolve(String.format("%03d_%s_kind0x%s.glsl", n, debugName == null ? "shader" : debugName, Integer.toHexString(shaderKind)));
final StringBuilder sb = new StringBuilder();
sb.append("// shaderc errors:\n");
if (msg != null) for (String line : msg.split("\\R")) sb.append("// ").append(line).append('\n');
sb.append('\n').append(source);
Files.writeString(p, sb.toString());
return p;
} catch (IOException e) {
LOGGER.warn("Failed to write shaderc failure dump for '{}': {}", debugName, e.getMessage());
return null;
}
}
}
@@ -0,0 +1,420 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.util.shaderc.Shaderc;
import org.lwjgl.util.spvc.Spvc;
import org.lwjgl.util.spvc.SpvcReflectedResource;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.stopIdx;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.memFree;
/** GLSL 330 core -> SPIR-V -> GLSL ES 320 via shaderc + SPIRV-Cross */
@Lwjgl3Aware
public final class SpirvShaderTranslator {
private static final Logger LOGGER = LogManager.getLogger("SpirvShaderTranslator");
private static final int SPV_DECORATION_LOCATION = 30;
private static final int SPV_DECORATION_BINDING = 33;
private SpirvShaderTranslator() {}
public static @Nullable String glslToGlslEs(String source, int glShaderType, String debugName) {
return glslToGlslEsImpl(source, glShaderType, debugName, false);
}
static @Nullable String glslToGlslEsRawForTest(String source, int glShaderType, String debugName) {
return glslToGlslEsImpl(source, glShaderType, debugName, true);
}
private static @Nullable String glslToGlslEsImpl(String source, int glShaderType, String debugName, boolean rawOutput) {
final int shaderKind = shaderKindForGlType(glShaderType);
if (shaderKind < 0) {
LOGGER.warn("Unknown GL shader type 0x{} for '{}' - skipping SPIR-V translation", Integer.toHexString(glShaderType), debugName);
return null;
}
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(source, glShaderType, debugName, false);
if (pre == null) return null;
final SpirvCompiler.Result cr = SpirvCompiler.compile(pre.rewrittenSource(), shaderKind, debugName, SpirvCompiler.Options.vulkanRelaxed());
if (cr.spirv() == null) {
final String firstErr = cr.error() == null ? "(no error)" : cr.error().split("\\R", 2)[0];
LOGGER.error("shaderc failed for '{}' (kind=0x{}): {} (dumped to {})",
debugName, Integer.toHexString(shaderKind), firstErr, cr.dumpPath() == null ? "<dump failed>" : cr.dumpPath());
return null;
}
try {
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
if (emitted == null) return null;
return rawOutput ? emitted : postProcessEsOutput(emitted, pre.boolUniforms());
} finally {
memFree(cr.spirv());
}
}
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames) {
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(glslEs);
} catch (Exception e) {
LOGGER.error("Failed to parse spvc ES output for post-processing: {}", e.getMessage());
return null;
}
final DefaultBlockInfo block = findDefaultBlock(root);
final List<Edit> edits = new ArrayList<>();
if (block != null) {
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), buildLooseUniforms(block.members, boolUniformNames)));
edits.add(new Edit(startIdx(block.uniformExternalDecl), stopIdx(block.uniformExternalDecl), ""));
}
final UseSiteRewriter rewriter = new UseSiteRewriter(block, boolUniformNames, edits);
ParseTreeWalker.DEFAULT.walk(rewriter, root);
return GlslVulkanPreprocess.applyEdits(glslEs, edits);
}
private record DefaultBlockInfo(ParserRuleContext structExternalDecl, ParserRuleContext uniformExternalDecl, String blockVarName, List<MemberInfo> members) {}
private record MemberInfo(String typePrefix, String name) {}
private static String buildLooseUniforms(List<MemberInfo> members, Set<String> boolNames) {
final StringBuilder sb = new StringBuilder();
for (MemberInfo m : members) {
if (boolNames.contains(m.name)) {
sb.append("uniform bool ").append(m.name).append(";\n");
} else {
sb.append("uniform ").append(m.typePrefix).append(' ').append(m.name).append(";\n");
}
}
if (!sb.isEmpty() && sb.charAt(sb.length() - 1) == '\n') sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
private static @Nullable DefaultBlockInfo findDefaultBlock(GLSLParser.Translation_unitContext root) {
final DefaultBlockFinder finder = new DefaultBlockFinder();
ParseTreeWalker.DEFAULT.walk(finder, root);
if (finder.structExternalDecl == null || finder.uniformExternalDecl == null) return null;
return new DefaultBlockInfo(finder.structExternalDecl, finder.uniformExternalDecl, finder.blockVarName, finder.members);
}
private static final class DefaultBlockFinder extends GLSLParserBaseListener {
String structName;
ParserRuleContext structExternalDecl;
ParserRuleContext uniformExternalDecl;
String blockVarName;
final List<MemberInfo> members = new ArrayList<>();
@Override
public void enterStruct_specifier(GLSLParser.Struct_specifierContext ctx) {
if (structExternalDecl != null) return;
final TerminalNode id = ctx.IDENTIFIER();
if (id == null) return;
final String idText = id.getText();
if (!idText.startsWith("_RESERVED_IDENTIFIER_FIXUP_") || !idText.endsWith("DefaultUniformBlock")) return;
structName = idText;
structExternalDecl = findExternalDeclaration(ctx);
for (GLSLParser.Struct_declarationContext sd : ctx.struct_declaration_list().struct_declaration()) {
final StringBuilder typePrefix = new StringBuilder();
if (sd.type_qualifier() != null) typePrefix.append(textWithSpaces(sd.type_qualifier())).append(' ');
typePrefix.append(textWithSpaces(sd.type_specifier()));
for (GLSLParser.Struct_declaratorContext decl : sd.struct_declarator_list().struct_declarator()) {
final String arraySuffix = decl.array_specifier() == null ? "" : decl.array_specifier().getText();
members.add(new MemberInfo(typePrefix + arraySuffix, decl.IDENTIFIER().getText()));
}
}
}
@Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
if (structName == null || uniformExternalDecl != null) return;
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.typeless_declaration() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
if (fst == null || fst.type_qualifier() == null) return;
boolean isUniform = false;
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
isUniform = true;
break;
}
}
if (!isUniform) return;
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
if (ts.type_name() == null) return;
if (!structName.equals(ts.type_name().getText())) return;
blockVarName = single.typeless_declaration().IDENTIFIER().getText();
uniformExternalDecl = findExternalDeclaration(ctx);
}
private static String textWithSpaces(ParseTree tree) {
final StringBuilder sb = new StringBuilder();
appendTextWithSpaces(tree, sb);
return sb.toString().trim();
}
private static void appendTextWithSpaces(ParseTree tree, StringBuilder sb) {
if (tree instanceof TerminalNode) {
if (!sb.isEmpty() && sb.charAt(sb.length() - 1) != ' ') sb.append(' ');
sb.append(tree.getText());
return;
}
for (int i = 0; i < tree.getChildCount(); i++) {
appendTextWithSpaces(tree.getChild(i), sb);
}
}
}
private static ParserRuleContext findExternalDeclaration(ParserRuleContext ctx) {
ParseTree cur = ctx;
while (cur != null && !(cur instanceof GLSLParser.External_declarationContext)) {
cur = cur.getParent();
}
return (ParserRuleContext) cur;
}
private static final class UseSiteRewriter extends GLSLParserBaseListener {
private final @Nullable DefaultBlockInfo block;
private final Set<String> boolNames;
private final List<Edit> edits;
private final Set<String> memberNames;
UseSiteRewriter(@Nullable DefaultBlockInfo block, Set<String> boolNames, List<Edit> edits) {
this.block = block;
this.boolNames = boolNames;
this.edits = edits;
this.memberNames = new HashSet<>();
if (block != null) for (MemberInfo m : block.members) memberNames.add(m.name);
}
@Override
public void enterBinary_expression(GLSLParser.Binary_expressionContext ctx) {
if (ctx.getChildCount() != 3) return;
final ParseTree opNode = ctx.getChild(1);
if (!(opNode instanceof TerminalNode op)) return;
final String opText = op.getText();
final boolean isNe = "!=".equals(opText);
final boolean isEq = "==".equals(opText);
if (!isNe && !isEq) return;
final ParseTree rhs = ctx.getChild(2);
if (!"0u".equals(rhs.getText())) return;
final ParseTree lhs = ctx.getChild(0);
final String nameIfBool = resolveBoolOperand(lhs);
if (nameIfBool == null) return;
final String replacement = isNe ? nameIfBool : "!" + nameIfBool;
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), replacement));
}
@Override
public void enterPostfix_expression(GLSLParser.Postfix_expressionContext ctx) {
if (ctx.type_specifier() != null && ctx.LEFT_PAREN() != null
&& "bool".equals(ctx.type_specifier().getText())
&& ctx.function_call_parameters() != null
&& ctx.function_call_parameters().assignment_expression() != null
&& ctx.function_call_parameters().assignment_expression().size() == 1) {
final String nameIfBool = resolveBoolOperand(ctx.function_call_parameters().assignment_expression(0));
if (nameIfBool != null) {
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), nameIfBool));
return;
}
}
if (block != null && ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
final ParseTree inner = ctx.getChild(0);
if (inner.getText().equals(block.blockVarName)) {
final String member = ctx.field_selection().getText();
if (memberNames.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), member));
}
}
}
}
private @Nullable String resolveBoolOperand(ParseTree operand) {
final String text = operand.getText();
if (boolNames.contains(text)) return text;
if (block != null && text.startsWith(block.blockVarName + ".")) {
final String member = text.substring(block.blockVarName.length() + 1);
if (boolNames.contains(member) && memberNames.contains(member)) return member;
}
return null;
}
private boolean coveredByAncestorBoolCompare(GLSLParser.Postfix_expressionContext ctx) {
final int start = startIdx(ctx), stop = stopIdx(ctx);
for (Edit e : edits) {
if (e.startIdx() <= start && stop <= e.stopIdx() && e.stopIdx() > stop) return true;
}
return false;
}
}
private static @Nullable String emitGlslEs(ByteBuffer spirv, String debugName, int glShaderType, Set<String> explicitVsInputs) {
try (MemoryStack stack = stackPush()) {
final PointerBuffer pContext = stack.pointers(0);
if (Spvc.spvc_context_create(pContext) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_context_create failed for '{}'", debugName);
return null;
}
final long ctx = pContext.get(0);
try {
final IntBuffer spirvWords = spirv.asIntBuffer();
final PointerBuffer pIr = stack.pointers(0);
if (Spvc.spvc_context_parse_spirv(ctx, spirvWords, spirvWords.remaining(), pIr) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_context_parse_spirv failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
final PointerBuffer pCompiler = stack.pointers(0);
if (Spvc.spvc_context_create_compiler(ctx, Spvc.SPVC_BACKEND_GLSL, pIr.get(0), Spvc.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, pCompiler) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_context_create_compiler(GLSL) failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
final long compiler = pCompiler.get(0);
final PointerBuffer pOptions = stack.pointers(0);
if (Spvc.spvc_compiler_create_compiler_options(compiler, pOptions) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_compiler_create_compiler_options failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
final long options = pOptions.get(0);
Spvc.spvc_compiler_options_set_uint(options, Spvc.SPVC_COMPILER_OPTION_GLSL_VERSION, 320);
Spvc.spvc_compiler_options_set_bool(options, Spvc.SPVC_COMPILER_OPTION_GLSL_ES, true);
Spvc.spvc_compiler_options_set_bool(options, Spvc.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, true);
if (Spvc.spvc_compiler_install_compiler_options(compiler, options) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_compiler_install_compiler_options failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
applyResourceDecorationFixups(ctx, compiler, glShaderType, explicitVsInputs, debugName);
final PointerBuffer pSource = stack.pointers(0);
if (Spvc.spvc_compiler_compile(compiler, pSource) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_compiler_compile failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
final long sourcePtr = pSource.get(0);
if (sourcePtr == 0L) {
LOGGER.error("spvc_compiler_compile returned NULL source for '{}'", debugName);
return null;
}
return MemoryUtil.memUTF8(sourcePtr);
} finally {
Spvc.spvc_context_destroy(ctx);
}
}
}
private static void applyResourceDecorationFixups(long ctx, long compiler, int glShaderType, Set<String> explicitVsInputs, String debugName) {
if (glShaderType == GL20.GL_VERTEX_SHADER) {
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_OUTPUT, SPV_DECORATION_LOCATION, null, debugName);
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_INPUT, SPV_DECORATION_LOCATION, explicitVsInputs, debugName);
} else if (glShaderType == GL20.GL_FRAGMENT_SHADER) {
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_INPUT, SPV_DECORATION_LOCATION, null, debugName);
} else if (glShaderType == GL32.GL_GEOMETRY_SHADER || glShaderType == GL40.GL_TESS_CONTROL_SHADER || glShaderType == GL40.GL_TESS_EVALUATION_SHADER) {
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_OUTPUT, SPV_DECORATION_LOCATION, null, debugName);
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_INPUT, SPV_DECORATION_LOCATION, null, debugName);
}
if (glShaderType != GL43.GL_COMPUTE_SHADER) {
for (int resourceType : BINDING_STRIP_RESOURCE_TYPES) {
unsetDecorationForResourceType(ctx, compiler, resourceType, SPV_DECORATION_BINDING, null, debugName);
}
}
}
private static final int[] BINDING_STRIP_RESOURCE_TYPES = {
Spvc.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
Spvc.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
Spvc.SPVC_RESOURCE_TYPE_SEPARATE_IMAGE,
Spvc.SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
Spvc.SPVC_RESOURCE_TYPE_STORAGE_IMAGE,
};
private static void unsetDecorationForResourceType(long ctx, long compiler, int resourceType, int decoration, Set<String> preserve, String debugName) {
try (MemoryStack stack = stackPush()) {
final PointerBuffer pResources = stack.pointers(0);
if (Spvc.spvc_compiler_create_shader_resources(compiler, pResources) != Spvc.SPVC_SUCCESS) {
LOGGER.warn("spvc_compiler_create_shader_resources failed for '{}': {}", debugName, lastSpvcError(ctx));
return;
}
final long resources = pResources.get(0);
final PointerBuffer pList = stack.pointers(0);
final PointerBuffer pCount = stack.pointers(0);
if (Spvc.spvc_resources_get_resource_list_for_type(resources, resourceType, pList, pCount) != Spvc.SPVC_SUCCESS) {
LOGGER.warn("spvc_resources_get_resource_list_for_type failed for '{}': {}", debugName, lastSpvcError(ctx));
return;
}
final long listAddr = pList.get(0);
final long count = pCount.get(0);
if (listAddr == 0L || count <= 0L) return;
final SpvcReflectedResource.Buffer list = SpvcReflectedResource.create(listAddr, (int) count);
for (int i = 0; i < list.remaining(); i++) {
final SpvcReflectedResource res = list.get(i);
if (preserve != null) {
final String name = res.nameString();
if (name != null && preserve.contains(name)) continue;
}
Spvc.spvc_compiler_unset_decoration(compiler, res.id(), decoration);
}
}
}
private static String lastSpvcError(long ctx) {
final String msg = Spvc.spvc_context_get_last_error_string(ctx);
return msg == null || msg.isEmpty() ? "(no error message)" : msg;
}
private static int shaderKindForGlType(int glShaderType) {
return switch (glShaderType) {
case GL20.GL_VERTEX_SHADER -> Shaderc.shaderc_vertex_shader;
case GL20.GL_FRAGMENT_SHADER -> Shaderc.shaderc_fragment_shader;
case GL32.GL_GEOMETRY_SHADER -> Shaderc.shaderc_geometry_shader;
case GL40.GL_TESS_CONTROL_SHADER -> Shaderc.shaderc_tess_control_shader;
case GL40.GL_TESS_EVALUATION_SHADER -> Shaderc.shaderc_tess_evaluation_shader;
case GL43.GL_COMPUTE_SHADER -> Shaderc.shaderc_compute_shader;
default -> -1;
};
}
}
@@ -1,4 +1,4 @@
package net.coderbot.iris.gl.uniform;
package com.gtnewhorizons.angelica.glsm.shader;
public enum UniformType {
INT,
@@ -38,4 +38,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -43,4 +43,8 @@ public class Color4Stack extends Color4 implements IStateStack<Color4Stack> {
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -38,4 +38,8 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -38,4 +38,8 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -45,4 +45,8 @@ public class IntegerStateStack extends IntegerState implements IStateStack<Integ
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -39,4 +39,8 @@ public class LineStateStack extends LineState implements IStateStack<LineStateSt
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -39,4 +39,8 @@ public class PointStateStack extends PointState implements IStateStack<PointStat
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -39,4 +39,8 @@ public class PolygonStateStack extends PolygonState implements IStateStack<Polyg
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -39,4 +39,8 @@ public class StencilStateStack extends StencilState implements IStateStack<Stenc
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -42,4 +42,8 @@ public class TextureBindingStack extends TextureBinding implements IStateStack<T
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import org.lwjgl.opengl.GL13;
/**
@@ -37,10 +38,16 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
}
private void setEnabledWithUnitSwitch(boolean enabled) {
final boolean bypass = GLStateManager.shouldBypassCache();
final boolean bypass = !GLStateManager.isCachingEnabled();
if (bypass || enabled != this.enabled) {
if (!bypass) this.enabled = enabled;
if (GLSMHooks.TEXTURE_UNIT_STATE.hasListeners()) {
GLSMHooks.textureUnitStateEvent.unit = unitIndex;
GLSMHooks.textureUnitStateEvent.enabled = enabled;
GLSMHooks.TEXTURE_UNIT_STATE.post(GLSMHooks.textureUnitStateEvent);
}
if (!ffpStateOnly) {
final int currentUnit = GLStateManager.getActiveTextureUnit();
final boolean needsSwitch = currentUnit != unitIndex;
@@ -38,4 +38,8 @@ public class ViewPortStateStack extends ViewportState implements IStateStack<Vie
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
}
}
@@ -1,4 +1,4 @@
package net.coderbot.iris.gl.blending;
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
@@ -22,6 +22,15 @@ public class BooleanState implements ISettableState<BooleanState> {
this.ffpStateOnly = ffpStateOnly;
}
public void applyToBackend() {
if (ffpStateOnly) return;
if (enabled) {
RENDER_BACKEND.enable(glCap);
} else {
RENDER_BACKEND.disable(glCap);
}
}
public void setUnknownState() {
stateUnknown = true;
}
@@ -35,12 +44,10 @@ public class BooleanState implements ISettableState<BooleanState> {
}
public void setEnabled(boolean enabled) {
final boolean bypass = GLStateManager.shouldBypassCache();
final boolean bypass = !GLStateManager.isCachingEnabled();
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
stateUnknown = false;
if (!bypass) {
this.enabled = enabled;
}
this.enabled = enabled;
if (!ffpStateOnly) {
if (enabled) {
RENDER_BACKEND.enable(this.glCap);
@@ -0,0 +1,33 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
public class ImageUnitArray {
private static final int MIN_UNITS = 8;
private volatile ImageUnitBinding[] bindings;
public ImageUnitBinding get(int unit) {
if (unit < 0) return null;
ImageUnitBinding[] local = bindings;
if (local == null) local = allocate();
return unit < local.length ? local[unit] : null;
}
public int size() {
final ImageUnitBinding[] local = bindings;
return local == null ? 0 : local.length;
}
private synchronized ImageUnitBinding[] allocate() {
ImageUnitBinding[] local = bindings;
if (local == null) {
final int count = Math.max(MIN_UNITS, RenderSystem.getMaxImageUnits());
local = new ImageUnitBinding[count];
for (int i = 0; i < count; i++) local[i] = new ImageUnitBinding();
bindings = local;
}
return local;
}
}
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.glsm.states;
import lombok.Getter;
public class ImageUnitBinding implements ISettableState<ImageUnitBinding> {
@Getter protected int texture;
@Getter protected int level;
@Getter protected boolean layered;
@Getter protected int layer;
@Getter protected int access;
@Getter protected int format;
public void setBinding(int texture, int level, boolean layered, int layer, int access, int format) {
this.texture = texture;
this.level = level;
this.layered = layered;
this.layer = layer;
this.access = access;
this.format = format;
}
@Override
public ImageUnitBinding set(ImageUnitBinding state) {
this.texture = state.texture;
this.level = state.level;
this.layered = state.layered;
this.layer = state.layer;
this.access = state.access;
this.format = state.format;
return this;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
if (!(state instanceof ImageUnitBinding other)) return false;
return texture == other.texture && level == other.level && layered == other.layered && layer == other.layer && access == other.access && format == other.format;
}
@Override
public ImageUnitBinding copy() {
return new ImageUnitBinding().set(this);
}
}
@@ -29,7 +29,7 @@ public class LightModelState implements ISettableState<LightModelState> {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
@@ -39,7 +39,7 @@ public class LightModelState implements ISettableState<LightModelState> {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
@@ -47,7 +47,7 @@ public class LightModelState implements ISettableState<LightModelState> {
}
public boolean setColorControl(int val) {
if (GLStateManager.shouldBypassCache() || this.colorControl != val) {
if (!GLStateManager.isCachingEnabled() || this.colorControl != val) {
this.colorControl = val;
return true;
}
@@ -59,7 +59,7 @@ public class LightModelState implements ISettableState<LightModelState> {
}
public boolean setLocalViewer(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.localViewer, val) != 0) {
if (!GLStateManager.isCachingEnabled() || Float.compare(this.localViewer, val) != 0) {
this.localViewer = val;
return true;
}
@@ -79,7 +79,7 @@ public class LightModelState implements ISettableState<LightModelState> {
}
public boolean setTwoSide(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.twoSide, val) != 0) {
if (!GLStateManager.isCachingEnabled() || Float.compare(this.twoSide, val) != 0) {
this.twoSide = val;
return true;
}
@@ -74,18 +74,18 @@ public class LightState implements ISettableState<LightState> {
this.rawPosition = new Vector4f(position);
this.position = position;
this.position.mul(GLStateManager.getModelViewMatrix());
this.posMvGeneration = GLStateManager.mvGeneration;
this.posMvGeneration = GLStateManager.getMvGeneration();
this.rawSpotDirection = new Vector3f(spotDirection);
this.spotDirection = spotDirection;
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.mul(matrix3f);
this.spotDirLinearGeneration = GLStateManager.mvLinearGeneration;
this.spotDirLinearGeneration = GLStateManager.getMvLinearGeneration();
}
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
@@ -95,7 +95,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
@@ -104,7 +104,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
@@ -114,7 +114,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
@@ -123,7 +123,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
@@ -133,7 +133,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
@@ -152,8 +152,8 @@ public class LightState implements ISettableState<LightState> {
}
private boolean applyPosition(Vector4f raw) {
final int mvGen = GLStateManager.mvGeneration;
if (GLStateManager.shouldBypassCache() || this.posMvGeneration != mvGen || !this.rawPosition.equals(raw)) {
final int mvGen = GLStateManager.getMvGeneration();
if (!GLStateManager.isCachingEnabled() || this.posMvGeneration != mvGen || !this.rawPosition.equals(raw)) {
this.rawPosition.set(raw);
this.posMvGeneration = mvGen;
this.position.set(raw);
@@ -175,8 +175,8 @@ public class LightState implements ISettableState<LightState> {
}
private boolean applySpotDirection(Vector3f raw) {
final int mvLinearGen = GLStateManager.mvLinearGeneration;
if (GLStateManager.shouldBypassCache() || this.spotDirLinearGeneration != mvLinearGen || !this.rawSpotDirection.equals(raw)) {
final int mvLinearGen = GLStateManager.getMvLinearGeneration();
if (!GLStateManager.isCachingEnabled() || this.spotDirLinearGeneration != mvLinearGen || !this.rawSpotDirection.equals(raw)) {
this.rawSpotDirection.set(raw);
this.spotDirLinearGeneration = mvLinearGen;
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
@@ -200,7 +200,7 @@ public class LightState implements ISettableState<LightState> {
}
public boolean setSpotExponent(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotExponent) != 0) {
if (!GLStateManager.isCachingEnabled() || Float.compare(f, this.spotExponent) != 0) {
this.spotExponent = f;
return true;
}
@@ -220,7 +220,7 @@ public class LightState implements ISettableState<LightState> {
}
public boolean setSpotCutoff(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotCutoff) != 0) {
if (!GLStateManager.isCachingEnabled() || Float.compare(f, this.spotCutoff) != 0) {
this.spotCutoff = f;
this.spotCosCutoff = (float) Math.cos(Math.toRadians(f));
return true;
@@ -241,7 +241,7 @@ public class LightState implements ISettableState<LightState> {
}
public boolean setConstantAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.constantAttenuation) != 0) {
if (!GLStateManager.isCachingEnabled() || Float.compare(f, this.constantAttenuation) != 0) {
this.constantAttenuation = f;
return true;
}
@@ -261,7 +261,7 @@ public class LightState implements ISettableState<LightState> {
}
public boolean setLinearAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.linearAttenuation) != 0) {
if (!GLStateManager.isCachingEnabled() || Float.compare(f, this.linearAttenuation) != 0) {
this.linearAttenuation = f;
return true;
}
@@ -281,7 +281,7 @@ public class LightState implements ISettableState<LightState> {
}
public boolean setQuadraticAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.quadraticAttenuation) != 0) {
if (!GLStateManager.isCachingEnabled() || Float.compare(f, this.quadraticAttenuation) != 0) {
this.quadraticAttenuation = f;
return true;
}
@@ -39,7 +39,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
@@ -49,7 +49,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
@@ -58,7 +58,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
@@ -68,7 +68,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
@@ -77,7 +77,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
@@ -87,7 +87,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
@@ -96,7 +96,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setEmission(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f);
return true;
}
@@ -106,7 +106,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setEmission(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
if (!GLStateManager.isCachingEnabled() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f);
return true;
}
@@ -114,7 +114,7 @@ public class MaterialState implements ISettableState<MaterialState> {
}
public boolean setShininess(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.shininess, val) != 0) {
if (!GLStateManager.isCachingEnabled() || Float.compare(this.shininess, val) != 0) {
this.shininess = val;
return true;
}
@@ -141,7 +141,7 @@ public class MaterialState implements ISettableState<MaterialState> {
vector3f.set(newBuffer);
}
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
if (!GLStateManager.isCachingEnabled() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f);
return true;
}
@@ -151,7 +151,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setColorIndexes(IntBuffer newBuffer) {
vector3i.set(newBuffer.get(0), newBuffer.get(2), newBuffer.get(1));
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
if (!GLStateManager.isCachingEnabled() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f);
return true;
}
@@ -14,12 +14,7 @@ public class MatrixMode implements ISettableState<MatrixMode> {
return;
}
final boolean bypass = GLStateManager.shouldBypassCache();
if (bypass || this.mode != mode) {
if (!bypass) {
this.mode = mode;
}
}
this.mode = mode;
}
public int getMatrix() {
@@ -14,6 +14,7 @@ public class PolygonState implements ISettableState<PolygonState> {
private int backMode = GL11.GL_FILL;
private float offsetFactor = 0.0f;
private float offsetUnits = 0.0f;
private float offsetClamp = 0.0f;
private int cullFaceMode = GL11.GL_BACK;
private int frontFace = GL11.GL_CCW;
@@ -23,6 +24,7 @@ public class PolygonState implements ISettableState<PolygonState> {
this.backMode = state.backMode;
this.offsetFactor = state.offsetFactor;
this.offsetUnits = state.offsetUnits;
this.offsetClamp = state.offsetClamp;
this.cullFaceMode = state.cullFaceMode;
this.frontFace = state.frontFace;
return this;
@@ -36,6 +38,7 @@ public class PolygonState implements ISettableState<PolygonState> {
&& backMode == polygonState.backMode
&& Float.compare(offsetFactor, polygonState.offsetFactor) == 0
&& Float.compare(offsetUnits, polygonState.offsetUnits) == 0
&& Float.compare(offsetClamp, polygonState.offsetClamp) == 0
&& cullFaceMode == polygonState.cullFaceMode
&& frontFace == polygonState.frontFace;
}
@@ -1,15 +1,38 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import lombok.Getter;
import lombok.Setter;
@Getter
public class TextureBinding implements ISettableState<TextureBinding> {
@Setter protected int binding;
@Getter protected int binding;
@Getter protected int target;
protected TextureInfo info;
public void setBinding(int binding) {
setBinding(binding, 0);
}
public void setBinding(int binding, int target) {
this.binding = binding;
this.target = target;
this.info = null;
}
public TextureInfo getOrResolveInfo() {
TextureInfo resolved = info;
if (resolved == null) {
resolved = TextureInfoCache.INSTANCE.getInfo(binding);
info = resolved;
}
return resolved;
}
@Override
public TextureBinding set(TextureBinding state) {
this.binding = state.binding;
this.target = state.target;
this.info = null;
return this;
}
@@ -29,6 +29,13 @@ public class ViewportState implements ISettableState<ViewportState> {
params.put(3, height);
}
public void get(int[] params) {
params[0] = x;
params[1] = y;
params[2] = width;
params[3] = height;
}
@Override
public ViewportState set(ViewportState state) {
this.x = state.x;
@@ -0,0 +1,172 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Persistent-mapped fence-reclaimed ring over any buffer target. Callers reserve a range and
* memCopy into address() + offset themselves.
*/
final class GlStreamingRing {
private final int target;
private final int capacity;
private final ByteBuffer mappedBuffer;
private final long mappedAddress;
private int bufferId;
private static final long SYNC_WAIT_SLICE_NANOS = 10_000_000L;
private static final long SYNC_WAIT_TOTAL_NANOS = 5_000_000_000L;
private final ObjectArrayFIFOQueue<FencedRegion> fenceQueue = new ObjectArrayFIFOQueue<>();
private int writePos;
private int remaining;
private int pendingBytes;
private int wraps;
private int fencesIssued;
private int forcedReclaims;
private GlStreamingRing(int target, int capacity, int bufferId, ByteBuffer mappedBuffer) {
this.target = target;
this.capacity = capacity;
this.bufferId = bufferId;
this.mappedBuffer = mappedBuffer;
this.mappedAddress = memAddress0(mappedBuffer);
this.remaining = capacity;
}
static GlStreamingRing create(int target, int capacity) {
return create(target, capacity, SystemProperties.FORCE_ORPHAN_STREAMING);
}
static GlStreamingRing create(int target, int capacity, boolean forceOrphan) {
if (forceOrphan || !RenderSystem.supportsBufferStorage()) return null;
final int id = RENDER_BACKEND.genBuffers();
RENDER_BACKEND.bindBuffer(target, id);
final int storageFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT | GL44.GL_CLIENT_STORAGE_BIT;
RenderSystem.bufferStorage(target, capacity, storageFlags);
final int mapFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT;
final ByteBuffer mapped = RENDER_BACKEND.mapBufferRange(target, 0, capacity, mapFlags);
RENDER_BACKEND.bindBuffer(target, 0);
if (mapped == null) {
GLStateManager.glDeleteBuffers(id);
GLStateManager.LOGGER.warn("Persistent map failed for streaming ring (target=0x{}, {} bytes)", Integer.toHexString(target), capacity);
return null;
}
GLStateManager.LOGGER.info("Streaming ring created (target=0x{}, {}KB)", Integer.toHexString(target), capacity / 1024);
return new GlStreamingRing(target, capacity, id, mapped);
}
/** Byte offset of a reserved range, or -1 when it can never be served. */
long reserve(int bytes, int alignment) {
if (bytes > capacity) return -1;
if (remaining < capacity / 2) {
reclaim();
}
int offset = (writePos + alignment - 1) / alignment * alignment;
int waste = offset - writePos;
if (offset + bytes > capacity) {
waste = capacity - writePos;
offset = 0;
wraps++;
}
if (!ensureRemaining(waste + bytes)) return -1;
writePos = offset + bytes;
remaining -= waste + bytes;
pendingBytes += waste + bytes;
if (pendingBytes >= capacity / 4) {
fence();
}
return offset;
}
private boolean ensureRemaining(int needed) {
while (remaining < needed) {
if (fenceQueue.isEmpty()) {
if (pendingBytes == 0) return false;
fence();
}
syncOldest();
}
return true;
}
private void fence() {
if (pendingBytes > 0) {
fenceQueue.enqueue(new FencedRegion(RENDER_BACKEND.fenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0), pendingBytes));
pendingBytes = 0;
fencesIssued++;
}
}
private static boolean signaled(int status) {
return status == GL32.GL_ALREADY_SIGNALED || status == GL32.GL_CONDITION_SATISFIED;
}
private void reclaim() {
while (!fenceQueue.isEmpty()) {
final FencedRegion region = fenceQueue.first();
if (!signaled(RENDER_BACKEND.clientWaitSync(region.fenceId, 0, 0L))) break;
GLStateManager.glDeleteSync(region.fenceId);
fenceQueue.dequeue();
remaining += region.bytes;
}
}
private void syncOldest() {
final FencedRegion region = fenceQueue.dequeue();
forcedReclaims++;
final long deadline = System.nanoTime() + SYNC_WAIT_TOTAL_NANOS;
long remainingNanos = SYNC_WAIT_TOTAL_NANOS;
int status;
do {
status = RENDER_BACKEND.clientWaitSync(region.fenceId, GL32.GL_SYNC_FLUSH_COMMANDS_BIT, Math.min(SYNC_WAIT_SLICE_NANOS, remainingNanos));
remainingNanos = deadline - System.nanoTime();
} while (status == GL32.GL_TIMEOUT_EXPIRED && remainingNanos > 0);
if (!signaled(status)) {
GLStateManager.LOGGER.warn("Streaming ring fence wait did not signal (status=0x{}); reclaiming anyway", Integer.toHexString(status));
}
GLStateManager.glDeleteSync(region.fenceId);
remaining += region.bytes;
reclaim();
}
void endFrame() {
fence();
reclaim();
}
long address() { return mappedAddress; }
ByteBuffer mapped() { return mappedBuffer; }
int bufferId() { return bufferId; }
int capacity() { return capacity; }
int remaining() { return remaining; }
int wraps() { return wraps; }
int fencesIssued() { return fencesIssued; }
int forcedReclaims() { return forcedReclaims; }
void destroy() {
while (!fenceQueue.isEmpty()) {
GLStateManager.glDeleteSync(fenceQueue.dequeue().fenceId);
}
if (bufferId != 0) {
RENDER_BACKEND.bindBuffer(target, bufferId);
RENDER_BACKEND.unmapBuffer(target);
RENDER_BACKEND.bindBuffer(target, 0);
GLStateManager.glDeleteBuffers(bufferId);
bufferId = 0;
}
}
private record FencedRegion(long fenceId, int bytes) {}
}
@@ -1,184 +1,60 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
import org.embeddedt.embeddium.impl.gl.sync.GlFence;
import com.gtnewhorizons.angelica.config.SystemProperties;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class PersistentStreamingBuffer {
/**
* Persistent-mapped ring buffer for streaming vertex data on GL4.4+
*/
public class PersistentStreamingBuffer implements StreamingBuffer {
public static final int DEFAULT_CAPACITY = 16 * 1024 * 1024;
public static final int DEFAULT_CAPACITY = 16 * 1024 * 1024; // 16 MB
private final GlStreamingRing ring;
private int bufferId;
private final int capacity;
private final ByteBuffer mappedBuffer;
private final long mappedAddress;
private final ObjectArrayFIFOQueue<FencedRegion> fenceQueue = new ObjectArrayFIFOQueue<>();
private int writePos;
private int remaining;
private int pendingBytes;
public PersistentStreamingBuffer() {
this(DEFAULT_CAPACITY);
private PersistentStreamingBuffer(GlStreamingRing ring) {
this.ring = ring;
}
public PersistentStreamingBuffer(int capacity) {
this.capacity = capacity;
this.remaining = capacity;
this.bufferId = RENDER_BACKEND.genBuffers();
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
final int storageFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT | GL44.GL_CLIENT_STORAGE_BIT;
RenderSystem.bufferStorage(GL15.GL_ARRAY_BUFFER, capacity, storageFlags);
final int mapFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT;
this.mappedBuffer = RENDER_BACKEND.mapBufferRange(GL15.GL_ARRAY_BUFFER, 0, capacity, mapFlags);
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (this.mappedBuffer == null) {
GLStateManager.glDeleteBuffers(bufferId);
bufferId = 0;
throw new RuntimeException("Failed to persistently map streaming buffer");
}
this.mappedAddress = memAddress0(mappedBuffer);
public static PersistentStreamingBuffer createOrNull(int capacity) {
return createOrNull(capacity, SystemProperties.FORCE_ORPHAN_STREAMING);
}
@Override
public static PersistentStreamingBuffer createOrNull(int capacity, boolean forceOrphan) {
final GlStreamingRing ring = GlStreamingRing.create(GL15.GL_ARRAY_BUFFER, capacity, forceOrphan);
return ring == null ? null : new PersistentStreamingBuffer(ring);
}
public int upload(ByteBuffer data, int vertexStride) {
final int dataSize = data.remaining();
if (dataSize > capacity) {
return -1;
}
reclaim();
final int alignUnit = vertexStride * 4;
final int alignedPos = ((writePos + alignUnit - 1) / alignUnit) * alignUnit;
final int alignmentWaste = alignedPos - writePos;
final int tailSpace = capacity - alignedPos;
if (dataSize <= tailSpace) {
// Fits at tail — just need enough remaining budget
if (ensureRemaining(alignmentWaste + dataSize)) {
return writeAt(alignedPos, data, dataSize, alignmentWaste, vertexStride);
}
} else {
// Must wrap to start — burn remaining tail space
final int wrapWaste = capacity - writePos;
if (ensureRemaining(wrapWaste + dataSize)) {
return writeAtStart(data, dataSize, wrapWaste);
}
}
return -1;
return upload(data, vertexStride, 1);
}
/** Sync fences until {@code needed} bytes are available, or no fences remain. */
private boolean ensureRemaining(int needed) {
while (remaining < needed) {
if (!syncOldest()) return false;
}
return true;
public int upload(ByteBuffer data, int vertexStride, int vertexAlignment) {
final int size = data.remaining();
final long offset = ring.reserve(size, vertexStride * vertexAlignment);
if (offset < 0) return -1;
memCopy(memAddress0(data) + data.position(), ring.address() + offset, size);
RENDER_BACKEND.onPersistentBufferWrite(ring.bufferId(), offset, size);
return (int) (offset / vertexStride);
}
private int writeAt(int offset, ByteBuffer data, int dataSize, int waste, int vertexStride) {
memCopy(memAddress0(data) + data.position(), mappedAddress + offset, dataSize);
public int getBufferId() { return ring.bufferId(); }
public int getCapacity() { return ring.capacity(); }
public int getRemaining() { return ring.remaining(); }
public int getForcedReclaims() { return ring.forcedReclaims(); }
public int getWraps() { return ring.wraps(); }
final int totalCost = waste + dataSize;
writePos = offset + dataSize;
remaining -= totalCost;
pendingBytes += totalCost;
return offset / vertexStride;
}
ByteBuffer getMappedBuffer() { return ring.mapped(); }
private int writeAtStart(ByteBuffer data, int dataSize, int wrapWaste) {
memCopy(memAddress0(data) + data.position(), mappedAddress, dataSize);
final int totalCost = wrapWaste + dataSize;
writePos = dataSize;
remaining -= totalCost;
pendingBytes += totalCost;
return 0;
}
@Override
public int getCapacity() {
return capacity;
}
public int getRemaining() { return remaining; }
@Override
public int getBufferId() {
return bufferId;
}
@Override
public void postDraw() {
if (pendingBytes > 0) {
final long fenceId = RENDER_BACKEND.fenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
fenceQueue.enqueue(new FencedRegion(new GlFence(fenceId), pendingBytes));
pendingBytes = 0;
}
ring.endFrame();
}
@Override
public void destroy() {
while (!fenceQueue.isEmpty()) {
fenceQueue.dequeue().fence.delete();
}
if (bufferId != 0) {
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
RENDER_BACKEND.unmapBuffer(GL15.GL_ARRAY_BUFFER);
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteBuffers(bufferId);
bufferId = 0;
}
ring.destroy();
}
private void reclaim() {
while (!fenceQueue.isEmpty()) {
final FencedRegion region = fenceQueue.first();
if (!region.fence.isCompleted()) break;
region.fence.delete();
fenceQueue.dequeue();
remaining += region.bytes;
}
}
/** Sync on oldest fence. Returns true if bytes were reclaimed. */
private boolean syncOldest() {
if (fenceQueue.isEmpty()) return false;
final FencedRegion region = fenceQueue.first();
if (!region.fence.sync()) {
GLStateManager.LOGGER.warn("Streaming buffer fence wait did not signal; reclaiming {} bytes anyway", region.bytes);
}
region.fence.delete();
fenceQueue.dequeue();
remaining += region.bytes;
reclaim(); // opportunistically reclaim any additional completed fences
return true;
}
// Package-private accessors for testing
ByteBuffer getMappedBuffer() { return mappedBuffer; }
private record FencedRegion(GlFence fence, int bytes) {}
}
@@ -1,12 +0,0 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import java.nio.ByteBuffer;
public interface StreamingBuffer {
int upload(ByteBuffer data, int vertexStride);
int getBufferId();
void postDraw();
int getCapacity();
void destroy();
}
@@ -45,8 +45,12 @@ public final class StreamingUploader {
return data.remaining();
}
final int dataSize = data.remaining();
final ByteBuffer mapped = RENDER_BACKEND.mapBufferRange(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
memCopy(memAddress0(data), memAddress0(mapped), dataSize);
final long dst = RENDER_BACKEND.mapBufferRangeAddress(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
if (dst == 0L) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
memCopy(memAddress0(data), dst, dataSize);
RENDER_BACKEND.unmapBuffer(GL15.GL_ARRAY_BUFFER);
return capacity;
}
@@ -4,9 +4,13 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -16,6 +20,7 @@ import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static org.joml.Math.clamp;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
@@ -28,6 +33,8 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
*/
public class TessellatorStreamingDrawer {
private static final Tracy.ZoneId Z_SDL_STREAM_DRAW = Tracy.zoneId("sdlStreamDraw", Tracy.COLOR_FFP);
private static final Logger LOGGER = LogManager.getLogger("TessellatorStreamingDrawer");
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE; // 16
@@ -43,6 +50,14 @@ public class TessellatorStreamingDrawer {
private static boolean initialized = false;
// Tracy profiling counters
public static long streamedBytes;
public static long streamDraws;
public static long orphanFallbacks;
public static long streamContiguous;
private static int lastStreamEndVertex = -1;
private static int lastStreamDrawMode = -1;
static {
// Initial repack buffer: 64KB
repackCapacity = 0x10000;
@@ -54,15 +69,7 @@ public class TessellatorStreamingDrawer {
if (initialized) return;
initialized = true;
if (RenderSystem.supportsBufferStorage() && !Boolean.getBoolean("angelica.forceOrphanStreaming")) {
try {
persistentBuffer = new PersistentStreamingBuffer();
LOGGER.info("Persistent streaming buffer created ({}MB)", PersistentStreamingBuffer.DEFAULT_CAPACITY / (1024 * 1024));
} catch (Exception e) {
LOGGER.warn("Failed to create persistent streaming buffer, using orphan fallback", e);
persistentBuffer = null;
}
}
persistentBuffer = PersistentStreamingBuffer.createOrNull(PersistentStreamingBuffer.DEFAULT_CAPACITY);
}
/**
@@ -84,7 +91,57 @@ public class TessellatorStreamingDrawer {
// Determine the optimal vertex format from the tessellator's flags
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
final int effectiveFlags;
if (GLSMConfig.expandVertexFormats) {
effectiveFlags = VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.NORMAL_BIT | VertexFlags.BRIGHTNESS_BIT;
if (effectiveFlags != flags) {
final int[] rawBuffer = tess.rawBuffer;
final int defaultColor;
if (!tess.hasColor) {
final var c = GLStateManager.getColor();
defaultColor = ((int)(clamp(0f, 1f, c.getAlpha()) * 255) << 24) | ((int)(clamp(0f, 1f, c.getBlue()) * 255) << 16)
| ((int)(clamp(0f, 1f, c.getGreen()) * 255) << 8) | (int)(clamp(0f, 1f, c.getRed()) * 255);
} else {
defaultColor = 0;
}
final int defaultBrightness;
if (!tess.hasBrightness) {
defaultBrightness = ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF);
} else {
defaultBrightness = 0;
}
final int defaultNormal;
if (!tess.hasNormals) {
final var n = ShaderManager.getCurrentNormal();
defaultNormal = ((int)(clamp(-1f, 1f, n.z) * 127) << 16) | (((int)(clamp(-1f, 1f, n.y) * 127) & 0xFF) << 8) | ((int)(clamp(-1f, 1f, n.x) * 127) & 0xFF);
} else {
defaultNormal = 0;
}
final int defaultTexU, defaultTexV;
if (!tess.hasTexture) {
final var tc = ShaderManager.getCurrentTexCoord();
defaultTexU = Float.floatToRawIntBits(tc.x);
defaultTexV = Float.floatToRawIntBits(tc.y);
} else {
defaultTexU = 0; defaultTexV = 0;
}
for (int i = 0; i < vertexCount; i++) {
final int base = i * 8;
if (!tess.hasTexture) { rawBuffer[base + 3] = defaultTexU; rawBuffer[base + 4] = defaultTexV; }
if (!tess.hasColor) { rawBuffer[base + 5] = defaultColor; }
if (!tess.hasNormals) { rawBuffer[base + 6] = defaultNormal; }
if (!tess.hasBrightness) { rawBuffer[base + 7] = defaultBrightness; }
}
}
} else {
effectiveFlags = flags;
}
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[effectiveFlags];
final int vertexSize = format.getVertexSize();
final int requiredBytes = vertexCount * vertexSize;
@@ -96,7 +153,7 @@ public class TessellatorStreamingDrawer {
repackBuffer.position(0);
repackBuffer.limit((int)(writePtr - repackAddress));
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount);
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
@@ -166,9 +223,9 @@ public class TessellatorStreamingDrawer {
}
if (persistentBuffer != null) {
cachedDebugInfo = String.format("Stream: Persistent %s (%s free) + %d orphan (%s)",
cachedDebugInfo = String.format("Stream: Persistent %s (%s free, %d forced) + %d orphan (%s)",
formatBytes(persistentBuffer.getCapacity()), formatBytes(persistentBuffer.getRemaining()),
orphanCount, formatBytes(orphanBytes));
persistentBuffer.getForcedReclaims(), orphanCount, formatBytes(orphanBytes));
} else {
cachedDebugInfo = String.format("Stream: Orphan (%d bufs, %s)", orphanCount, formatBytes(orphanBytes));
}
@@ -192,31 +249,49 @@ public class TessellatorStreamingDrawer {
* Tries the persistent ring buffer first, falls back to orphan buffer on overflow.
*/
private static void uploadAndDraw(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) {
Tracy.beginZone(Z_SDL_STREAM_DRAW);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureVAO(flags, format);
if (Tracy.ENABLED) {
streamedBytes += packed.remaining();
streamDraws++;
}
int firstVertex = -1;
if (persistentBuffer != null) {
firstVertex = persistentBuffer.upload(packed, vertexSize);
firstVertex = persistentBuffer.upload(packed, vertexSize, drawMode == GL11.GL_QUADS ? 4 : 1);
}
if (firstVertex >= 0) {
final boolean fromRing = firstVertex >= 0;
if (fromRing) {
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
} else {
if (Tracy.ENABLED && persistentBuffer != null) orphanFallbacks++;
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
if (Tracy.ENABLED) {
if (fromRing && firstVertex == lastStreamEndVertex && drawMode == lastStreamDrawMode) streamContiguous++;
lastStreamEndVertex = fromRing ? firstVertex + vertexCount : -1;
lastStreamDrawMode = drawMode;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
} finally {
if (locked) GLStateManager.releaseDrawLock();
Tracy.endZone();
}
}
public static long ringWraps() {
return persistentBuffer == null ? 0L : persistentBuffer.getWraps();
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
@@ -264,7 +339,6 @@ public class TessellatorStreamingDrawer {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
format.setupBufferState(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
if (persistentBuffer != null && persistentVAOs[flags] == 0) {
@@ -273,7 +347,6 @@ public class TessellatorStreamingDrawer {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
format.setupBufferState(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
}
@@ -0,0 +1,99 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class UniformRingBuffer {
private static final int OFFSET_ALIGNMENT = 256;
private final int capacity;
private final int blockSize;
private final int stride;
private final GlStreamingRing ring;
private int orphanBufferId;
private int writePos;
private int orphanWraps;
public UniformRingBuffer(int capacity, int blockSize) {
this(capacity, blockSize, SystemProperties.FORCE_ORPHAN_STREAMING);
}
public UniformRingBuffer(int capacity, int blockSize, boolean forceOrphan) {
this.capacity = capacity;
this.blockSize = blockSize;
this.stride = (blockSize + OFFSET_ALIGNMENT - 1) & -OFFSET_ALIGNMENT;
this.ring = GlStreamingRing.create(GL31.GL_UNIFORM_BUFFER, capacity, forceOrphan);
if (ring == null) {
orphanBufferId = RENDER_BACKEND.genBuffers();
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
RENDER_BACKEND.bufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
}
}
public int writeBlock(ByteBuffer src) {
final long srcAddress = memAddress0(src);
if (ring != null) {
final long offset = ring.reserve(stride, OFFSET_ALIGNMENT);
if (offset < 0) {
throw new IllegalStateException("uniform ring exhausted with nothing in flight (needed=" + stride + ")");
}
memCopy(srcAddress, ring.address() + offset, blockSize);
return (int) offset;
}
return writeOrphan(srcAddress);
}
private int writeOrphan(long srcAddress) {
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
int offset = writePos;
if (offset + stride > capacity) {
RENDER_BACKEND.bufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
offset = 0;
orphanWraps++;
}
final long dst = RENDER_BACKEND.mapBufferRangeAddress(GL31.GL_UNIFORM_BUFFER, offset, blockSize, GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
if (dst == 0L) {
throw new IllegalStateException("mapBufferRange failed on uniform ring (offset=" + offset + ")");
}
memCopy(srcAddress, dst, blockSize);
RENDER_BACKEND.unmapBuffer(GL31.GL_UNIFORM_BUFFER);
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
writePos = offset + stride;
return offset;
}
public void endFrame() {
if (ring != null) {
ring.endFrame();
}
}
public int getBufferId() { return ring != null ? ring.bufferId() : orphanBufferId; }
public int getBlockSize() { return blockSize; }
public int getStride() { return stride; }
public boolean isPersistent() { return ring != null; }
public int getWraps() { return ring != null ? ring.wraps() : orphanWraps; }
public int getFencesIssued() { return ring != null ? ring.fencesIssued() : 0; }
public int getForcedReclaims() { return ring != null ? ring.forcedReclaims() : 0; }
public void destroy() {
if (ring != null) {
ring.destroy();
} else if (orphanBufferId != 0) {
GLStateManager.glDeleteBuffers(orphanBufferId);
orphanBufferId = 0;
}
}
}
@@ -1,4 +1,4 @@
package net.coderbot.iris.gl.texture;
package com.gtnewhorizons.angelica.glsm.texture;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11;
@@ -77,4 +77,12 @@ public enum DepthBufferFormat {
public boolean isCombinedStencil() {
return combinedStencil;
}
public DepthBufferFormat stripStencil() {
return switch (this) {
case DEPTH_STENCIL, DEPTH24_STENCIL8 -> DEPTH24;
case DEPTH32F_STENCIL8 -> DEPTH32F;
default -> this;
};
}
}
@@ -1,4 +1,4 @@
package net.coderbot.iris.gl;
package com.gtnewhorizons.angelica.glsm.texture;
public enum GlVersion {
GL_11,
Loaded 100 of 647 files, more files were not shown because too many files have changed in this diff. Show more