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 .gradle
.kotlin/
.settings .settings
/.idea/ /.idea/
/.vs/ /.vs/
@@ -38,3 +39,6 @@ addon.local.gradle.kts
addon.late.local.gradle addon.late.local.gradle
addon.late.local.gradle.kts addon.late.local.gradle.kts
local/ 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<DowngradeJar>().configureEach { logLevel.set("FATAL") }
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") } tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
val lwjglDebug = false
val gpuHud = false
val renderdoc = false
val rgp = false
minecraft { minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true") extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true") extraRunJvmArguments.add("-Dangelica.tracy=true")
// extraRunJvmArguments.add("-Dangelica.useVulkan=true") extraRunJvmArguments.add("-Dangelica.sdlgpu.enable=true")
// extraRunJvmArguments.add("-Dangelica.vulkanDebug=true") extraRunJvmArguments.add("-Dangelica.unmappedGL=fail")
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true") 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("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true") // extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true") //extraRunJvmArguments.add("-Dangelica.debug.redirectorLogspam=true")
} }
tasks.test { // Ensure flyby args are passed to the client
useJUnitPlatform() tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
testLogging { for ((key, value) in System.getProperties()) {
events("passed", "skipped", "failed") val name = key.toString()
} if (name.startsWith("angelica.flyby.")) {
// On macOS ARM64, use an ARM64-compatible JVM for tests jvmArgs("-D$name=$value")
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 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 { tasks.processResources {
@@ -57,6 +144,7 @@ val shadowImplementation: Configuration by configurations
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) { val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = embedOnly inputCollection = embedOnly
outputs.dir(temporaryDir)
} }
val stripModuleInfoFromShadow by tasks.registering(Jar::class) { val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
@@ -64,7 +152,7 @@ val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
from(shadowImplementation.map(::zipTree)) from(shadowImplementation.map(::zipTree))
exclude("module-info.class") exclude("module-info.class")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier = "shadow-stripped" archiveFileName = "shadow-stripped.jar"
} }
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) { val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
@@ -72,15 +160,74 @@ val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
outputs.dir(temporaryDir) outputs.dir(temporaryDir)
} }
tasks.test { val glsmTestFixtures: Configuration by configurations.creating {
dependsOn(downgradeEmbedOnlyForTest, downgradeShadowImplForTest) isCanBeConsumed = false
classpath = classpath isCanBeResolved = true
.minus(embedOnly) attributes {
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection })) attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
.minus(shadowImplementation) attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
.plus(files({ fileTree(downgradeShadowImplForTest.get().temporaryDir) })) }
} }
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 { tasks.shadowJar {
dependsOn(embedOnly) dependsOn(embedOnly)
from(embedOnly.map(::zipTree)) from(embedOnly.map(::zipTree))
@@ -89,13 +236,18 @@ tasks.shadowJar {
mergeServiceFiles() mergeServiceFiles()
minimize { minimize {
exclude(project(rootProject.path))
exclude(project(":glsm")) exclude(project(":glsm"))
exclude(project(":lwjgl3-backend")) exclude(project(":lwjgl3-backend"))
exclude(project(":sdl-gpu"))
exclude(dependency("org.taumc:.*:.*"))
exclude(dependency("org.antlr:.*:.*"))
} }
relocate("com.mitchej123", "com.mitchej123") relocate("com.mitchej123", "com.mitchej123")
relocate("org.embeddedt", "org.embeddedt") 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.glsm", "com.gtnewhorizons.angelica.glsm")
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3") 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 { plugins {
`java-library` `java-library`
`java-test-fixtures`
`maven-publish` `maven-publish`
} }
@@ -9,6 +10,8 @@ apply(plugin = "xyz.wagyourtail.jvmdowngrader")
version = rootProject.version version = rootProject.version
val lwjglNatives: String by extra
java { java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21)) toolchain.languageVersion.set(JavaLanguageVersion.of(21))
withSourcesJar() withSourcesJar()
@@ -20,7 +23,7 @@ val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation",
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType) val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
configurations { configurations {
listOf("apiElements", "runtimeElements").forEach { name -> listOf("apiElements", "runtimeElements", "testFixturesApiElements", "testFixturesRuntimeElements").forEach { name ->
named(name) { named(name) {
attributes { attributes {
attribute(rfgObfAttr, "mcp") attribute(rfgObfAttr, "mcp")
@@ -31,7 +34,7 @@ configurations {
} }
// Compile stubs: MC class signatures needed to resolve GTNHLib's type hierarchy. // 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 { sourceSets {
create("stubs") { create("stubs") {
java.srcDir("src/stubs/java") java.srcDir("src/stubs/java")
@@ -65,6 +68,16 @@ repositories {
url = uri("https://maven.wagyourtail.xyz/releases") url = uri("https://maven.wagyourtail.xyz/releases")
content { includeGroup("xyz.wagyourtail.jvmdowngrader") } 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() mavenCentral()
mavenLocal() mavenLocal()
} }
@@ -74,42 +87,62 @@ dependencies {
compileOnly(sourceSets["stubs"].output) compileOnly(sourceSets["stubs"].output)
// LWJGL // LWJGL
compileOnly("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}") compileOnly(libs.lwjgl2)
compileOnly("org.lwjgl.lwjgl:lwjgl_util:${property("lwjglVersion")}") compileOnly(libs.lwjgl2.util)
compileOnly("org.ow2.asm:asm-tree:5.0.3") compileOnly(libs.asm.tree.compile)
// Our Deps // Our Deps
api("com.github.GTNewHorizons:GTNHLib:${property("gtnhlibVersion")}:dev") api(libs.gtnhlib) { artifact { classifier = "dev" } }
api("net.minecraftforge:eventbus:${property("eventbusVersion")}") api(libs.eventbus)
compileOnly("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false } compileOnly(libs.celeritas.common) { isTransitive = false }
implementation("org.taumc:glsl-transformation-lib:${property("glslTransformLibVersion")}") { exclude(module = "antlr4") } api(libs.glsl.transformation.lib) { exclude(module = "antlr4") }
implementation("org.antlr:antlr4-runtime:${property("antlr4RuntimeVersion")}") api(libs.antlr4.runtime)
implementation("org.anarres:jcpp:${property("jcppVersion")}") implementation(libs.jcpp)
compileOnly("org.projectlombok:lombok:${property("lombokVersion")}") { isTransitive = false } // shaderc + SPIRV-Cross for the SpirvShaderTranslator (GLSL -> SPIR-V -> GLSL ES).
annotationProcessor("org.projectlombok:lombok:${property("lombokVersion")}") // TODO: Remove
compileOnly("org.jetbrains:annotations:${property("jetbrainsAnnotationsVersion")}") compileOnly(libs.lwjgl3.shaderc)
compileOnly("org.apache.logging.log4j:log4j-api:${property("log4jVersion")}") 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 // Deps (normally from MC) - compile only, but needed at test runtime
compileOnly("com.google.guava:guava:${property("guavaVersion")}") compileOnly(libs.guava)
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}") testRuntimeOnly(libs.guava)
testRuntimeOnly("it.unimi.dsi:fastutil:${property("fastutilVersion")}") testRuntimeOnly(libs.fastutil)
testRuntimeOnly("org.joml:joml:${property("jomlVersion")}") { isTransitive = false } testRuntimeOnly(libs.joml) { isTransitive = false }
testFixturesCompileOnly(sourceSets["stubs"].output)
testFixturesApi(platform(libs.junit.bom))
testFixturesApi(libs.junit.jupiter.api)
testFixturesApi(libs.lwjgl2)
// Test // Test
testImplementation(sourceSets["stubs"].output) testImplementation(sourceSets["stubs"].output)
testImplementation(platform("org.junit:junit-bom:${property("junitBomVersion")}")) testImplementation(platform(libs.junit.bom))
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation(libs.junit.jupiter)
testImplementation("org.ow2.asm:asm-tree:9.7") testImplementation(libs.asm.tree.test)
testImplementation("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}") testImplementation(libs.lwjgl2)
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly(libs.junit.platform.launcher)
testRuntimeOnly("org.apache.logging.log4j:log4j-core:${property("log4jVersion")}") testRuntimeOnly(libs.log4j.core)
testRuntimeOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:${property("celeritasVersion")}") { isTransitive = false } testImplementation(libs.celeritas.common) { isTransitive = false }
testRuntimeOnly("xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:${property("jvmDowngraderVersion")}:downgraded-8") testRuntimeOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
testRuntimeOnly("org.apache.commons:commons-lang3:${property("commonsLang3Version")}") testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
testRuntimeOnly(libs.commons.lang3)
} }
val depsToDowngrade by configurations.creating { val depsToDowngrade by configurations.creating {
@@ -118,8 +151,8 @@ val depsToDowngrade by configurations.creating {
} }
dependencies { dependencies {
depsToDowngrade("net.minecraftforge:eventbus:${property("eventbusVersion")}") depsToDowngrade(libs.eventbus)
depsToDowngrade("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false } depsToDowngrade(libs.celeritas.common) { isTransitive = false }
} }
tasks.withType<DowngradeFiles>().configureEach { tasks.withType<DowngradeFiles>().configureEach {
@@ -136,9 +169,18 @@ val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
val downgradeMainClasses by tasks.registering(DowngradeFiles::class) { val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["main"].output.classesDirs.plus(sourceSets["stubs"].output.classesDirs) inputCollection = sourceSets["main"].output.classesDirs.plus(sourceSets["stubs"].output.classesDirs)
classpath = sourceSets["main"].compileClasspath 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")) 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) { val downgradeTestClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["test"].output.classesDirs inputCollection = sourceSets["test"].output.classesDirs
classpath = sourceSets["test"].compileClasspath 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 val mainClassesDirs = sourceSets["main"].output.classesDirs
.plus(sourceSets["stubs"].output.classesDirs) .plus(sourceSets["stubs"].output.classesDirs)
@@ -175,7 +217,9 @@ fun Test.configureGlsmJava8() {
classpath = downgradedTest classpath = downgradedTest
.plus(downgradedMain) .plus(downgradedMain)
.plus(downgradedDeps) .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") val extractNatives = rootProject.tasks.named("extractNatives2")
dependsOn(extractNatives) dependsOn(extractNatives)
@@ -184,7 +228,10 @@ fun Test.configureGlsmJava8() {
tasks.test { tasks.test {
useJUnitPlatform { useJUnitPlatform {
excludeTags = GL_TASK_TAGS.values.toSet() excludeTags = GL_TASK_TAGS.values.toSet() + "lwjgl3"
}
filter {
excludeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
} }
configureGlsmJava8() configureGlsmJava8()
} }
@@ -225,6 +272,41 @@ val verifyTestsRan by tasks.registering {
} }
tasks.check { dependsOn(verifyTestsRan) } 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 { publishing {
publications { publications {
create<MavenPublication>("mavenJava") { 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; package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage; 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.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.v4.runtime.tree.TerminalNode;
import org.lwjgl.opengl.GL20;
import org.taumc.glsl.grammar.GLSLLexer; import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser; import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.ShaderParser; import org.taumc.glsl.ShaderParser;
@@ -15,6 +19,7 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -33,12 +38,12 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
* <ul> * <ul>
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li> * <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>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>gl_TexCoord[N] varying array -> per-index in/out declarations</li>
* <li>Texture function renames (texture2D → texture, etc.)</li> * <li>Texture function renames (texture2D -> texture, etc.)</li>
* <li>Fragment output handling (gl_FragColor → layout-qualified out declarations)</li> * <li>Fragment output handling (gl_FragColor -> layout-qualified out declarations)</li>
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li> * <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
* <li>gl_FrontColor → local variable in vertex shaders</li> * <li>gl_FrontColor -> local variable in vertex shaders</li>
* <li>shadow2D/shadow2DLod → texture/textureLod with vec4 wrapping</li> * <li>shadow2D/shadow2DLod -> texture/textureLod with vec4 wrapping</li>
* <li>Version upgrade to 330 core minimum</li> * <li>Version upgrade to 330 core minimum</li>
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li> * <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
* </ul> * </ul>
@@ -71,30 +76,59 @@ public class CompatShaderTransformer {
"gl_NormalMatrix", "angelica_NormalMatrix" "gl_NormalMatrix", "angelica_NormalMatrix"
); );
private static final Path DUMP_DIR;
private static final AtomicInteger dumpCounter = new AtomicInteger(0); private static final AtomicInteger dumpCounter = new AtomicInteger(0);
static { private static final int CACHE_SIZE = 256;
DUMP_DIR = Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false")) ? Paths.get("compat_shaders") : null;
}
private static final int CACHE_SIZE = 32;
private record CacheKey(String source, boolean isFragment) {} private record CacheKey(String source, boolean isFragment) {}
private static final Map<CacheKey, String> cache = Collections.synchronizedMap( 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) { @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() { public static void clearCache() {
cache.clear(); cache.clear();
esCache.clear();
} }
/** public static void prewarm(String source, int glShaderType, boolean isFragment) {
* Transform a mod shader source for core profile compatibility. 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) { 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); final boolean needsTransform = needsTransformation(source);
String result; String result;
if (!needsTransform) { if (!needsTransform) {
@@ -103,17 +137,27 @@ public class CompatShaderTransformer {
final CacheKey key = new CacheKey(source, isFragment); final CacheKey key = new CacheKey(source, isFragment);
final String cached = cache.get(key); final String cached = cache.get(key);
if (cached != null) { if (cached != null) {
dumpShader(source, cached, isFragment, needsTransform); result = cached;
return 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 { try {
result = transformInternal(source, isFragment); result = toGLES(result, glShaderType, isFragment);
cache.put(key, result); } catch (RuntimeException e) {
} catch (Exception e) { dumpShader(source, preGles, isFragment, needsTransform);
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e); throw e;
result = fixupVersion(source);
} }
esCache.put(new EsCacheKey(source, glShaderType, isFragment), result);
} }
dumpShader(source, result, isFragment, needsTransform); 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); 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.replaceTexture(source);
source = GlslTransformUtils.renameReservedWords(source, targetVersion); source = GlslTransformUtils.renameReservedWords(source, targetVersion);
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source); final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
final Transformer transformer = new Transformer(parsedShader.full()); final Transformer transformer = new Transformer(parsed.full());
injectMatrixUniforms(transformer); injectMatrixUniforms(transformer);
transformFog(transformer, isFragment, source); transformFog(transformer, isFragment, source);
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform // gl_FrontLightModelProduct.sceneColor -> angelica_SceneColor uniform
if (source.contains("gl_FrontLightModelProduct")) { if (source.contains("gl_FrontLightModelProduct")) {
transformer.injectVariable("uniform vec4 angelica_SceneColor;"); transformer.injectVariable("uniform vec4 angelica_SceneColor;");
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "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")) { if (source.contains("gl_LightSource")) {
transformer.injectFunction("struct angelica_LightSourceParameters {" transformer.injectFunction("struct angelica_LightSourceParameters {"
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;" + "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
@@ -172,7 +216,7 @@ public class CompatShaderTransformer {
transformer.rename("gl_LightSource", "angelica_LightSource"); 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")) { if (source.contains("gl_FrontMaterial")) {
transformer.injectFunction("struct angelica_MaterialParameters {" transformer.injectFunction("struct angelica_MaterialParameters {"
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;" + "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
@@ -181,7 +225,7 @@ public class CompatShaderTransformer {
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial"); 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. // 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"). // Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
if (!isFragment) { 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<>(); final Set<Integer> texCoordIndices = new HashSet<>();
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices); transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
for (Integer i : texCoordIndices) { for (Integer i : texCoordIndices) {
@@ -226,7 +270,7 @@ public class CompatShaderTransformer {
transformer.renameAndWrapShadow("shadow2DLod", "textureLod"); transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
final String versionDirective = "#version " + targetVersion + " core\n"; 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 // Preserve #define directives
final StringBuilder defines = new StringBuilder(); final StringBuilder defines = new StringBuilder();
@@ -242,7 +286,7 @@ public class CompatShaderTransformer {
// Restore pre-parse renames // Restore pre-parse renames
String output = GlslTransformUtils.restoreReservedWords(result.toString()); 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); output = fixupQualifiers(output, isFragment);
return output; return output;
@@ -310,7 +354,8 @@ public class CompatShaderTransformer {
transformer.injectVariable("uniform float angelica_FogEnd;"); transformer.injectVariable("uniform float angelica_FogEnd;");
transformer.injectVariable("uniform vec4 angelica_FogColor;"); transformer.injectVariable("uniform vec4 angelica_FogColor;");
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};"); 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, " + "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
+ "1.0 / (angelica_FogEnd - angelica_FogStart));"); + "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) { 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 int id = dumpCounter.getAndIncrement();
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl"; final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
try { try {
Files.createDirectories(DUMP_DIR); Files.createDirectories(dir);
// Capture caller info for identification // Capture caller info for identification
final String caller = identifyCaller(); final String caller = identifyCaller();
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")" final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
+ "\n// Transformed: " + wasTransformed + "\n// Transformed: " + wasTransformed
+ "\n// Caller: " + caller + "\n\n"; + "\n// Caller: " + caller + "\n\n";
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8); Files.writeString(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 + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
} catch (IOException e) { } catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage()); GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
} }
@@ -524,10 +570,10 @@ public class CompatShaderTransformer {
return source; return source;
} }
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders private static final String PRECISION_QUALIFIER = "(?:highp\\s+|mediump\\s+|lowp\\s+)?";
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b"); 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+vec4\\s+angelica_TexCoord(\\d+)\\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+float\\s+angelica_FogFragCoord\\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. * Generate a passthrough vertex shader for a fragment-only program.
@@ -548,22 +594,26 @@ public class CompatShaderTransformer {
final StringBuilder varyings = new StringBuilder(); final StringBuilder varyings = new StringBuilder();
final StringBuilder assignments = new StringBuilder(); final StringBuilder assignments = new StringBuilder();
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) { final Matcher colorMatcher = FRAG_IN_COLOR.matcher(fragmentSource);
sb.append("layout(location = 1) in vec4 a_Color;\n"); if (colorMatcher.find()) {
varyings.append("out vec4 angelica_FrontColor;\n"); 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"); assignments.append(" angelica_FrontColor = a_Color;\n");
} }
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource); final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
final Set<Integer> texCoordIndices = new HashSet<>(); final Map<Integer, String> texCoordTypes = new HashMap<>();
while (texCoordMatcher.find()) { 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. // 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; final int location = i == 0 ? 2 : 3;
sb.append("layout(location = ").append(location).append(") in vec4 a_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 vec4 angelica_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"); assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
} }
@@ -581,6 +631,23 @@ public class CompatShaderTransformer {
return sb.toString(); 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. */ /** Ensure #version is at least 330 core, strip 'compatibility' profile. */
private static String fixupVersion(String source) { private static String fixupVersion(String source) {
final Matcher m = VERSION_PATTERN.matcher(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.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState; import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 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.Matrix3f;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
@@ -173,10 +177,10 @@ public class CompatUniformManager {
final int[] locs = pu.locs; final int[] locs = pu.locs;
// Matrix uniforms — skip if this program's storage already holds the current generation // Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.mvGeneration; final int mvGen = GLStateManager.getMvGeneration();
final int mvLinearGen = GLStateManager.mvLinearGeneration; final int mvLinearGen = GLStateManager.getMvLinearGeneration();
final int projGen = GLStateManager.projGeneration; final int projGen = GLStateManager.getProjGeneration();
final int texMatGen = GLStateManager.texMatrixGeneration; final int texMatGen = GLStateManager.getTexMatrixGeneration();
final boolean mvChanged = mvGen != pu.lastMvGen; final boolean mvChanged = mvGen != pu.lastMvGen;
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen; final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
final boolean projChanged = projGen != pu.lastProjGen; final boolean projChanged = projGen != pu.lastProjGen;
@@ -190,14 +194,14 @@ public class CompatUniformManager {
} }
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged // Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration; final int fragGen = GLStateManager.getFragmentGeneration();
if (fragGen != pu.lastFragmentGen) { if (fragGen != pu.lastFragmentGen) {
pu.lastFragmentGen = fragGen; pu.lastFragmentGen = fragGen;
uploadFragmentUniforms(locs); uploadFragmentUniforms(locs);
} }
// Lighting-derived uniforms (scene color, light sources, material) // Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.lightingGeneration; final int litGen = GLStateManager.getLightingGeneration();
if (litGen != pu.lastLightingGen) { if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen; pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs); 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 // Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) { 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) { if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen; pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs); uploadClipPlanes(locs);
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.glsm; package com.gtnewhorizons.angelica.glsm;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities; import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizon.gtnhlib.client.model.NormalHelper; import com.gtnewhorizon.gtnhlib.client.model.NormalHelper;
import com.gtnewhorizon.gtnhlib.client.renderer.CallbackTessellator; 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.VBOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer; import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat; 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.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder; import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList; import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
@@ -52,18 +56,15 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
*/ */
@UtilityClass @UtilityClass
public class DisplayListManager { public class DisplayListManager {
// -Dangelica.debugDisplayLists: disable transform collapsing and draw merging private static final Tracy.ZoneId Z_GL_LIST_COMPILE = Tracy.zoneId("glListCompile", Tracy.COLOR_FFP);
private static final boolean DEBUG_DISPLAY_LISTS; private static final Tracy.ZoneId Z_GL_LIST_PLAYBACK = Tracy.zoneId("glListPlayback", Tracy.COLOR_FFP);
// -Dangelica.logDisplayListCompilation: log compiled display list commands // Tracy profiling counter
private static final boolean LOG_DISPLAY_LIST_COMPILATION; public static long listPlaybacks;
static { static {
DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debugDisplayLists"); if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
GLStateManager.LOGGER.warn("Display list compilation logging ENABLED (-Dangelica.debug.displayLists.compilation=true)");
LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.logDisplayListCompilation");
if (LOG_DISPLAY_LIST_COMPILATION) {
GLStateManager.LOGGER.warn("Display list compilation logging ENABLED (-Dangelica.logDisplayListCompilation=true)");
} }
} }
@@ -460,7 +461,7 @@ public class DisplayListManager {
currentRecorder.writeHint(target, mode); currentRecorder.writeHint(target, mode);
} }
public static void recordFog(int pname, java.nio.FloatBuffer params) { public static void recordFog(int pname, FloatBuffer params) {
drawBarrier(); drawBarrier();
currentRecorder.writeFog(pname, params); currentRecorder.writeFog(pname, params);
} }
@@ -475,7 +476,7 @@ public class DisplayListManager {
currentRecorder.writeLighti(light, pname, param); 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(); drawBarrier();
currentRecorder.writeLight(light, pname, params); currentRecorder.writeLight(light, pname, params);
} }
@@ -490,7 +491,7 @@ public class DisplayListManager {
currentRecorder.writeLightModeli(pname, param); currentRecorder.writeLightModeli(pname, param);
} }
public static void recordLightModel(int pname, java.nio.FloatBuffer params) { public static void recordLightModel(int pname, FloatBuffer params) {
drawBarrier(); drawBarrier();
currentRecorder.writeLightModel(pname, params); currentRecorder.writeLightModel(pname, params);
} }
@@ -500,7 +501,7 @@ public class DisplayListManager {
currentRecorder.writeMaterialf(face, pname, val); 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(); drawBarrier();
currentRecorder.writeMaterial(face, pname, params); currentRecorder.writeMaterial(face, pname, params);
} }
@@ -556,7 +557,7 @@ public class DisplayListManager {
currentRecorder.writeDrawBuffers(count, buf); currentRecorder.writeDrawBuffers(count, buf);
} }
public static void recordDrawBuffers(int count, java.nio.IntBuffer bufs) { public static void recordDrawBuffers(int count, IntBuffer bufs) {
drawBarrier(); drawBarrier();
currentRecorder.writeDrawBuffers(count, bufs); currentRecorder.writeDrawBuffers(count, bufs);
} }
@@ -623,7 +624,7 @@ public class DisplayListManager {
pendingTransformOps.add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z)); pendingTransformOps.add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z));
} }
if (DEBUG_DISPLAY_LISTS) { if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix(); flushMatrix();
} }
} }
@@ -636,7 +637,7 @@ public class DisplayListManager {
pendingTransformOps.add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z)); pendingTransformOps.add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z));
} }
if (DEBUG_DISPLAY_LISTS) { if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix(); flushMatrix();
} }
} }
@@ -654,7 +655,7 @@ public class DisplayListManager {
pendingTransformOps.add(String.format("glRotatef(%.4f, %.4f, %.4f, %.4f)", Math.toDegrees(rad), x, y, z)); 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(); flushMatrix();
} }
} }
@@ -676,7 +677,7 @@ public class DisplayListManager {
pendingTransformOps.add("glMultMatrixf(...)"); pendingTransformOps.add("glMultMatrixf(...)");
} }
if (DEBUG_DISPLAY_LISTS) { if (SystemProperties.DEBUG_DISPLAY_LISTS) {
flushMatrix(); 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)); 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(); 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)); 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(); flushMatrix();
} }
} }
@@ -780,10 +781,10 @@ public class DisplayListManager {
currentRecorder = new CommandRecorder(); // Create command recorder currentRecorder = new CommandRecorder(); // Create command recorder
accumulatedDraws = new ArrayList<>(8); // Fewer draws than commands typically accumulatedDraws = new ArrayList<>(8); // Fewer draws than commands typically
transformCallback = new DisplayListCallback(); 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) // Initialize debug logging fields (only when logging enabled)
if (LOG_DISPLAY_LIST_COMPILATION) { if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>(); pendingTransformOps = new ArrayList<>();
multMatrixSources = new ArrayList<>(); multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>(); drawRangeSources = new ArrayList<>();
@@ -804,9 +805,11 @@ public class DisplayListManager {
GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!"); GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!");
return; return;
} }
if (Tracy.ENABLED) Tracy.beginZone(Z_GL_LIST_COMPILE);
try { try {
finishCurrentList(); finishCurrentList();
} finally { } finally {
if (Tracy.ENABLED) Tracy.endZone();
// Non-null here means finishCurrentList threw before transferring or releasing // Non-null here means finishCurrentList threw before transferring or releasing
// ownership of the recorder. // ownership of the recorder.
if (currentRecorder != null) { if (currentRecorder != null) {
@@ -888,7 +891,7 @@ public class DisplayListManager {
} }
// Log compilation details if enabled (before context restoration changes glListId) // 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); logCompiledDisplayList(glListId, compiled, compilationStackTrace);
} }
} }
@@ -989,7 +992,7 @@ public class DisplayListManager {
tessellator, copyLast tessellator, copyLast
); );
accumulatedDraws.add(pendingDraw); accumulatedDraws.add(pendingDraw);
if (DEBUG_DISPLAY_LISTS) { if (SystemProperties.DEBUG_DISPLAY_LISTS) {
drawBarrier(); drawBarrier();
} }
} }
@@ -1017,29 +1020,38 @@ public class DisplayListManager {
} }
private static void executeDisplayList(int list) { private static void executeDisplayList(int list) {
if (Tracy.ENABLED) {
listPlaybacks++;
Tracy.beginZone(Z_GL_LIST_PLAYBACK);
}
final boolean locked = GLStateManager.acquireDrawLock(); final boolean locked = GLStateManager.acquireDrawLock();
try { try {
final CompiledDisplayList compiled = displayListCache.get(list); executeDisplayListInner(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)
} finally { } finally {
if (locked) GLStateManager.releaseDrawLock(); 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. * Delete display lists and free their VBO resources.
*/ */
@@ -76,29 +76,29 @@ public class Feature {
*/ */
private static void addTextureGenStates(Set<IStateStack<?>> collection) { private static void addTextureGenStates(Set<IStateStack<?>> collection) {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) { for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
collection.add(GLStateManager.textures.getTexGenSStates(i)); collection.add(GLStateManager.getTextures().getTexGenSStates(i));
collection.add(GLStateManager.textures.getTexGenTStates(i)); collection.add(GLStateManager.getTextures().getTexGenTStates(i));
collection.add(GLStateManager.textures.getTexGenRStates(i)); collection.add(GLStateManager.getTextures().getTexGenRStates(i));
collection.add(GLStateManager.textures.getTexGenQStates(i)); collection.add(GLStateManager.getTextures().getTexGenQStates(i));
} }
} }
static { static {
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST enable bit GLStateManager.getAlphaTest()
, GLStateManager.alphaState // Alpha test function and reference value , GLStateManager.getAlphaState()
, GLStateManager.blendMode // GL_BLEND enable bit , GLStateManager.getBlendMode()
, GLStateManager.blendState // Blending source/destination functions, equation, blend color , GLStateManager.getBlendState()
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit , GLStateManager.getColorLogicOpState()
, GLStateManager.ditherState // GL_DITHER enable bit , GLStateManager.getDitherState()
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting , GLStateManager.getDrawBuffer()
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit , GLStateManager.getIndexLogicOpState()
, GLStateManager.logicOpMode // Logic op function , GLStateManager.getLogicOpMode()
, GLStateManager.colorMask // Color-mode and index-mode writemasks , GLStateManager.getColorMask()
, GLStateManager.clearColor // Color-mode and index-mode clear values , GLStateManager.getClearColor()
)); ));
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
GLStateManager.color // Current RGBA color GLStateManager.getColor()
, ShaderManager.getNormalStack() // Current normal vector , ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates , ShaderManager.getTexCoordStack() // Current texture coordinates
// Current color index // Current color index
@@ -110,78 +110,78 @@ public class Feature {
// GL_EDGE_FLAG flag // GL_EDGE_FLAG flag
)); ));
attribToFeatures.put(GL11.GL_DEPTH_BUFFER_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_DEPTH_BUFFER_BIT, ImmutableSet.of(
GLStateManager.depthTest // GL_DEPTH_TEST enable bit GLStateManager.getDepthTest()
, GLStateManager.depthState // Depth buffer test function , GLStateManager.getDepthState()
// Depth buffer clear value // Depth buffer clear value
// GL_DEPTH_WRITEMASK enable bit // GL_DEPTH_WRITEMASK enable bit
)); ));
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of( final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST flag GLStateManager.getAlphaTest()
, GLStateManager.autoNormalState // GL_AUTO_NORMAL flag , GLStateManager.getAutoNormalState()
, GLStateManager.blendMode // GL_BLEND flag , GLStateManager.getBlendMode()
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP flag , GLStateManager.getColorLogicOpState()
, GLStateManager.colorMaterial // GL_COLOR_MATERIAL , GLStateManager.getColorMaterial()
, GLStateManager.cullState // GL_CULL_FACE flag , GLStateManager.getCullState()
, GLStateManager.depthTest // GL_DEPTH_TEST flag , GLStateManager.getDepthTest()
, GLStateManager.ditherState // GL_DITHER flag , GLStateManager.getDitherState()
, GLStateManager.fogMode // GL_FOG flag , GLStateManager.getFogMode()
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP flag , GLStateManager.getIndexLogicOpState()
, GLStateManager.lightStates[0] // GL_LIGHT0 , GLStateManager.getLightStates()[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1 , GLStateManager.getLightStates()[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2 , GLStateManager.getLightStates()[2] // GL_LIGHT2
, GLStateManager.lightStates[3] // GL_LIGHT3 , GLStateManager.getLightStates()[3] // GL_LIGHT3
, GLStateManager.lightStates[4] // GL_LIGHT4 , GLStateManager.getLightStates()[4] // GL_LIGHT4
, GLStateManager.lightStates[5] // GL_LIGHT5 , GLStateManager.getLightStates()[5] // GL_LIGHT5
, GLStateManager.lightStates[6] // GL_LIGHT6 , GLStateManager.getLightStates()[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7 , GLStateManager.getLightStates()[7] // GL_LIGHT7
, GLStateManager.lightingState // GL_LIGHTING flag , GLStateManager.getLightingState()
, GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag , GLStateManager.getLineSmoothState()
, GLStateManager.lineStippleState // GL_LINE_STIPPLE flag , GLStateManager.getLineStippleState()
, GLStateManager.map1Color4State // GL_MAP1_COLOR_4 , GLStateManager.getMap1Color4State()
, GLStateManager.map1IndexState // GL_MAP1_INDEX , GLStateManager.getMap1IndexState()
, GLStateManager.map1NormalState // GL_MAP1_NORMAL , GLStateManager.getMap1NormalState()
, GLStateManager.map1TextureCoord1State // GL_MAP1_TEXTURE_COORD_1 , GLStateManager.getMap1TextureCoord1State()
, GLStateManager.map1TextureCoord2State // GL_MAP1_TEXTURE_COORD_2 , GLStateManager.getMap1TextureCoord2State()
, GLStateManager.map1TextureCoord3State // GL_MAP1_TEXTURE_COORD_3 , GLStateManager.getMap1TextureCoord3State()
, GLStateManager.map1TextureCoord4State // GL_MAP1_TEXTURE_COORD_4 , GLStateManager.getMap1TextureCoord4State()
, GLStateManager.map1Vertex3State // GL_MAP1_VERTEX_3 , GLStateManager.getMap1Vertex3State()
, GLStateManager.map1Vertex4State // GL_MAP1_VERTEX_4 , GLStateManager.getMap1Vertex4State()
, GLStateManager.map2Color4State // GL_MAP2_COLOR_4 , GLStateManager.getMap2Color4State()
, GLStateManager.map2IndexState // GL_MAP2_INDEX , GLStateManager.getMap2IndexState()
, GLStateManager.map2NormalState // GL_MAP2_NORMAL , GLStateManager.getMap2NormalState()
, GLStateManager.map2TextureCoord1State // GL_MAP2_TEXTURE_COORD_1 , GLStateManager.getMap2TextureCoord1State()
, GLStateManager.map2TextureCoord2State // GL_MAP2_TEXTURE_COORD_2 , GLStateManager.getMap2TextureCoord2State()
, GLStateManager.map2TextureCoord3State // GL_MAP2_TEXTURE_COORD_3 , GLStateManager.getMap2TextureCoord3State()
, GLStateManager.map2TextureCoord4State // GL_MAP2_TEXTURE_COORD_4 , GLStateManager.getMap2TextureCoord4State()
, GLStateManager.map2Vertex3State // GL_MAP2_VERTEX_3 , GLStateManager.getMap2Vertex3State()
, GLStateManager.map2Vertex4State // GL_MAP2_VERTEX_4 , GLStateManager.getMap2Vertex4State()
, GLStateManager.multisampleState // GL_MULTISAMPLE flag , GLStateManager.getMultisampleState()
, GLStateManager.normalizeState // GL_NORMALIZE flag , GLStateManager.getNormalizeState()
, GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag , GLStateManager.getPointSmoothState()
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag , GLStateManager.getPolygonOffsetPointState()
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag , GLStateManager.getPolygonOffsetLineState()
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag , GLStateManager.getPolygonOffsetFillState()
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag , GLStateManager.getPolygonSmoothState()
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE flag , GLStateManager.getPolygonStippleState()
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag , GLStateManager.getRescaleNormalState()
, GLStateManager.sampleAlphaToCoverageState // GL_SAMPLE_ALPHA_TO_COVERAGE flag , GLStateManager.getSampleAlphaToCoverageState()
, GLStateManager.sampleAlphaToOneState // GL_SAMPLE_ALPHA_TO_ONE flag , GLStateManager.getSampleAlphaToOneState()
, GLStateManager.sampleCoverageState // GL_SAMPLE_COVERAGE flag , GLStateManager.getSampleCoverageState()
, GLStateManager.scissorTest // GL_SCISSOR_TEST flag , GLStateManager.getScissorTest()
, GLStateManager.stencilTest // GL_STENCIL_TEST flag , GLStateManager.getStencilTest()
)); ));
// Enable bits for the user-definable clipping planes // Enable bits for the user-definable clipping planes
for(int i = 0; i < GLStateManager.clipPlaneStates.length; i++) { for(int i = 0; i < GLStateManager.getClipPlaneStates().length; i++) {
enableBits.add(GLStateManager.clipPlaneStates[i]); enableBits.add(GLStateManager.getClipPlaneStates()[i]);
} }
// GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags // GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) { for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
enableBits.add(GLStateManager.textures.getTexture1DStates(i)); enableBits.add(GLStateManager.getTextures().getTexture1DStates(i));
enableBits.add(GLStateManager.textures.getTextureUnitStates(i)); enableBits.add(GLStateManager.getTextures().getTextureUnitStates(i));
enableBits.add(GLStateManager.textures.getTexture3DStates(i)); enableBits.add(GLStateManager.getTextures().getTexture3DStates(i));
} }
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q // 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 // GL_AUTO_NORMAL enable bit
)); ));
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
GLStateManager.fogMode // GL_FOG enable bit GLStateManager.getFogMode()
, GLStateManager.fogState // Fog color , GLStateManager.getFogState()
// ^^ Fog density // ^^ Fog density
// ^^ Linear fog start // ^^ Linear fog start
// ^^ Linear fog end // ^^ Linear fog end
@@ -214,42 +214,42 @@ public class Feature {
// GL_TEXTURE_COMPRESSION_HINT setting // GL_TEXTURE_COMPRESSION_HINT setting
)); ));
attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of(
GLStateManager.colorMaterial // GL_COLOR_MATERIAL enable bit GLStateManager.getColorMaterial()
, GLStateManager.colorMaterialFace // GL_COLOR_MATERIAL_FACE value , GLStateManager.getColorMaterialFace()
, GLStateManager.colorMaterialParameter // Color material parameters that are tracking the current color , GLStateManager.getColorMaterialParameter()
, GLStateManager.lightModel // Ambient scene color, GL_LIGHT_MODEL_LOCAL_VIEWER, GL_LIGHT_MODEL_TWO_SIDE , GLStateManager.getLightModel()
, GLStateManager.lightingState // GL_LIGHTING enable bit , GLStateManager.getLightingState()
// Enable bit for each light // Enable bit for each light
, GLStateManager.lightStates[0] // GL_LIGHT0 , GLStateManager.getLightStates()[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1 , GLStateManager.getLightStates()[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2 , GLStateManager.getLightStates()[2] // GL_LIGHT2
, GLStateManager.lightStates[3] // GL_LIGHT3 , GLStateManager.getLightStates()[3] // GL_LIGHT3
, GLStateManager.lightStates[4] // GL_LIGHT4 , GLStateManager.getLightStates()[4] // GL_LIGHT4
, GLStateManager.lightStates[5] // GL_LIGHT5 , GLStateManager.getLightStates()[5] // GL_LIGHT5
, GLStateManager.lightStates[6] // GL_LIGHT6 , GLStateManager.getLightStates()[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7 , GLStateManager.getLightStates()[7] // GL_LIGHT7
// Ambient, diffuse, and specular intensity for each light // Ambient, diffuse, and specular intensity for each light
// Direction, position, exponent, and cutoff angle for each light // Direction, position, exponent, and cutoff angle for each light
// Constant, linear, and quadratic attenuation factors for each light // Constant, linear, and quadratic attenuation factors for each light
, GLStateManager.lightDataStates[0] , GLStateManager.getLightDataStates()[0]
, GLStateManager.lightDataStates[1] , GLStateManager.getLightDataStates()[1]
, GLStateManager.lightDataStates[2] , GLStateManager.getLightDataStates()[2]
, GLStateManager.lightDataStates[3] , GLStateManager.getLightDataStates()[3]
, GLStateManager.lightDataStates[4] , GLStateManager.getLightDataStates()[4]
, GLStateManager.lightDataStates[5] , GLStateManager.getLightDataStates()[5]
, GLStateManager.lightDataStates[6] , GLStateManager.getLightDataStates()[6]
, GLStateManager.lightDataStates[7] , GLStateManager.getLightDataStates()[7]
// Ambient, diffuse, specular, and emissive color for each material // Ambient, diffuse, specular, and emissive color for each material
// Ambient, diffuse, and specular color indices for each material // Ambient, diffuse, and specular color indices for each material
// Specular exponent for each material // Specular exponent for each material
, GLStateManager.frontMaterial , GLStateManager.getFrontMaterial()
, GLStateManager.backMaterial , GLStateManager.getBackMaterial()
, GLStateManager.shadeModelState // GL_SHADE_MODEL setting , GLStateManager.getShadeModelState()
)); ));
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag GLStateManager.getLineSmoothState()
, GLStateManager.lineStippleState // GL_LINE_STIPPLE enable bit , GLStateManager.getLineStippleState()
, GLStateManager.lineState // Line stipple pattern, repeat counter, and width , GLStateManager.getLineState()
)); ));
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
// GL_LIST_BASE setting // GL_LIST_BASE setting
@@ -274,31 +274,31 @@ public class Feature {
// GL_READ_BUFFER setting // GL_READ_BUFFER setting
)); ));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag GLStateManager.getPointSmoothState()
, GLStateManager.pointState // Point size , GLStateManager.getPointState()
)); ));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
GLStateManager.cullState // GL_CULL_FACE enable bit GLStateManager.getCullState()
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag , GLStateManager.getPolygonSmoothState()
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE enable bit , GLStateManager.getPolygonStippleState()
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag , GLStateManager.getPolygonOffsetFillState()
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag , GLStateManager.getPolygonOffsetLineState()
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag , GLStateManager.getPolygonOffsetPointState()
, GLStateManager.polygonState // GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_OFFSET_FACTOR/UNITS , GLStateManager.getPolygonState()
)); ));
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
// Polygon stipple pattern // Polygon stipple pattern
)); ));
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
GLStateManager.scissorTest // GL_SCISSOR_TEST enable bit GLStateManager.getScissorTest()
// Scissor box // Scissor box
)); ));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of( attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.stencilTest // GL_STENCIL_TEST enable bit GLStateManager.getStencilTest()
, GLStateManager.stencilState // Stencil function, ref, mask, ops, writemask, clear value , GLStateManager.getStencilState()
)); ));
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of( 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) // GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
// Enable bits for the four texture coordinates // Enable bits for the four texture coordinates
@@ -315,8 +315,8 @@ public class Feature {
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState // Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) { for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i)); textureAttribs.add(GLStateManager.getTextures().getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.textures.getTexEnvState(i)); textureAttribs.add(GLStateManager.getTextures().getTexEnvState(i));
} }
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q // 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( attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
// Coefficients of the six clipping planes // Coefficients of the six clipping planes
// Enable bits for the user-definable clipping planes
GLStateManager.matrixMode GLStateManager.getMatrixMode()
, GLStateManager.normalizeState // GL_NORMALIZE flag , GLStateManager.getNormalizeState()
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag , GLStateManager.getRescaleNormalState()
)); ));
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of( 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(); computeMVP();
final int stride = positionStride(); final int stride = positionStride();
final int elementSize = VAOManager.Attrib.glTypeSizeBytes(type); final int elementSize = GLTypes.sizeBytes(type);
final int indexByteCount = indexCount * elementSize; final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount); ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(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; package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager; 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.EXTBlendColor;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL12;
@@ -14,6 +16,10 @@ import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL43; import org.lwjgl.opengl.GL43;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public final class GLDebug { 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) { public static String getLightName(int light) {
return switch (light) { return switch (light) {
case GL11.GL_LIGHT0 -> "LIGHT0"; case GL11.GL_LIGHT0 -> "LIGHT0";
@@ -559,7 +546,9 @@ public final class GLDebug {
} }
public static void initDebugState() { 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(); debugState = new KHRDebugState();
} else { } else {
debugState = new UnsupportedDebugState(); 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) { public static void pushGroup(String group) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) { if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.pushGroup(group); debugState.pushGroup(group);
@@ -606,4 +599,49 @@ public final class GLDebug {
return ""; 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; 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.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode; 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.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -79,6 +88,77 @@ public class GlslTransformUtils {
return source; 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) { public static String getFormattedShader(ParseTree tree, String header) {
final StringBuilder sb = new StringBuilder(header + "\n"); final StringBuilder sb = new StringBuilder(header + "\n");
final String[] tabHolder = {""}; final String[] tabHolder = {""};
@@ -90,13 +90,33 @@ public final class QuadConverter {
drawSharedQuadEbo(first, vertexCount, primcount, true); drawSharedQuadEbo(first, vertexCount, primcount, true);
} }
private static int misalignedDraws;
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) { 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(); final boolean locked = GLStateManager.acquireDrawLock();
try { try {
GLStateManager.preDraw(); 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(); final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount); ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId); RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
@@ -184,7 +204,7 @@ public final class QuadConverter {
final int quadCount = indexCount / 4; final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6; final int triIndexCount = quadCount * 6;
final int bytesPerIndex = VAOManager.Attrib.glTypeSizeBytes(type); final int bytesPerIndex = GLTypes.sizeBytes(type);
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex); final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src); 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.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL42; import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43; import org.lwjgl.opengl.GL43;
@@ -40,6 +41,7 @@ public class RenderSystem {
private static boolean supportsCompute; private static boolean supportsCompute;
private static boolean supportsImageLoadStore; private static boolean supportsImageLoadStore;
private static boolean supportsSSBO; private static boolean supportsSSBO;
private static boolean supportsMultiDrawIndirect;
private static boolean supportsBufferStorage; private static boolean supportsBufferStorage;
private static boolean supportsClearTexture; private static boolean supportsClearTexture;
private static boolean supportsTesselation; private static boolean supportsTesselation;
@@ -52,6 +54,11 @@ public class RenderSystem {
@Getter private static int glMinor; @Getter private static int glMinor;
@Getter private static boolean coreProfile; @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) // Sampler object state tracking (null if unsupported)
private static int[] samplers; private static int[] samplers;
@@ -64,30 +71,38 @@ public class RenderSystem {
public static void initRenderer() { public static void initRenderer() {
if (rendererInitialized) return; if (rendererInitialized) return;
rendererInitialized = true; 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) { if (!isGLES) {
dsaState = new DSAARB(); try {
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA."); 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) { if (dsaState == null) {
dsaState = new DSAUnsupported(); 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(); BackendManager.init();
@@ -99,8 +114,10 @@ public class RenderSystem {
|| GLStateManager.capabilities.GL_ARB_shader_image_load_store || GLStateManager.capabilities.GL_ARB_shader_image_load_store
|| GLStateManager.capabilities.GL_EXT_shader_image_load_store; || GLStateManager.capabilities.GL_EXT_shader_image_load_store;
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object; 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; supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
// Cache maximum image units // Cache maximum image units
if (supportsImageLoadStore) { if (supportsImageLoadStore) {
@@ -140,7 +157,10 @@ public class RenderSystem {
GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings); GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
GLStateManager.LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects); 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); final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) { if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
coreProfile = true; 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) { 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.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer(); GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels); 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); 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) { public static int getTexLevelParameteri(int texture, int level, int pname) {
return dsaState.getTexLevelParameteri(texture, level, 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) { 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() { public static int getMaxImageUnits() {
@@ -340,13 +368,49 @@ public class RenderSystem {
} }
public static boolean supportsImageLoadStore() { public static boolean supportsImageLoadStore() {
if (RENDER_BACKEND.isSDLGPU()) return true;
return supportsImageLoadStore; 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() { public static boolean supportsSSBO() {
return supportsSSBO; return supportsSSBO;
} }
public static boolean supportsMultiDrawIndirect() {
return supportsMultiDrawIndirect;
}
public static boolean supportsBufferStorage() { public static boolean supportsBufferStorage() {
return supportsBufferStorage; return supportsBufferStorage;
} }
@@ -427,9 +491,14 @@ public class RenderSystem {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW); 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, 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) {
int height2, int bufferChoice, int filter) { final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter); 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() { public static int createFramebuffer() {
@@ -443,7 +512,7 @@ public class RenderSystem {
// TODO: Proper notification of compute support // TODO: Proper notification of compute support
public static boolean supportsCompute() { public static boolean supportsCompute() {
try { try {
return GLStateManager.capabilities.GL_ARB_compute_shader; return GLStateManager.capabilities.GL_ARB_compute_shader || GLStateManager.capabilities.OpenGL43;
} catch (Exception ignored) { } catch (Exception ignored) {
return false; return false;
} }
@@ -453,6 +522,10 @@ public class RenderSystem {
return GLStateManager.glGenBuffers(); 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) { public static void bindBufferBase(int target, int index, int buffer) {
RENDER_BACKEND.bindBufferBase(target, index, buffer); RENDER_BACKEND.bindBufferBase(target, index, buffer);
} }
@@ -505,6 +578,7 @@ public class RenderSystem {
} }
public static boolean supportsSnormFormats() { public static boolean supportsSnormFormats() {
if (isGLES()) return false;
return GLStateManager.capabilities.OpenGL31; 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" */ /** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Strips
static String parseGlVersionString(String info) { * "OpenGL ES " / "OpenGL ES GLSL ES " prefixes so ES contexts parse too. */
final Matcher matcher = SEMVER_PATTERN.matcher(Objects.requireNonNull(info)); 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()) { if (!matcher.matches()) {
throw new IllegalStateException("Could not parse GL version from \"" + info + "\""); 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; package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.AMDDebugOutput; import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback; import org.lwjgl.opengl.AMDDebugOutputCallback;
@@ -7,8 +8,10 @@ import org.lwjgl.opengl.ARBClearTexture;
import org.lwjgl.opengl.ARBDebugOutput; import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.ARBDebugOutputCallback; import org.lwjgl.opengl.ARBDebugOutputCallback;
import org.lwjgl.opengl.ARBDirectStateAccess; import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.ARBTimerQuery;
import org.lwjgl.opengl.ContextCapabilities; import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.EXTDirectStateAccess; import org.lwjgl.opengl.EXTDirectStateAccess;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GLContext; import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL12;
@@ -27,6 +30,8 @@ import org.lwjgl.opengl.GL44;
import org.lwjgl.opengl.GL45; import org.lwjgl.opengl.GL45;
import org.lwjgl.opengl.KHRDebug; import org.lwjgl.opengl.KHRDebug;
import org.lwjgl.opengl.KHRDebugCallback; import org.lwjgl.opengl.KHRDebugCallback;
import org.lwjgl.opengl.Display;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.DoubleBuffer; 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 @Override
public int getMinGLSLVersion() {return 330;} 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 @Override
public void flush() {GL11.glFlush();} public void flush() {GL11.glFlush();}
@@ -322,7 +344,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override @Override
public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) { 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 @Override
@@ -332,8 +354,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override @Override
public void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) { 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. GL32.glDrawElementsBaseVertex(mode, count, type, indices, baseVertex);
throw new UnsupportedOperationException("GLRenderBackend.drawElementsBaseVertex should not be called — LWJGL service handles this");
} }
@Override @Override
@@ -927,6 +948,32 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL30.glBindBufferBase(target, index, buffer); 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 @Override
public void bufferData(int target, long size, int usage) { public void bufferData(int target, long size, int usage) {
GL15.glBufferData(target, size, usage); GL15.glBufferData(target, size, usage);
@@ -1058,6 +1105,16 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return GL15.glIsBuffer(buffer); 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 @Override
public ByteBuffer mapBufferRange(int target, long offset, long length, int access) { public ByteBuffer mapBufferRange(int target, long offset, long length, int access) {
return GL30.glMapBufferRange(target, offset, length, access, null); return GL30.glMapBufferRange(target, offset, length, access, null);
@@ -1284,6 +1341,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return ARBDirectStateAccess.glGetTextureParameteri(texture, pname); return ARBDirectStateAccess.glGetTextureParameteri(texture, pname);
} }
@Override
public float getTextureParameterf(int texture, int target, int pname) {
return ARBDirectStateAccess.glGetTextureParameterf(texture, pname);
}
@Override @Override
public int getTextureLevelParameteri(int texture, int level, int pname) { public int getTextureLevelParameteri(int texture, int level, int pname) {
return ARBDirectStateAccess.glGetTextureLevelParameteri(texture, level, pname); return ARBDirectStateAccess.glGetTextureLevelParameteri(texture, level, pname);
@@ -1350,6 +1412,66 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
LWJGL.glDeleteSync(sync); 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 @Override
public void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) { 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); GL43.glClearBufferSubData(target, internalFormat, offset, size, format, type, data);
@@ -1366,7 +1488,13 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override @Override
public void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) { 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 @Override
@@ -1391,7 +1519,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override @Override
public int setupDebugOutput(DebugMessageHandler handler) { public int setupDebugOutput(DebugMessageHandler handler) {
if (caps.OpenGL43 || caps.GL_KHR_debug) { 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_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_MEDIUM, null, false);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, 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; return 1;
} else if (caps.GL_ARB_debug_output) { } 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_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_MEDIUM, null, false);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, 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; package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities; import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
import me.eigenraven.lwjgl3ify.api.SwapchainInvalidatingChange;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.DoubleBuffer; import java.nio.DoubleBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
@@ -16,14 +20,19 @@ import java.util.List;
*/ */
public abstract class RenderBackend { public abstract class RenderBackend {
public void onPostWindowCreate(long window, boolean debug) {}
public abstract void init(); public abstract void init();
public abstract void shutdown(); public abstract void shutdown();
public abstract boolean isAvailable(); public abstract boolean isAvailable();
public abstract String getName(); 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(); public abstract boolean hasContext();
/** Returns true if the calling thread owns the "GL Context" */
public abstract boolean isCurrent();
/** Higher priority backends are preferred. */ /** Higher priority backends are preferred. */
public int getPriority() { return 0; } public int getPriority() { return 0; }
@@ -35,14 +44,46 @@ public abstract class RenderBackend {
public List<String> pollDroppedFiles() { return Collections.emptyList(); } 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 int getMinGLSLVersion();
public abstract void flush(); public abstract void flush();
public abstract void finish(); public abstract void finish();
public void onFrameBegin() {} public abstract void setVSyncEnabled(boolean enabled);
public void onFrameBegin() {
if (GLStateManager.takeStateSeedPending()) {
GLStateManager.replayStateToBackend();
}
}
public void onFrameEnd() {} 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 enable(int cap);
public abstract void enablei(int cap, int index); public abstract void enablei(int cap, int index);
public abstract void disable(int cap); public abstract void disable(int cap);
@@ -60,6 +101,7 @@ public abstract class RenderBackend {
public abstract void frontFace(int mode); public abstract void frontFace(int mode);
public abstract void polygonMode(int face, int mode); public abstract void polygonMode(int face, int mode);
public abstract void polygonOffset(float factor, float units); 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 stencilFunc(int func, int ref, int mask);
public abstract void stencilOp(int sfail, int dpfail, int dppass); public abstract void stencilOp(int sfail, int dpfail, int dppass);
public abstract void stencilMask(int mask); 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, ShortBuffer indices) {}
public void drawElements(int mode, int count, int type, ByteBuffer 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 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 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 drawElementsInstanced(int mode, int count, int type, long indices, int primcount);
public abstract void drawArraysInstanced(int mode, int first, int count, int primcount); public abstract void drawArraysInstanced(int mode, int first, int count, int primcount);
public void provokingVertex(int provokeMode) {} public void provokingVertex(int provokeMode) {}
public abstract void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex); 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 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 drawBuffer(int mode);
public abstract void dispatchCompute(int numGroupsX, int numGroupsY, int numGroupsZ); public abstract void dispatchCompute(int numGroupsX, int numGroupsY, int numGroupsZ);
public abstract void dispatchComputeIndirect(long offset); 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 bindFramebuffer(int target, int framebuffer);
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level); 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 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 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 blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public abstract void drawBuffers(int buffer); public abstract void drawBuffers(int buffer);
@@ -158,6 +218,7 @@ public abstract class RenderBackend {
public abstract void deleteShader(int shader); public abstract void deleteShader(int shader);
public abstract void shaderSource(int shader, CharSequence source); public abstract void shaderSource(int shader, CharSequence source);
public abstract void compileShader(int shader); 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 int createProgram();
public abstract void deleteProgram(int program); public abstract void deleteProgram(int program);
public abstract void attachShader(int program, int shader); 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 void getProgramiv(int program, int pname, IntBuffer params);
public abstract String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType); 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 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 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, CharSequence name);
public abstract int getAttribLocation(int program, ByteBuffer name); public abstract int getAttribLocation(int program, ByteBuffer name);
public abstract int getUniformLocation(int program, CharSequence 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 uniform1i(int location, int v0);
public abstract void uniform1f(int location, float v0); public abstract void uniform1f(int location, float v0);
public void uniform1fv(int location, FloatBuffer values) {} 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 uniform2f(int location, float v0, float v1);
public abstract void uniform2i(int location, int v0, int v1); public abstract void uniform2i(int location, int v0, int v1);
public abstract void uniform3f(int location, float v0, float v1, float v2); public abstract void 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 deleteBuffers(IntBuffer buffers);
public abstract void bindBuffer(int target, int buffer); public abstract void bindBuffer(int target, int buffer);
public abstract void bindBufferBase(int target, int index, 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, long size, int usage);
public abstract void bufferData(int target, ByteBuffer data, int usage); public abstract void bufferData(int target, ByteBuffer data, int usage);
public abstract void bufferData(int target, FloatBuffer data, int usage); public abstract void bufferData(int target, FloatBuffer data, int usage);
@@ -251,6 +322,16 @@ public abstract class RenderBackend {
public abstract boolean isBuffer(int buffer); public abstract boolean isBuffer(int buffer);
public abstract ByteBuffer mapBufferRange(int target, long offset, long length, int access); public abstract ByteBuffer mapBufferRange(int target, long offset, long length, int access);
public abstract void flushMappedBufferRange(int target, long offset, long length); 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 int genVertexArrays();
public abstract void deleteVertexArrays(int array); 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 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 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 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 getTextureLevelParameteri(int texture, int level, int pname);
public abstract int getInteger(int pname); public abstract int getInteger(int pname);
public abstract void getInteger(int pname, IntBuffer params); 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 float getFloat(int pname);
public abstract void getFloat(int pname, FloatBuffer params); public abstract void getFloat(int pname, FloatBuffer params);
public abstract boolean getBoolean(int pname); public abstract boolean getBoolean(int pname);
@@ -315,8 +398,30 @@ public abstract class RenderBackend {
public abstract long fenceSync(int condition, int flags); public abstract long fenceSync(int condition, int flags);
public abstract int clientWaitSync(long sync, int flags, long timeout); public abstract int clientWaitSync(long sync, int flags, long timeout);
public abstract void deleteSync(long sync); 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 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 clearTexImage(int texture, int level, int format, int type);
public abstract void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format); public abstract void 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 // Debug message insertion
public void debugMessageInsert(int source, int type, int id, int severity, CharSequence message) {} 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; package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
@@ -75,6 +76,11 @@ public class DSAARB extends DSAUnsupported {
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname)); return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname));
} }
@Override
public float getTexParameterf(int texture, int target, int pname) {
return RENDER_BACKEND.getTextureParameterf(texture, target, pname);
}
@Override @Override
public int getTexLevelParameteri(int texture, int level, int pname) { public int getTexLevelParameteri(int texture, int level, int pname) {
return RENDER_BACKEND.getTextureLevelParameteri(texture, level, pname); return RENDER_BACKEND.getTextureLevelParameteri(texture, level, pname);
@@ -87,19 +93,20 @@ public class DSAARB extends DSAUnsupported {
@Override @Override
public void bindTextureToUnit(int unit, int texture) { public void bindTextureToUnit(int unit, int texture) {
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return; bindTextureToUnit(0, unit, texture);
if (texture == 0) {
super.bindTextureToUnit(unit, texture);
} else {
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture);
}
} }
@Override @Override
public void bindTextureToUnit(int target, int unit, int texture) { 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 @Override
@@ -23,6 +23,8 @@ public interface DSAAccess {
int getTexParameteri(int texture, int target, int pname); int getTexParameteri(int texture, int target, int pname);
float getTexParameterf(int texture, int target, int pname);
int getTexLevelParameteri(int texture, int level, int pname); int getTexLevelParameteri(int texture, int level, int pname);
void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height); void 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); 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 @Override
public int getTexLevelParameteri(int texture, int level, int pname) { public int getTexLevelParameteri(int texture, int level, int pname) {
final int previous = GLStateManager.getBoundTextureForServerState(); 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) { private static void emitUniforms(StringBuilder sb, FragmentKey key) {
// Samplers for each enabled unit
for (int i = 0; i < key.nrEnabledUnits(); i++) { for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) { if (key.unitEnabled(i)) {
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n"); sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
} }
} }
if (key.alphaTestEnabled()) { sb.append(FFPUniformBlock.GLSL_DECL);
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('\n'); sb.append('\n');
} }
@@ -14,8 +14,8 @@ public final class GeometryShaderGenerator {
sb.append("layout(lines) in;\n"); sb.append("layout(lines) in;\n");
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n"); sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
sb.append("uniform vec2 u_ViewportSize;\n"); sb.append(FFPUniformBlock.GLSL_DECL);
sb.append("uniform float u_LineWidth;\n\n"); sb.append('\n');
sb.append("// Pass-through varyings\n"); sb.append("// Pass-through varyings\n");
sb.append("in vec4 v_Color_gs[];\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; 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.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend; import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import lombok.Getter; import lombok.Getter;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40; import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43; import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug; 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 java.util.concurrent.atomic.AtomicInteger;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND; 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 VertexKey vertexKey;
@Getter private final FragmentKey fragmentKey; @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 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) { Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
this.programId = programId; this.programId = programId;
this.vertexKey = vertexKey; this.vertexKey = vertexKey;
this.fragmentKey = fragmentKey; 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++) { for (int i = 0; i < 4; i++) {
locTextureMatrix[i] = loc("u_TextureMatrix" + i); locSampler[i] = RENDER_BACKEND.getUniformLocation(programId, "u_Sampler" + i);
locCurrentTexCoord[i] = loc("u_CurrentTexCoord" + 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() { public void destroy() {
@@ -241,7 +83,7 @@ public class Program {
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")"; final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName); 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++) { for (int i = 0; i < shaderCount; i++) {
backend.detachShader(program, shaders[i]); backend.detachShader(program, shaders[i]);
backend.deleteShader(shaders[i]); backend.deleteShader(shaders[i]);
@@ -250,6 +92,11 @@ public class Program {
final Program ffpProgram = new Program(program, vk, fk); 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(); final int previousProgram = GLStateManager.getActiveProgram();
backend.useProgram(program); backend.useProgram(program);
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
@@ -268,6 +115,10 @@ public class Program {
private static int compileShader(int type, String src, String name) { private static int compileShader(int type, String src, String name) {
final RenderBackend backend = RENDER_BACKEND; final RenderBackend backend = RENDER_BACKEND;
final int shader = backend.createShader(type); 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.shaderSource(shader, src);
backend.compileShader(shader); backend.compileShader(shader);
@@ -296,4 +147,22 @@ public class Program {
return shader; 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 it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Setter; import lombok.Setter;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -25,6 +26,8 @@ import java.util.IdentityHashMap;
* </pre> * </pre>
*/ */
public class ShaderCache { 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 SLOT_STRIDE = 6;
private static final int INITIAL_CAPACITY = 64; // power of 2 private static final int INITIAL_CAPACITY = 64; // power of 2
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero) private 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 VertexKey vk = VertexKey.fromPacked(vkPacked);
final String vertSrc = getOrGenerateVertex(vk);
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen); final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
String fragSrc = findFragSource(fkPacked, fkLen); final String vertSrc;
if (fragSrc == null) { String fragSrc;
fragSrc = FragmentShaderGenerator.generate(fk); final String geomSrc;
uniqueFragCount++; 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 insertIdx = findEmptySlot(hash);
final int base = insertIdx * SLOT_STRIDE; final int base = insertIdx * SLOT_STRIDE;
keys[base] = vkPacked; keys[base] = vkPacked;
@@ -1,12 +1,15 @@
package com.gtnewhorizons.angelica.glsm.ffp; package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags; 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.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler; import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks; import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig; import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.QuadConverter; 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.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack; import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer; import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
@@ -14,7 +17,6 @@ import lombok.Getter;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.joml.Vector4f; import org.joml.Vector4f;
import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND; 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 normalGeneration;
@Getter private static int texCoordGeneration; @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() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; } public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false; @Getter private boolean enabled = false;
private ShaderManager() { private ShaderManager() {
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) { cache.setDumpDir(SystemProperties.shaderDumpDir("ffp"));
cache.setDumpDir(Paths.get("ffp_shaders")); VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
} VAOManager.setCurrentVertexFlags(format.getVertexFlags());
return false;
});
} }
public static ShaderManager getInstance() { public static ShaderManager getInstance() {
@@ -110,6 +119,7 @@ public final class ShaderManager {
lastBoundProgramId = -1; lastBoundProgramId = -1;
currentVertexKeyPacked = Long.MIN_VALUE; currentVertexKeyPacked = Long.MIN_VALUE;
currentFKLen = 0; currentFKLen = 0;
GLStateManager.forceAttribDefaultsDirty();
} }
public void preDraw() { public void preDraw() {
@@ -133,7 +143,7 @@ public final class ShaderManager {
if (!active) return; if (!active) return;
preDrawCalls++;
final int fkLen = FragmentKey.packFromState(currentFKScratch); final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen); final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask); final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
@@ -152,6 +162,7 @@ public final class ShaderManager {
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen); currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
final int programId = currentProgram.getProgramId(); final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) { if (programId != lastBoundProgramId) {
if (Tracy.ENABLED) variantSwitches++;
RENDER_BACKEND.useProgram(programId); RENDER_BACKEND.useProgram(programId);
lastBoundProgramId = programId; lastBoundProgramId = programId;
} }
@@ -159,10 +170,17 @@ public final class ShaderManager {
private void uploadUniforms() { private void uploadUniforms() {
if (currentProgram != null) { 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) { public static void setCurrentNormal(float x, float y, float z) {
currentNormal.set(x, y, z); currentNormal.set(x, y, z);
normalGeneration++; normalGeneration++;
@@ -186,13 +204,31 @@ public final class ShaderManager {
uniforms.destroy(); uniforms.destroy();
final GLSMInitConfig config = GLStateManager.getInitConfig(); final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run(); if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
com.gtnewhorizons.angelica.glsm.QuadConverter.destroy(); QuadConverter.destroy();
active = false; active = false;
currentProgram = null; currentProgram = null;
} }
public String getDebugInfo() { public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants)", 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()); 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.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState; import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState; import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig; import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math; import org.joml.Math;
import org.joml.Matrix3f; import org.joml.Matrix3f;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f; import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer; 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.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree; 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; import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/** /**
* Uploads GLSM cached state to the active FFP program's uniforms. * Maintains the shared {@link FFPUniformBlock} image from GLSM cached state and streams it to the UBO ring.
*
* 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)
*/ */
public class Uniforms { 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 Matrix4f mvpMatrix = new Matrix4f();
private final Matrix3f normalMatrix = new Matrix3f(); 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 LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2); private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
/** public void upload() {
* Upload all relevant uniforms to the given FFP program based on current GLSM state. boolean dirty = false;
* Uses generation counters to skip categories that haven't changed since last upload. contentChanged = false;
*/
public void upload(Program program) {
final Program.UploadState st = program.uploadState;
final int mvGen = GLStateManager.mvGeneration; final int mvG = GLStateManager.getMvGeneration();
final int mvLinearGen = GLStateManager.mvLinearGeneration; final int projG = GLStateManager.getProjGeneration();
final int projGen = GLStateManager.projGeneration; final boolean mvChanged = mvG != mvGen;
final int texMatGen = GLStateManager.texMatrixGeneration; final boolean projChanged = projG != projGen;
final boolean mvChanged = mvGen != st.mvGen; if (mvChanged || projChanged) {
final boolean mvLinearChanged = mvLinearGen != st.mvLinearGen; stageMatrices(mvChanged, projChanged);
final boolean projChanged = projGen != st.projGen; mvGen = mvG;
final boolean texMatChanged = texMatGen != st.texMatGen; projGen = projG;
if (mvChanged || projChanged || texMatChanged) { stagedMatrices++;
uploadMatrices(program, mvChanged, mvLinearChanged, projChanged, texMatChanged); dirty = true;
st.mvGen = mvGen;
st.mvLinearGen = mvLinearGen;
st.projGen = projGen;
st.texMatGen = texMatGen;
} }
final int mvLinG = GLStateManager.getMvLinearGeneration();
if (program.getVertexKey().lightingEnabled()) { if (mvLinG != mvLinearGen) {
final int litGen = GLStateManager.lightingGeneration; stageNormalMatrix();
if (litGen != st.lightingGen) { mvLinearGen = mvLinG;
uploadLighting(program); dirty = true;
st.lightingGen = litGen;
}
} }
final int texMatG = GLStateManager.getTexMatrixGeneration();
// Current color/normal/texcoord — skip if generation unchanged if (texMatG != texMatGen) {
if (!program.getVertexKey().hasVertexColor()) { stageTextureMatrices();
final int colorGen = GLStateManager.colorGeneration; texMatGen = texMatG;
if (colorGen != st.colorGen) { dirty = true;
uploadCurrentColor(program);
st.colorGen = colorGen;
}
} }
final int litG = GLStateManager.getLightingGeneration();
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) { if (litG != lightingGen) {
final int normalGen = ShaderManager.getNormalGeneration(); stageLighting();
if (normalGen != st.normalGen) { lightingGen = litG;
uploadCurrentNormal(program); stagedLighting++;
st.normalGen = normalGen; dirty = true;
}
} }
final int colG = GLStateManager.getColorGeneration();
if (program.getVertexKey().anyUnitTexCoordEnabled()) { if (colG != colorGen) {
final int texGen = ShaderManager.getTexCoordGeneration(); stageCurrentColor();
if (texGen != st.texCoordGen) { colorGen = colG;
uploadCurrentTexCoords(program); stagedColor++;
st.texCoordGen = texGen; dirty = true;
}
} }
final int normG = ShaderManager.getNormalGeneration();
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) { if (normG != normalGen) {
if (GLSMConfig.lastBrightnessX != st.lightmapX || GLSMConfig.lastBrightnessY != st.lightmapY) { final Vector3f normal = ShaderManager.getCurrentNormal();
uploadCurrentLightmapCoord(program); putVec3(FFPUniformBlock.CURRENT_NORMAL, normal.x, normal.y, normal.z);
st.lightmapX = GLSMConfig.lastBrightnessX; normalGen = normG;
st.lightmapY = GLSMConfig.lastBrightnessY; stagedNormal++;
} dirty = true;
} }
final int tcG = ShaderManager.getTexCoordGeneration();
if (program.getVertexKey().texGenEnabled()) { if (tcG != texCoordGen) {
final int tgGen = GLStateManager.texGenGeneration; putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ShaderManager.getCurrentTexCoord(0));
if (tgGen != st.texGenGen) { putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ShaderManager.getCurrentTexCoord(2));
uploadTexGen(program); putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ShaderManager.getCurrentTexCoord(3));
st.texGenGen = tgGen; texCoordGen = tcG;
} stagedTexCoord++;
dirty = true;
} }
final float brightX = GLSMConfig.lastBrightnessX;
if (program.getVertexKey().clipPlanesEnabled()) { final float brightY = GLSMConfig.lastBrightnessY;
final int cpGen = GLStateManager.clipPlaneGeneration; if (brightX != lightmapX || brightY != lightmapY) {
if (cpGen != st.clipPlaneGen) { putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD, brightX);
uploadClipPlanes(program); putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD + 4, brightY);
st.clipPlaneGen = cpGen; lightmapX = brightX;
} lightmapY = brightY;
stagedLightmap++;
dirty = true;
} }
final int tgG = GLStateManager.getTexGenGeneration();
final int fragGen = GLStateManager.fragmentGeneration; if (tgG != texGenGen) {
if (fragGen != st.fragmentGen) { stageTexGen();
uploadFragmentUniforms(program); texGenGen = tgG;
st.fragmentGen = fragGen; stagedTexGen++;
dirty = true;
} }
final int cpG = GLStateManager.getClipPlaneGeneration();
// Wide line emulation uniforms if (cpG != clipPlaneGen) {
if (program.locLineWidth != -1 && program.locViewportSize != -1) { stageClipPlanes();
uploadWideLineUniforms(program); clipPlaneGen = cpG;
stagedClipPlanes++;
dirty = true;
} }
final int fragG = GLStateManager.getFragmentGeneration();
if (program.locLineStipple != -1) { if (fragG != fragmentGen) {
uploadLineStippleUniforms(program); stageFragment();
fragmentGen = fragG;
stagedFragment++;
dirty = true;
} }
} final float lw = GLStateManager.getLineState().getWidth();
if (lw != lineWidth) {
private void uploadLineStippleUniforms(Program program) { putFloat(FFPUniformBlock.LINE_WIDTH, lw);
final Program.UploadState st = program.uploadState; lineWidth = lw;
final LineState line = GLStateManager.getLineState(); stagedMisc++;
dirty = true;
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 ViewportState vp = GLStateManager.getViewportState(); final ViewportState vp = GLStateManager.getViewportState();
if (vp.x != st.stippleViewportX || vp.y != st.stippleViewportY || vp.width != st.stippleViewportW || vp.height != st.stippleViewportH) { if (vp.x != viewportX || vp.y != viewportY || vp.width != viewportWidth || vp.height != viewportHeight) {
RENDER_BACKEND.uniform4f(program.locViewport, vp.x, vp.y, vp.width, vp.height); putFloat(FFPUniformBlock.VIEWPORT_SIZE, vp.width);
st.stippleViewportX = vp.x; putFloat(FFPUniformBlock.VIEWPORT_SIZE + 4, vp.height);
st.stippleViewportY = vp.y; putVec4(FFPUniformBlock.VIEWPORT, vp.x, vp.y, vp.width, vp.height);
st.stippleViewportW = vp.width; viewportX = vp.x;
st.stippleViewportH = vp.height; 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 mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix(); final Matrix4f proj = GLStateManager.getProjectionMatrix();
// ModelView + derived (normal matrix, normal scale)
if (mvChanged) { if (mvChanged) {
if (program.locModelViewMatrix != -1) { putMat4(FFPUniformBlock.MODEL_VIEW_MATRIX, mv);
mv.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
}
} }
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 (projChanged) {
if (program.locProjectionMatrix != -1) { putMat4(FFPUniformBlock.PROJECTION_MATRIX, proj);
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);
}
} }
proj.mul(mv, mvpMatrix);
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
} }
private void uploadLighting(Program program) { private void stageNormalMatrix() {
final VertexKey vk = program.getVertexKey(); GLStateManager.getModelViewMatrix().normal(normalMatrix);
putMat3(FFPUniformBlock.NORMAL_MATRIX, normalMatrix);
if (vk.colorMaterialEnabled()) { final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
uploadLightingColorMaterial(program, vk); putFloat(FFPUniformBlock.NORMAL_SCALE, scale);
} else {
uploadLightingPreComputed(program, vk);
}
} }
/** private void stageTextureMatrices() {
* Color material active: upload raw light and material values. putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, GLStateManager.getTextures().getTextureUnitMatrix(0));
* The shader multiplies them with vertex color at runtime. putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, GLStateManager.getTextures().getTextureUnitMatrix(2));
*/ putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, GLStateManager.getTextures().getTextureUnitMatrix(3));
private void uploadLightingColorMaterial(Program program, VertexKey vk) { putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, GLStateManager.getTextures().getTextureUnitMatrix(1));
}
private void stageLighting() {
final MaterialState mat = GLStateManager.getFrontMaterial(); 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); putVec4(FFPUniformBlock.LIGHT_MODEL_AMBIENT, lmAmbient);
uploadVec4(program.locMaterialEmission, mat.emission); putVec4(FFPUniformBlock.MATERIAL_EMISSION, mat.emission);
uploadVec4(program.locMaterialAmbient, mat.ambient); putVec4(FFPUniformBlock.MATERIAL_AMBIENT, mat.ambient);
uploadVec4(program.locMaterialDiffuse, mat.diffuse); putVec4(FFPUniformBlock.MATERIAL_DIFFUSE, mat.diffuse);
uploadVec4(program.locMaterialSpecular, mat.specular); putVec4(FFPUniformBlock.MATERIAL_SPECULAR, mat.specular);
if (program.locMaterialShininess != -1) { putFloat(FFPUniformBlock.MATERIAL_SHININESS, mat.shininess);
RENDER_BACKEND.uniform1f(program.locMaterialShininess, 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()) { putVec4(FFPUniformBlock.SCENE_COLOR,
final LightState light0 = GLStateManager.getLightDataStates()[0]; mat.emission.x + mat.ambient.x * lmAmbient.x,
uploadVec4(program.locLight0Ambient, light0.ambient); mat.emission.y + mat.ambient.y * lmAmbient.y,
uploadVec4(program.locLight0Diffuse, light0.diffuse); mat.emission.z + mat.ambient.z * lmAmbient.z,
uploadVec4(program.locLight0Specular, light0.specular); mat.diffuse.w);
uploadVec4(program.locLight0Position, light0.position); putLightProduct(FFPUniformBlock.LIGHT_PROD0_AMBIENT, light0.ambient, mat.ambient);
} putLightProduct(FFPUniformBlock.LIGHT_PROD0_DIFFUSE, light0.diffuse, mat.diffuse);
if (vk.light1Enabled()) { putLightProduct(FFPUniformBlock.LIGHT_PROD0_SPECULAR, light0.specular, mat.specular);
final LightState light1 = GLStateManager.getLightDataStates()[1]; putLightProduct(FFPUniformBlock.LIGHT_PROD1_AMBIENT, light1.ambient, mat.ambient);
uploadVec4(program.locLight1Ambient, light1.ambient); putLightProduct(FFPUniformBlock.LIGHT_PROD1_DIFFUSE, light1.diffuse, mat.diffuse);
uploadVec4(program.locLight1Diffuse, light1.diffuse); putLightProduct(FFPUniformBlock.LIGHT_PROD1_SPECULAR, light1.specular, mat.specular);
uploadVec4(program.locLight1Specular, light1.specular);
uploadVec4(program.locLight1Position, light1.position);
}
} }
/** private void putLightProduct(int offset, Vector4f lightVal, Vector4f materialVal) {
* No color material: upload pre-computed light products and scene color. putVec3(offset, lightVal.x * materialVal.x, lightVal.y * materialVal.y, lightVal.z * materialVal.z);
* 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 stageCurrentColor() {
* 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
final var color = GLStateManager.getColor(); final var color = GLStateManager.getColor();
tempVec4.set( putVec4(FFPUniformBlock.CURRENT_COLOR,
Math.clamp(0f, 1f, color.getRed()), Math.clamp(0f, 1f, color.getRed()),
Math.clamp(0f, 1f, color.getGreen()), Math.clamp(0f, 1f, color.getGreen()),
Math.clamp(0f, 1f, color.getBlue()), Math.clamp(0f, 1f, color.getBlue()),
Math.clamp(0f, 1f, color.getAlpha())); Math.clamp(0f, 1f, color.getAlpha()));
vec4Buf.clear();
tempVec4.get(vec4Buf);
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
} }
private void uploadCurrentTexCoords(Program program) { private void stageTexGen() {
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();
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0); final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_S, tg.getObjectPlane(GL11.GL_S));
if (vk.texGenModeS() == VertexKey.TG_OBJ_LINEAR) { putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_T, tg.getObjectPlane(GL11.GL_T));
uploadPlane(program.locTexGenObjPlaneS, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_S)); putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_R, tg.getObjectPlane(GL11.GL_R));
} else if (vk.texGenModeS() == VertexKey.TG_EYE_LINEAR) { putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_Q, tg.getObjectPlane(GL11.GL_Q));
uploadPlane(program.locTexGenEyePlaneS, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_S)); 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));
if (vk.texGenModeT() == VertexKey.TG_OBJ_LINEAR) { putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_Q, tg.getEyePlane(GL11.GL_Q));
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));
}
} }
private void uploadPlane(int loc, float[] plane) { private void stageClipPlanes() {
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;
final ClipPlaneState cps = GLStateManager.getClipPlaneState(); final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear(); clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) { for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf); cps.putEyePlane(i, clipPlaneBuf);
} }
clipPlaneBuf.flip(); final long src = memAddress0(clipPlaneBuf);
RENDER_BACKEND.uniform4(program.locClipPlanes, clipPlaneBuf); for (int i = 0; i < 32 * 4; i += 4) {
putInt(FFPUniformBlock.CLIP_PLANES + i, memGetInt(src + i));
}
} }
private void uploadFragmentUniforms(Program program) { private void stageFragment() {
final FragmentKey fk = program.getFragmentKey(); 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++) { for (int i = 0; i < 4; i++) {
if (program.locTexEnvColor[i] != -1) { final var envState = GLStateManager.getTextures().getTexEnvState(i);
final var envState = GLStateManager.getTextures().getTexEnvState(i); putVec4(FFPUniformBlock.TEX_ENV_COLOR_0 + i * 16,
vec4Buf.clear(); envState.envColorR, envState.envColorG, envState.envColorB, envState.envColorA);
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);
}
} }
if (fk.overlayEnabled() && program.locOverlayColor != -1) { putVec4(FFPUniformBlock.OVERLAY_COLOR,
vec4Buf.clear(); GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
vec4Buf.put(GLStateManager.getOverlayR()); GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
vec4Buf.put(GLStateManager.getOverlayG());
vec4Buf.put(GLStateManager.getOverlayB());
vec4Buf.put(GLStateManager.getOverlayA());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locOverlayColor, vec4Buf);
}
// Fog // Mesa STATE_FOG_PARAMS_OPTIMIZED
if (fk.fogMode() != FragmentKey.FOG_NONE) {
uploadFog(program);
}
}
/**
* Upload optimized fog params (following Mesa's STATE_FOG_PARAMS_OPTIMIZED):
*/
private void uploadFog(Program program) {
final FogState fog = GLStateManager.getFogState(); final FogState fog = GLStateManager.getFogState();
final float start = fog.getStart();
if (program.locFogParams != -1) { final float end = fog.getEnd();
final float start = fog.getStart(); final float density = fog.getDensity();
final float end = fog.getEnd(); final float range = end - start;
final float density = fog.getDensity(); putVec4(FFPUniformBlock.FOG_PARAMS,
final float range = end - start; range != 0.0f ? -1.0f / range : 0.0f,
range != 0.0f ? end / range : 1.0f,
vec4Buf.clear(); (float) (density / LN2),
vec4Buf.put(range != 0.0f ? -1.0f / range : 0.0f); // [0]: -1/(end-start) (float) (density / SQRT_LN2));
vec4Buf.put(range != 0.0f ? end / range : 1.0f); // [1]: end/(end-start) putVec4(FFPUniformBlock.FOG_COLOR,
vec4Buf.put((float)(density / LN2)); // [2]: density/ln(2) (float) fog.getFogColor().x, (float) fog.getFogColor().y, (float) fog.getFogColor().z,
vec4Buf.put((float)(density / SQRT_LN2)); // [3]: density/sqrt(ln(2)) fog.getFogAlpha());
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);
}
} }
private void uploadWideLineUniforms(Program program) { private void putFloat(int offset, float v) {
final Program.UploadState st = program.uploadState; final long a = stagingAddress + offset;
final float lineWidth = GLStateManager.getLineState().getWidth(); final int bits = Float.floatToRawIntBits(v);
if (lineWidth != st.lineWidth) { if (memGetInt(a) != bits) contentChanged = true;
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth); memPutInt(a, bits);
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 uploadVec4(int loc, Vector4f v) { private void putInt(int offset, int v) {
if (loc == -1) return; final long a = stagingAddress + offset;
vec4Buf.clear(); if (memGetInt(a) != v) contentChanged = true;
v.get(vec4Buf); memPutInt(a, v);
RENDER_BACKEND.uniform4(loc, vec4Buf); }
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() { public void destroy() {
memFree(mat4Buf); memFree(staging);
memFree(mat3Buf);
memFree(vec4Buf);
memFree(vec3Buf);
memFree(clipPlaneBuf); memFree(clipPlaneBuf);
if (ring != null) {
ring.destroy();
ring = null;
}
bound = false;
} }
} }
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.ffp; package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL15;
@@ -315,17 +316,7 @@ public final class VAOManager {
} }
public int typeSizeBytes() { public int typeSizeBytes() {
return glTypeSizeBytes(type); return GLTypes.sizeBytes(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;
};
} }
public float readComponent(ByteBuffer buf, int base, int component) { 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_UNIT_TEXMAT_BASE = 37;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41; private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41;
private static final int BIT_LINE_STIPPLE = 42; 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_NONE = 0;
public static final int TG_OBJ_LINEAR = 1; 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 clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); } public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); } 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 cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; } public boolean 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); bits |= (1L << BIT_LINE_STIPPLE);
} }
if (GLStateManager.instancedFfpDrawActive) {
bits |= (1L << BIT_INSTANCED);
}
return bits; return bits;
} }
@@ -258,12 +264,12 @@ public final class VertexKey {
@Override @Override
public String toString() { 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(), packed, lightingEnabled(), light0Enabled(), light1Enabled(),
colorMaterialEnabled(), fogEnabled(), colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, 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, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), 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()) { if (key.hasVertexNormal()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n"); 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'); sb.append('\n');
} }
@@ -78,119 +86,10 @@ public final class VertexShaderGenerator {
} }
private static void emitUniforms(StringBuilder sb, VertexKey key) { private static void emitUniforms(StringBuilder sb, VertexKey key) {
sb.append("// Matrices\n"); sb.append(FFPUniformBlock.GLSL_DECL);
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('\n'); 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) { private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n"); sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n"); sb.append("out vec4 v_Color;\n");
@@ -218,22 +117,30 @@ public final class VertexShaderGenerator {
private static void emitPositionTransform(StringBuilder sb, VertexKey key) { private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Position transform\n"); sb.append(" // Position transform\n");
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\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 // Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) { if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n"); sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
}
} }
sb.append('\n'); sb.append('\n');
} }
private static void emitNormalTransform(StringBuilder sb, VertexKey key) { private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n"); sb.append(" // Normal transform\n");
if (key.hasVertexNormal()) { final String srcNormal = key.hasVertexNormal() ? "a_Normal" : "u_CurrentNormal";
sb.append(" vec3 normal = u_NormalMatrix * a_Normal;\n"); if (key.instancedDraw()) {
} else { sb.append(" vec3 normal = normalize(mat3(instMV) * ").append(srcNormal).append(");\n");
sb.append(" vec3 normal = u_NormalMatrix * u_CurrentNormal;\n"); sb.append('\n');
return;
} }
sb.append(" vec3 normal = u_NormalMatrix * ").append(srcNormal).append(";\n");
if (key.normalizeEnabled()) { if (key.normalizeEnabled()) {
sb.append(" normal = normalize(normal);\n"); 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) // 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(" vec4 matColor = ");
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor"); sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancedDraw()) {
sb.append(" * a_InstColor");
}
sb.append(";\n"); sb.append(";\n");
// Emission source: vertex color for CM_EMISSION, else material uniform // 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) { private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
sb.append(" // Color passthrough (no lighting)\n"); sb.append(" // Color passthrough (no lighting)\n");
if (key.hasVertexColor()) { final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
sb.append(" v_Color = a_Color;\n"); if (key.instancedDraw()) {
sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
} else { } else {
sb.append(" v_Color = u_CurrentColor;\n"); sb.append(" v_Color = ").append(src).append(";\n");
} }
sb.append('\n'); sb.append('\n');
} }
@@ -369,7 +280,9 @@ public final class VertexShaderGenerator {
} }
if (key.lightmapEnabled()) { 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"); sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
} else { } else {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n"); 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 boolean hudCacheOverride;
public static volatile boolean expandVertexFormats;
private GLSMConfig() {} private GLSMConfig() {}
} }
@@ -9,28 +9,36 @@ public final class GLSMHooks {
public static DeferredAlphaHandler alphaHandler; public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler; public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler; public static GlintColorHandler glintColorHandler;
public static ShaderWorkSubmitter shaderWorkSubmitter;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
// EventBus instances // EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class); 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<TextureDeleteEvent> TEXTURE_DELETE = EventBus.create(TextureDeleteEvent.class);
public static final EventBus<TextureUnitStateEvent> TEXTURE_UNIT_STATE = EventBus.create(TextureUnitStateEvent.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<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<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.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<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.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<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
public static final EventBus<LoadingCheckpointEvent> LOADING_CHECKPOINT = EventBus.create(LoadingCheckpointEvent.class);
// Reusable event instances // Reusable event instances
public static final TextureBindEvent textureBindEvent = new TextureBindEvent(); public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
public static final TextureDeleteEvent textureDeleteEvent = new TextureDeleteEvent(); public static final TextureDeleteEvent textureDeleteEvent = new TextureDeleteEvent();
public static final TextureUnitStateEvent textureUnitStateEvent = new TextureUnitStateEvent(); public static final TextureUnitStateEvent textureUnitStateEvent = new TextureUnitStateEvent();
public static final ProgramChangeEvent programChangeEvent = new ProgramChangeEvent(); public static final ProgramChangeEvent programChangeEvent = new ProgramChangeEvent();
public static final ProgramDeleteEvent programDeleteEvent = new ProgramDeleteEvent();
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent(); public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent(); public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent(); public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent(); public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent(); public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
public static final LoadingCheckpointEvent loadingCheckpointEvent = new LoadingCheckpointEvent();
private GLSMHooks() {} private GLSMHooks() {}
} }
@@ -2,19 +2,20 @@ package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator; import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader; import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import lombok.Getter;
import java.util.function.Consumer; import java.util.function.Consumer;
public final class GLSMInitConfig { public final class GLSMInitConfig {
private final boolean lwjglDebug; @Getter private final boolean lwjglDebug;
private final StreamingUploader.UploadStrategy streamingUploadStrategy; @Getter private final StreamingUploader.UploadStrategy streamingUploadStrategy;
private final Consumer<DirectTessellator> directDrawer; @Getter private final Consumer<DirectTessellator> directDrawer;
private final Runnable streamingDrawerDestroy; @Getter private final Runnable streamingDrawerDestroy;
private final int displayWidth; @Getter private final int displayWidth;
private final int displayHeight; @Getter private final int displayHeight;
private final Runnable postInitCallback; @Getter private final Runnable postInitCallback;
private final boolean enableDSA; @Getter private final boolean DSAEnabled;
private final boolean noErrorChecks; @Getter private final boolean noErrorChecks;
private GLSMInitConfig(Builder builder) { private GLSMInitConfig(Builder builder) {
this.lwjglDebug = builder.lwjglDebug; this.lwjglDebug = builder.lwjglDebug;
@@ -25,23 +26,13 @@ public final class GLSMInitConfig {
this.displayWidth = builder.displayWidth; this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight; this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback; this.postInitCallback = builder.postInitCallback;
this.enableDSA = builder.enableDSA; this.DSAEnabled = builder.enableDSA;
} }
public static Builder builder() { public static Builder builder() {
return new 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 { public static final class Builder {
private boolean lwjglDebug = false; private boolean lwjglDebug = false;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA; 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.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager; import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -10,16 +11,24 @@ import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class AttribSnapshot { 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 record AttribDesc(int location, int size, int type, boolean normalized, int stride, long offset, int sourceVboId, ByteBuffer readBuffer, long readBufferBaseOffset) {
public int effectiveStride() { public int effectiveStride() {
return stride != 0 ? stride : size * VAOManager.Attrib.glTypeSizeBytes(type); return stride != 0 ? stride : size * GLTypes.sizeBytes(type);
} }
public int typeSizeBytes() { 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 AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>(); final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO(); final int prevVBO = GLStateManager.getBoundVBO();
boolean success = false;
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations. // Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
int rangeSoleVboId = 0; int rangeSoleVboId = 0;
@@ -71,7 +81,7 @@ public final class AttribSnapshot {
final VAOManager.Attrib a = VAOManager.get(i); final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.vboId == 0) continue; if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride(); 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 start = a.offset + (long) firstVertex * stride;
final long end = a.offset + lastVtx * stride + typeBytes; final long end = a.offset + lastVtx * stride + typeBytes;
@@ -125,8 +135,8 @@ public final class AttribSnapshot {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId); GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE); final int bufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) { if (bufferSize <= 0) {
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId); warnOnce("vbo-orphaned", "[AttribSnapshot] GL_BUFFER_SIZE={} for VBO {} -- skipping capture", bufferSize, a.vboId);
continue; return null;
} }
final long rStart; final long rStart;
@@ -139,9 +149,16 @@ public final class AttribSnapshot {
rStart = rangeSoleStart; rStart = rangeSoleStart;
rEnd = rangeSoleEnd; 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 long ro = Math.max(0, rStart);
final int rs = (int) (Math.min(bufferSize, rEnd) - ro); final int rs = (int) (rEnd - ro);
if (rs <= 0) continue; 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); buf = MemoryUtilities.memAlloc(rs);
GLStateManager.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, ro, buf); 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); 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 { } finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO); 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; package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject; 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. * 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 drawMode;
private final int start; private final int start;
private final int count; 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.vao = vao;
this.drawMode = drawMode; this.drawMode = drawMode;
this.start = start; this.start = start;
this.count = count; this.count = count;
this.vertexFlags = vertexFlags;
} }
public int getStart() { public int getStart() {
@@ -62,11 +65,12 @@ public final class DisplayListVBO {
} }
public void render() { public void render() {
if (vao == null) return; if (vao == null) return;
vao.bind(); vao.bind();
if (count <= 0) { vao.unbind(); return; } if (count <= 0) { vao.unbind(); return; }
vao.draw(drawMode, start, count); VAOManager.setCurrentVertexFlags(vertexFlags);
vao.unbind(); vao.draw(drawMode, start, count);
vao.unbind();
} }
@@ -72,17 +72,18 @@ public final class DisplayListVBOBuilder {
} }
vertexCount = format.getVertexCount(size); vertexCount = format.getVertexCount(size);
} }
final int flags = format.getVertexFlags();
if (data.drawMode == GL11.GL_QUADS) { if (data.drawMode == GL11.GL_QUADS) {
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount)); 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) { } 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 // 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) { } 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 // 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 { } 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) { public static DirectTessellator processDrawArraysFromAttribs(int mode, int first, int count) {
final AttribSnapshot snap = AttribSnapshot.snapshot(first, count); final AttribSnapshot snap = AttribSnapshot.snapshot(first, count);
if (snap == null) return null;
try { try {
prepareReaders(snap); prepareReaders(snap);
if (positionReaderIndex < 0) return null; 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); 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; package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager; import com.gtnewhorizons.angelica.glsm.GLTypes;
import java.util.Arrays; import java.util.Arrays;
@@ -24,7 +24,7 @@ public final class AttribLayoutKey {
int base = 0; int base = 0;
int maxAlign = 1; int maxAlign = 1;
for (int i = 0; i < n; i++) { 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; if (typeSize > maxAlign) maxAlign = typeSize;
base = (base + typeSize - 1) & -typeSize; base = (base + typeSize - 1) & -typeSize;
off[i] = base; off[i] = base;
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities; import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.QuadConverter; import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager; import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.AttribSnapshot; 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); GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
return null; return null;
} }
final int srcIndexSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType); final int srcIndexSize = GLTypes.sizeBytes(srcIndexType);
if (srcIndexSize <= 0) return null; if (srcIndexSize <= 0) return null;
final int prevEBO = GLStateManager.getBoundEBO(); 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); GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 - skipping", indicesCount);
return null; return null;
} }
final int idxSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType); final int idxSize = GLTypes.sizeBytes(srcIndexType);
if (idxSize <= 0) return null; if (idxSize <= 0) return null;
if ((long) indicesCount * idxSize > srcByteLength) { if ((long) indicesCount * idxSize > srcByteLength) {
GLStateManager.warnOnce("client-idx-range", "[IndexedDrawCapture] index count {} ({} bytes) exceeds client buffer {} bytes - skipping", indicesCount, (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; final int vertexCount = maxVtx - minVtx + 1;
snap = AttribSnapshot.snapshot(minVtx, vertexCount); snap = AttribSnapshot.snapshot(minVtx, vertexCount);
if (snap == null) return null;
int enabledCount = 0; int enabledCount = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) { 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.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState; import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -78,6 +79,6 @@ public record TexImage2DCmd(
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null"; 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)", 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), 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.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState; import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -78,6 +79,6 @@ public record TexSubImage2DCmd(
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null"; 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)", 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.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.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser; import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
import com.gtnewhorizons.angelica.config.SystemProperties;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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.Handle;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AbstractInsnNode;
@@ -21,6 +24,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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. * 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 { public class GLSMRedirector {
private static final boolean ASSERT_MAIN_THREAD = Boolean.getBoolean("angelica.assertMainThread"); private static final boolean LOG_SPAM = SystemProperties.REDIRECTOR_LOGSPAM;
private static final boolean LOG_SPAM = Boolean.getBoolean("angelica.redirectorLogspam");
private static final Logger LOGGER = LogManager.getLogger("GLSMRedirector"); 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 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 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 GL_PREFIX = "org/lwjgl/opengl/GL";
private static final String ARBVertexArrayObject = "org/lwjgl/opengl/ARBVertexArrayObject"; private static final String ARBVertexArrayObject = "org/lwjgl/opengl/ARBVertexArrayObject";
private static final String APPLEVertexArrayObject = "org/lwjgl/opengl/APPLEVertexArrayObject"; 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 ARBBufferStorage = "org/lwjgl/opengl/ARBBufferStorage";
private static final String ARBCopyBuffer = "org/lwjgl/opengl/ARBCopyBuffer"; private static final String ARBCopyBuffer = "org/lwjgl/opengl/ARBCopyBuffer";
private static final String ARBMapBufferRange = "org/lwjgl/opengl/ARBMapBufferRange"; 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 // Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO"; 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, Map<String, String>> methodRedirects = new HashMap<>(32);
private static final Map<String, String> glMethodRedirects = new HashMap<>(256); 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<Integer, String> glCapRedirects = new HashMap<>();
private static final Map<String, String> typeRedirects = new HashMap<>(); private static final Map<String, String> typeRedirects = new HashMap<>();
private static final ClassConstantPoolParser cstPoolParser; private static final ClassConstantPoolParser cstPoolParser;
@@ -78,15 +181,15 @@ public class GLSMRedirector {
}; };
static { static {
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_ALPHA_TEST, "AlphaTest"); glCapRedirects.put(GL11.GL_ALPHA_TEST, "AlphaTest");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_BLEND, "Blend"); glCapRedirects.put(GL11.GL_BLEND, "Blend");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_DEPTH_TEST, "DepthTest"); glCapRedirects.put(GL11.GL_DEPTH_TEST, "DepthTest");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_CULL_FACE, "Cull"); glCapRedirects.put(GL11.GL_CULL_FACE, "Cull");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_LIGHTING, "Lighting"); glCapRedirects.put(GL11.GL_LIGHTING, "Lighting");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_TEXTURE_2D, "Texture"); glCapRedirects.put(GL11.GL_TEXTURE_2D, "Texture");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_FOG, "Fog"); glCapRedirects.put(GL11.GL_FOG, "Fog");
glCapRedirects.put(org.lwjgl.opengl.GL12.GL_RESCALE_NORMAL, "RescaleNormal"); glCapRedirects.put(GL12.GL_RESCALE_NORMAL, "RescaleNormal");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_SCISSOR_TEST, "ScissorTest"); glCapRedirects.put(GL11.GL_SCISSOR_TEST, "ScissorTest");
final var gl11 = RedirectMap.newMap() final var gl11 = RedirectMap.newMap()
// glEnable/Disable - Special cased in GLSMRedirector, but included here for the external API // glEnable/Disable - Special cased in GLSMRedirector, but included here for the external API
@@ -117,6 +220,7 @@ public class GLSMRedirector {
.add("glGenLists") .add("glGenLists")
.add("glGenTextures") .add("glGenTextures")
.add("glIsList") .add("glIsList")
.add("glIsTexture")
.add("glDeleteTextures") .add("glDeleteTextures")
.add("glDepthFunc") .add("glDepthFunc")
.add("glDepthMask") .add("glDepthMask")
@@ -135,6 +239,11 @@ public class GLSMRedirector {
.add("glGetFloat") .add("glGetFloat")
.add("glGetFloatv", "glGetFloat") .add("glGetFloatv", "glGetFloat")
.add("glGetInteger") .add("glGetInteger")
.add("glGetIntegerv", "glGetInteger")
.add("glGetIntegeri")
.add("glGetIntegeri_v")
.add("glGetBooleani")
.add("glGetBooleani_v")
.add("glGetLight") .add("glGetLight")
.add("glGetMaterial") .add("glGetMaterial")
.add("glGetTexLevelParameteri") .add("glGetTexLevelParameteri")
@@ -155,6 +264,7 @@ public class GLSMRedirector {
.add("glListBase") .add("glListBase")
.add("glLoadIdentity") .add("glLoadIdentity")
.add("glLoadMatrix") .add("glLoadMatrix")
.add("glLoadMatrixf", "glLoadMatrix")
.add("glLogicOp") .add("glLogicOp")
.add("glMatrixMode") .add("glMatrixMode")
.add("glMultMatrix") .add("glMultMatrix")
@@ -236,6 +346,7 @@ public class GLSMRedirector {
.add("glPolygonStipple") .add("glPolygonStipple")
.add("glAccum") .add("glAccum")
.add("glReadBuffer") .add("glReadBuffer")
.add("glReadPixels")
.add("glSampleCoverage") .add("glSampleCoverage")
.add("glScissor") .add("glScissor")
.add("glStencilFunc") .add("glStencilFunc")
@@ -273,7 +384,8 @@ public class GLSMRedirector {
final var gl12 = RedirectMap.newMap() final var gl12 = RedirectMap.newMap()
.add("glTexImage3D") .add("glTexImage3D")
.add("glTexSubImage3D") .add("glTexSubImage3D")
.add("glCopyTexSubImage3D"); .add("glCopyTexSubImage3D")
.add("glDrawRangeElements");
final var gl13 = RedirectMap.newMap() final var gl13 = RedirectMap.newMap()
.add("glActiveTexture") .add("glActiveTexture")
.add("glSampleCoverage") .add("glSampleCoverage")
@@ -284,7 +396,10 @@ public class GLSMRedirector {
final var gl14 = RedirectMap.newMap() final var gl14 = RedirectMap.newMap()
.add("glBlendFuncSeparate", "tryBlendFuncSeparate") .add("glBlendFuncSeparate", "tryBlendFuncSeparate")
.add("glBlendColor") .add("glBlendColor")
.add("glBlendEquation"); .add("glBlendEquation")
.add("glMultiDrawArrays")
.add("glPointParameterf")
.add("glPointParameteri");
final var gl15 = RedirectMap.newMap() final var gl15 = RedirectMap.newMap()
.add("glGenBuffers") .add("glGenBuffers")
.add("glBindBuffer") .add("glBindBuffer")
@@ -300,7 +415,14 @@ public class GLSMRedirector {
.add("glGetBufferParameteri") .add("glGetBufferParameteri")
.add("glGetBufferParameter") .add("glGetBufferParameter")
.add("glGetBufferParameteriv") .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() final var gl20 = RedirectMap.newMap()
.add("glBlendEquationSeparate") .add("glBlendEquationSeparate")
.add("glDrawBuffers") .add("glDrawBuffers")
@@ -320,6 +442,8 @@ public class GLSMRedirector {
.add("glValidateProgram") .add("glValidateProgram")
.add("glGetUniformLocation") .add("glGetUniformLocation")
.add("glGetAttribLocation") .add("glGetAttribLocation")
.add("glIsShader")
.add("glIsProgram")
.add("glUniform1f") .add("glUniform1f")
.add("glUniform2f") .add("glUniform2f")
.add("glUniform3f") .add("glUniform3f")
@@ -332,9 +456,13 @@ public class GLSMRedirector {
.add("glUniform2") .add("glUniform2")
.add("glUniform3") .add("glUniform3")
.add("glUniform4") .add("glUniform4")
.add("glUniform1fv", "glUniform1")
.add("glUniform3fv", "glUniform3")
.add("glUniform4fv", "glUniform4")
.add("glUniformMatrix2") .add("glUniformMatrix2")
.add("glUniformMatrix3") .add("glUniformMatrix3")
.add("glUniformMatrix4") .add("glUniformMatrix4")
.add("glUniformMatrix3fv", "glUniformMatrix3")
.add("glUniformMatrix4fv", "glUniformMatrix4") .add("glUniformMatrix4fv", "glUniformMatrix4")
.add("glDeleteShader") .add("glDeleteShader")
.add("glGetShaderi") .add("glGetShaderi")
@@ -352,12 +480,14 @@ public class GLSMRedirector {
.add("glGetUniform") .add("glGetUniform")
.add("glVertexAttribPointer") .add("glVertexAttribPointer")
.add("glEnableVertexAttribArray") .add("glEnableVertexAttribArray")
.add("glDisableVertexAttribArray"); .add("glDisableVertexAttribArray")
.add("glGetActiveAttrib");
final var gl30 = RedirectMap.newMap() final var gl30 = RedirectMap.newMap()
.add("glGenVertexArrays") .add("glGenVertexArrays")
.add("glBindVertexArray") .add("glBindVertexArray")
.add("glDeleteVertexArrays") .add("glDeleteVertexArrays")
.add("glIsVertexArray") .add("glIsVertexArray")
.add("glBindFragDataLocation")
.add("glBindFramebuffer") .add("glBindFramebuffer")
.add("glDeleteFramebuffers") .add("glDeleteFramebuffers")
.add("glGenFramebuffers") .add("glGenFramebuffers")
@@ -368,20 +498,37 @@ public class GLSMRedirector {
.add("glGetFramebufferAttachmentParameteri") .add("glGetFramebufferAttachmentParameteri")
.add("glBlitFramebuffer") .add("glBlitFramebuffer")
.add("glMapBufferRange") .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() final var gl31 = RedirectMap.newMap()
.add("glDrawElementsInstanced") .add("glDrawElementsInstanced")
.add("glDrawArraysInstanced") .add("glDrawArraysInstanced")
.add("glCopyBufferSubData"); .add("glCopyBufferSubData")
.add("glPrimitiveRestartIndex")
.add("glGetUniformBlockIndex")
.add("glUniformBlockBinding");
final var gl32 = RedirectMap.newMap() final var gl32 = RedirectMap.newMap()
.add("glFramebufferTexture"); .add("glFramebufferTexture")
.add("glDrawRangeElementsBaseVertex")
.add("glDrawElementsBaseVertex");
final var gl33 = RedirectMap.newMap() final var gl33 = RedirectMap.newMap()
.add("glGenSamplers") .add("glGenSamplers")
.add("glDeleteSamplers") .add("glDeleteSamplers")
.add("glIsSampler")
.add("glBindSampler") .add("glBindSampler")
.add("glSamplerParameteri") .add("glSamplerParameteri")
.add("glSamplerParameterf") .add("glSamplerParameterf")
.add("glVertexAttribDivisor"); .add("glVertexAttribDivisor")
.add("glQueryCounter")
.add("glGetQueryObjectui64");
final var gl42 = RedirectMap.newMap() final var gl42 = RedirectMap.newMap()
.add("glBindImageTexture") .add("glBindImageTexture")
.add("glMemoryBarrier") .add("glMemoryBarrier")
@@ -389,10 +536,12 @@ public class GLSMRedirector {
final var gl43 = RedirectMap.newMap() final var gl43 = RedirectMap.newMap()
.add("glDispatchCompute") .add("glDispatchCompute")
.add("glClearBufferSubData") .add("glClearBufferSubData")
.add("glClearBufferData")
.add("glBindVertexBuffer") .add("glBindVertexBuffer")
.add("glVertexAttribFormat") .add("glVertexAttribFormat")
.add("glVertexAttribIFormat") .add("glVertexAttribIFormat")
.add("glVertexAttribBinding"); .add("glVertexAttribBinding")
.add("glMultiDrawElementsIndirect");
final var gl44 = RedirectMap.newMap() final var gl44 = RedirectMap.newMap()
.add("glBufferStorage") .add("glBufferStorage")
.add("glClearTexImage"); .add("glClearTexImage");
@@ -400,8 +549,12 @@ public class GLSMRedirector {
.add("glCreateBuffers") .add("glCreateBuffers")
.add("glNamedBufferData") .add("glNamedBufferData")
.add("glNamedBufferSubData"); .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(gl11);
glMethodRedirects.putAll(gl12); glMethodRedirects.putAll(gl12);
glMethodRedirects.putAll(gl13); glMethodRedirects.putAll(gl13);
@@ -416,6 +569,7 @@ public class GLSMRedirector {
glMethodRedirects.putAll(gl43); glMethodRedirects.putAll(gl43);
glMethodRedirects.putAll(gl44); glMethodRedirects.putAll(gl44);
glMethodRedirects.putAll(gl45); glMethodRedirects.putAll(gl45);
glMethodRedirects.putAll(gl46);
// MINECRAFT // MINECRAFT
methodRedirects.put(OpenGlHelper, RedirectMap.newMap() methodRedirects.put(OpenGlHelper, RedirectMap.newMap()
@@ -435,10 +589,73 @@ public class GLSMRedirector {
// ARB // ARB
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap() methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
.add("glActiveTextureARB") .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() methodRedirects.put(ARBShaderObjects, RedirectMap.newMap()
.add("glUseProgramObjectARB", "glUseProgram") .add("glUseProgramObjectARB", "glUseProgram")
.add("glShaderSourceARB", "glShaderSource") .add("glShaderSourceARB", "glShaderSource")
.add("nglShaderSourceARB", "nglShaderSource")
.add("glLinkProgramARB", "glLinkProgram") .add("glLinkProgramARB", "glLinkProgram")
.add("glCreateShaderObjectARB", "glCreateShader") .add("glCreateShaderObjectARB", "glCreateShader")
.add("glCompileShaderARB", "glCompileShader") .add("glCompileShaderARB", "glCompileShader")
@@ -476,7 +693,23 @@ public class GLSMRedirector {
methodRedirects.put(ARBVertexArrayObject, RedirectMap.newMap() methodRedirects.put(ARBVertexArrayObject, RedirectMap.newMap()
.add("glBindVertexArray") .add("glBindVertexArray")
.add("glDeleteVertexArrays") .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() methodRedirects.put(ARBInstancedArrays, RedirectMap.newMap()
.add("glVertexAttribDivisorARB") .add("glVertexAttribDivisorARB")
); );
@@ -517,6 +750,8 @@ public class GLSMRedirector {
// APPLE // APPLE
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap() methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
.add("glBindVertexArrayAPPLE", "glBindVertexArray") .add("glBindVertexArrayAPPLE", "glBindVertexArray")
.add("glGenVertexArraysAPPLE", "glGenVertexArrays")
.add("glDeleteVertexArraysAPPLE", "glDeleteVertexArrays")
); );
// GTNHLib VAO // GTNHLib VAO
@@ -530,12 +765,15 @@ public class GLSMRedirector {
.add("gluPerspective") .add("gluPerspective")
.add("gluLookAt") .add("gluLookAt")
.add("gluPickMatrix") .add("gluPickMatrix")
.add("gluUnProject")
); );
methodRedirects.put(GLU, RedirectMap.newMap() methodRedirects.put(GLU, RedirectMap.newMap()
.add("gluPerspective") .add("gluPerspective")
.add("gluLookAt") .add("gluLookAt")
.add("gluOrtho2D") .add("gluOrtho2D")
.add("gluPickMatrix") .add("gluPickMatrix")
.add("gluBuild2DMipmaps")
.add("gluErrorString")
); );
// Quadric type replacements // 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/Disk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaDisk");
typeRedirects.put("org/lwjgl/util/glu/PartialDisk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaPartialDisk"); 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); 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()); stringsToSearch.addAll(typeRedirects.keySet());
for (String key : methodRedirects.keySet()) { stringsToSearch.addAll(methodRedirects.keySet());
stringsToSearch.add(key);
}
cstPoolParser = new ClassConstantPoolParser(stringsToSearch.toArray(new String[0])); cstPoolParser = new ClassConstantPoolParser(stringsToSearch.toArray(new String[0]));
} }
/** Core exclusions that GLSM always requires. */
public String[] getCoreExclusions() { public String[] getCoreExclusions() {
return CORE_EXCLUSIONS.clone(); 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; } public static String getGLPrefix() { return GL_PREFIX; }
/** The internal name of the redirect target class. */
public static String getTargetClassName() { return GLStateManager; } 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() { public static Map<String, String> getGLPrefixMethodRedirects() {
return Collections.unmodifiableMap(glMethodRedirects); 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() { public static Map<String, Map<String, String>> getNamedClassMethodRedirects() {
return Collections.unmodifiableMap(methodRedirects); return Collections.unmodifiableMap(methodRedirects);
} }
@@ -579,8 +814,11 @@ public class GLSMRedirector {
return cstPoolParser.find(basicClass, true); return cstPoolParser.find(basicClass, true);
} }
/** @return Was the class changed? */
public boolean transformClassNode(String transformedName, ClassNode cn) { public boolean transformClassNode(String transformedName, ClassNode cn) {
return transformClassNode(transformedName, cn, false);
}
public boolean transformClassNode(String transformedName, ClassNode cn, boolean lwjgl3Aware) {
boolean changed = false; boolean changed = false;
final boolean isOpenGlHelper = transformedName.equals("net.minecraft.client.renderer.OpenGlHelper"); 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"))) { if (isOpenGlHelper && (mn.name.equals("glBlendFunc") || mn.name.equals("func_148821_a"))) {
continue; continue;
} }
boolean redirectInMethod = false;
for (AbstractInsnNode node : mn.instructions.toArray()) { for (AbstractInsnNode node : mn.instructions.toArray()) {
if (node instanceof TypeInsnNode tNode) { if (node instanceof TypeInsnNode tNode) {
// Redirect NEW and CHECKCAST for quadric classes
if (tNode.getOpcode() == Opcodes.NEW || tNode.getOpcode() == Opcodes.CHECKCAST) { if (tNode.getOpcode() == Opcodes.NEW || tNode.getOpcode() == Opcodes.CHECKCAST) {
final String redirect = typeRedirects.get(tNode.desc); final String redirect = typeRedirects.get(tNode.desc);
if (redirect != null) { if (redirect != null) {
@@ -633,8 +869,7 @@ public class GLSMRedirector {
mn.instructions.remove(prevNode); mn.instructions.remove(prevNode);
} }
changed = true; changed = true;
redirectInMethod = true; } else if (mNode.name.equals("makeCurrent") && isDrawableOwner(mNode.owner)) {
} else if (mNode.name.equals("makeCurrent") && mNode.owner.startsWith(Drawable)) {
mNode.setOpcode(Opcodes.INVOKESTATIC); mNode.setOpcode(Opcodes.INVOKESTATIC);
mNode.owner = GLStateManager; mNode.owner = GLStateManager;
mNode.desc = "(L" + Drawable + ";)V"; mNode.desc = "(L" + Drawable + ";)V";
@@ -643,10 +878,48 @@ public class GLSMRedirector {
if (LOG_SPAM) { if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.makeCurrent()", transformedName); 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 { } else {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner); String glsmName = glDescRedirects.get(mNode.owner + " " + mNode.name + mNode.desc);
final String glsmName = redirects != null ? redirects.get(mNode.name) : null; if (glsmName == null) {
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) { if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1); 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); 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.owner = GLStateManager;
mNode.name = glsmName; mNode.name = glsmName;
changed = true; 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>")) { if (mNode.getOpcode() == Opcodes.INVOKESPECIAL && mNode.name.equals("<init>")) {
final String redirect = typeRedirects.get(mNode.owner); final String redirect = typeRedirects.get(mNode.owner);
if (redirect != null) { if (redirect != null) {
@@ -672,22 +949,28 @@ public class GLSMRedirector {
// Redirect method handles in invokedynamic bootstrap arguments // Redirect method handles in invokedynamic bootstrap arguments
for (int i = 0; i < dynNode.bsmArgs.length; i++) { for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue; if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner()); String glsmName = glDescRedirects.get(handle.getOwner() + " " + handle.getName() + handle.getDesc());
final String glsmName = redirects != null ? redirects.get(handle.getName()) : null; if (glsmName == null) {
if (glsmName == null) continue; 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) { if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1); 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()); 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()); dynNode.bsmArgs[i] = new Handle(handle.getTag(), GLStateManager, glsmName, handle.getDesc());
changed = true; 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; 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. // 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.GL20;
import org.lwjgl.opengl.GL32; 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 { public enum UniformType {
INT, INT,
@@ -38,4 +38,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
public boolean isEmpty() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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() { public boolean isEmpty() {
return pointer == 0; 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.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager; import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL13;
/** /**
@@ -37,10 +38,16 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
} }
private void setEnabledWithUnitSwitch(boolean enabled) { private void setEnabledWithUnitSwitch(boolean enabled) {
final boolean bypass = GLStateManager.shouldBypassCache(); final boolean bypass = !GLStateManager.isCachingEnabled();
if (bypass || enabled != this.enabled) { if (bypass || enabled != this.enabled) {
if (!bypass) this.enabled = 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) { if (!ffpStateOnly) {
final int currentUnit = GLStateManager.getActiveTextureUnit(); final int currentUnit = GLStateManager.getActiveTextureUnit();
final boolean needsSwitch = currentUnit != unitIndex; final boolean needsSwitch = currentUnit != unitIndex;
@@ -38,4 +38,8 @@ public class ViewPortStateStack extends ViewportState implements IStateStack<Vie
public boolean isEmpty() { public boolean isEmpty() {
return pointer == 0; 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; import org.lwjgl.opengl.GL11;
@@ -22,6 +22,15 @@ public class BooleanState implements ISettableState<BooleanState> {
this.ffpStateOnly = ffpStateOnly; this.ffpStateOnly = ffpStateOnly;
} }
public void applyToBackend() {
if (ffpStateOnly) return;
if (enabled) {
RENDER_BACKEND.enable(glCap);
} else {
RENDER_BACKEND.disable(glCap);
}
}
public void setUnknownState() { public void setUnknownState() {
stateUnknown = true; stateUnknown = true;
} }
@@ -35,12 +44,10 @@ public class BooleanState implements ISettableState<BooleanState> {
} }
public void setEnabled(boolean enabled) { 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())) { if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
stateUnknown = false; stateUnknown = false;
if (!bypass) { this.enabled = enabled;
this.enabled = enabled;
}
if (!ffpStateOnly) { if (!ffpStateOnly) {
if (enabled) { if (enabled) {
RENDER_BACKEND.enable(this.glCap); 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) { public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer); vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) { if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f); this.ambient.set(vector4f);
return true; return true;
} }
@@ -39,7 +39,7 @@ public class LightModelState implements ISettableState<LightModelState> {
public boolean setAmbient(IntBuffer newBuffer) { public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer); vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w)); 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); this.ambient.set(vector4f);
return true; return true;
} }
@@ -47,7 +47,7 @@ public class LightModelState implements ISettableState<LightModelState> {
} }
public boolean setColorControl(int val) { public boolean setColorControl(int val) {
if (GLStateManager.shouldBypassCache() || this.colorControl != val) { if (!GLStateManager.isCachingEnabled() || this.colorControl != val) {
this.colorControl = val; this.colorControl = val;
return true; return true;
} }
@@ -59,7 +59,7 @@ public class LightModelState implements ISettableState<LightModelState> {
} }
public boolean setLocalViewer(float val) { 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; this.localViewer = val;
return true; return true;
} }
@@ -79,7 +79,7 @@ public class LightModelState implements ISettableState<LightModelState> {
} }
public boolean setTwoSide(float val) { 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; this.twoSide = val;
return true; return true;
} }
@@ -74,18 +74,18 @@ public class LightState implements ISettableState<LightState> {
this.rawPosition = new Vector4f(position); this.rawPosition = new Vector4f(position);
this.position = position; this.position = position;
this.position.mul(GLStateManager.getModelViewMatrix()); this.position.mul(GLStateManager.getModelViewMatrix());
this.posMvGeneration = GLStateManager.mvGeneration; this.posMvGeneration = GLStateManager.getMvGeneration();
this.rawSpotDirection = new Vector3f(spotDirection); this.rawSpotDirection = new Vector3f(spotDirection);
this.spotDirection = spotDirection; this.spotDirection = spotDirection;
GLStateManager.getModelViewMatrix().get3x3(matrix3f); GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.mul(matrix3f); this.spotDirection.mul(matrix3f);
this.spotDirLinearGeneration = GLStateManager.mvLinearGeneration; this.spotDirLinearGeneration = GLStateManager.getMvLinearGeneration();
} }
public boolean setAmbient(FloatBuffer newBuffer) { public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer); vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) { if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f); this.ambient.set(vector4f);
return true; return true;
} }
@@ -95,7 +95,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setAmbient(IntBuffer newBuffer) { public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer); vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w)); 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); this.ambient.set(vector4f);
return true; return true;
} }
@@ -104,7 +104,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setDiffuse(FloatBuffer newBuffer) { public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer); vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) { if (!GLStateManager.isCachingEnabled() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f); this.diffuse.set(vector4f);
return true; return true;
} }
@@ -114,7 +114,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setDiffuse(IntBuffer newBuffer) { public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer); vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w)); 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); this.diffuse.set(vector4f);
return true; return true;
} }
@@ -123,7 +123,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setSpecular(FloatBuffer newBuffer) { public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer); vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) { if (!GLStateManager.isCachingEnabled() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f); this.specular.set(vector4f);
return true; return true;
} }
@@ -133,7 +133,7 @@ public class LightState implements ISettableState<LightState> {
public boolean setSpecular(IntBuffer newBuffer) { public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer); vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w)); 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); this.specular.set(vector4f);
return true; return true;
} }
@@ -152,8 +152,8 @@ public class LightState implements ISettableState<LightState> {
} }
private boolean applyPosition(Vector4f raw) { private boolean applyPosition(Vector4f raw) {
final int mvGen = GLStateManager.mvGeneration; final int mvGen = GLStateManager.getMvGeneration();
if (GLStateManager.shouldBypassCache() || this.posMvGeneration != mvGen || !this.rawPosition.equals(raw)) { if (!GLStateManager.isCachingEnabled() || this.posMvGeneration != mvGen || !this.rawPosition.equals(raw)) {
this.rawPosition.set(raw); this.rawPosition.set(raw);
this.posMvGeneration = mvGen; this.posMvGeneration = mvGen;
this.position.set(raw); this.position.set(raw);
@@ -175,8 +175,8 @@ public class LightState implements ISettableState<LightState> {
} }
private boolean applySpotDirection(Vector3f raw) { private boolean applySpotDirection(Vector3f raw) {
final int mvLinearGen = GLStateManager.mvLinearGeneration; final int mvLinearGen = GLStateManager.getMvLinearGeneration();
if (GLStateManager.shouldBypassCache() || this.spotDirLinearGeneration != mvLinearGen || !this.rawSpotDirection.equals(raw)) { if (!GLStateManager.isCachingEnabled() || this.spotDirLinearGeneration != mvLinearGen || !this.rawSpotDirection.equals(raw)) {
this.rawSpotDirection.set(raw); this.rawSpotDirection.set(raw);
this.spotDirLinearGeneration = mvLinearGen; this.spotDirLinearGeneration = mvLinearGen;
GLStateManager.getModelViewMatrix().get3x3(matrix3f); GLStateManager.getModelViewMatrix().get3x3(matrix3f);
@@ -200,7 +200,7 @@ public class LightState implements ISettableState<LightState> {
} }
public boolean setSpotExponent(float f) { 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; this.spotExponent = f;
return true; return true;
} }
@@ -220,7 +220,7 @@ public class LightState implements ISettableState<LightState> {
} }
public boolean setSpotCutoff(float f) { 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.spotCutoff = f;
this.spotCosCutoff = (float) Math.cos(Math.toRadians(f)); this.spotCosCutoff = (float) Math.cos(Math.toRadians(f));
return true; return true;
@@ -241,7 +241,7 @@ public class LightState implements ISettableState<LightState> {
} }
public boolean setConstantAttenuation(float f) { 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; this.constantAttenuation = f;
return true; return true;
} }
@@ -261,7 +261,7 @@ public class LightState implements ISettableState<LightState> {
} }
public boolean setLinearAttenuation(float f) { 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; this.linearAttenuation = f;
return true; return true;
} }
@@ -281,7 +281,7 @@ public class LightState implements ISettableState<LightState> {
} }
public boolean setQuadraticAttenuation(float f) { 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; this.quadraticAttenuation = f;
return true; return true;
} }
@@ -39,7 +39,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setAmbient(FloatBuffer newBuffer) { public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer); vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) { if (!GLStateManager.isCachingEnabled() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f); this.ambient.set(vector4f);
return true; return true;
} }
@@ -49,7 +49,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setAmbient(IntBuffer newBuffer) { public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer); vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w)); 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); this.ambient.set(vector4f);
return true; return true;
} }
@@ -58,7 +58,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setDiffuse(FloatBuffer newBuffer) { public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer); vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) { if (!GLStateManager.isCachingEnabled() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f); this.diffuse.set(vector4f);
return true; return true;
} }
@@ -68,7 +68,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setDiffuse(IntBuffer newBuffer) { public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer); vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w)); 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); this.diffuse.set(vector4f);
return true; return true;
} }
@@ -77,7 +77,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setSpecular(FloatBuffer newBuffer) { public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer); vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) { if (!GLStateManager.isCachingEnabled() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f); this.specular.set(vector4f);
return true; return true;
} }
@@ -87,7 +87,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setSpecular(IntBuffer newBuffer) { public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer); vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w)); 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); this.specular.set(vector4f);
return true; return true;
} }
@@ -96,7 +96,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setEmission(FloatBuffer newBuffer) { public boolean setEmission(FloatBuffer newBuffer) {
vector4f.set(newBuffer); vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) { if (!GLStateManager.isCachingEnabled() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f); this.emission.set(vector4f);
return true; return true;
} }
@@ -106,7 +106,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setEmission(IntBuffer newBuffer) { public boolean setEmission(IntBuffer newBuffer) {
vector4i.set(newBuffer); vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w)); 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); this.emission.set(vector4f);
return true; return true;
} }
@@ -114,7 +114,7 @@ public class MaterialState implements ISettableState<MaterialState> {
} }
public boolean setShininess(float val) { 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; this.shininess = val;
return true; return true;
} }
@@ -141,7 +141,7 @@ public class MaterialState implements ISettableState<MaterialState> {
vector3f.set(newBuffer); vector3f.set(newBuffer);
} }
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) { if (!GLStateManager.isCachingEnabled() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f); this.colorIndexes.set(vector3f);
return true; return true;
} }
@@ -151,7 +151,7 @@ public class MaterialState implements ISettableState<MaterialState> {
public boolean setColorIndexes(IntBuffer newBuffer) { public boolean setColorIndexes(IntBuffer newBuffer) {
vector3i.set(newBuffer.get(0), newBuffer.get(2), newBuffer.get(1)); vector3i.set(newBuffer.get(0), newBuffer.get(2), newBuffer.get(1));
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z); 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); this.colorIndexes.set(vector3f);
return true; return true;
} }
@@ -14,12 +14,7 @@ public class MatrixMode implements ISettableState<MatrixMode> {
return; return;
} }
final boolean bypass = GLStateManager.shouldBypassCache(); this.mode = mode;
if (bypass || this.mode != mode) {
if (!bypass) {
this.mode = mode;
}
}
} }
public int getMatrix() { public int getMatrix() {
@@ -14,6 +14,7 @@ public class PolygonState implements ISettableState<PolygonState> {
private int backMode = GL11.GL_FILL; private int backMode = GL11.GL_FILL;
private float offsetFactor = 0.0f; private float offsetFactor = 0.0f;
private float offsetUnits = 0.0f; private float offsetUnits = 0.0f;
private float offsetClamp = 0.0f;
private int cullFaceMode = GL11.GL_BACK; private int cullFaceMode = GL11.GL_BACK;
private int frontFace = GL11.GL_CCW; private int frontFace = GL11.GL_CCW;
@@ -23,6 +24,7 @@ public class PolygonState implements ISettableState<PolygonState> {
this.backMode = state.backMode; this.backMode = state.backMode;
this.offsetFactor = state.offsetFactor; this.offsetFactor = state.offsetFactor;
this.offsetUnits = state.offsetUnits; this.offsetUnits = state.offsetUnits;
this.offsetClamp = state.offsetClamp;
this.cullFaceMode = state.cullFaceMode; this.cullFaceMode = state.cullFaceMode;
this.frontFace = state.frontFace; this.frontFace = state.frontFace;
return this; return this;
@@ -36,6 +38,7 @@ public class PolygonState implements ISettableState<PolygonState> {
&& backMode == polygonState.backMode && backMode == polygonState.backMode
&& Float.compare(offsetFactor, polygonState.offsetFactor) == 0 && Float.compare(offsetFactor, polygonState.offsetFactor) == 0
&& Float.compare(offsetUnits, polygonState.offsetUnits) == 0 && Float.compare(offsetUnits, polygonState.offsetUnits) == 0
&& Float.compare(offsetClamp, polygonState.offsetClamp) == 0
&& cullFaceMode == polygonState.cullFaceMode && cullFaceMode == polygonState.cullFaceMode
&& frontFace == polygonState.frontFace; && frontFace == polygonState.frontFace;
} }
@@ -1,15 +1,38 @@
package com.gtnewhorizons.angelica.glsm.states; 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.Getter;
import lombok.Setter;
@Getter
public class TextureBinding implements ISettableState<TextureBinding> { 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 @Override
public TextureBinding set(TextureBinding state) { public TextureBinding set(TextureBinding state) {
this.binding = state.binding; this.binding = state.binding;
this.target = state.target;
this.info = null;
return this; return this;
} }
@@ -29,6 +29,13 @@ public class ViewportState implements ISettableState<ViewportState> {
params.put(3, height); params.put(3, height);
} }
public void get(int[] params) {
params[0] = x;
params[1] = y;
params[2] = width;
params[3] = height;
}
@Override @Override
public ViewportState set(ViewportState state) { public ViewportState set(ViewportState state) {
this.x = state.x; 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; package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
import org.embeddedt.embeddium.impl.gl.sync.GlFence;
import org.lwjgl.opengl.GL15; 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 java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0; 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.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class PersistentStreamingBuffer {
/** public static final int DEFAULT_CAPACITY = 16 * 1024 * 1024;
* 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; // 16 MB private final GlStreamingRing ring;
private int bufferId; private PersistentStreamingBuffer(GlStreamingRing ring) {
private final int capacity; this.ring = ring;
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);
} }
public PersistentStreamingBuffer(int capacity) { public static PersistentStreamingBuffer createOrNull(int capacity) {
this.capacity = capacity; return createOrNull(capacity, SystemProperties.FORCE_ORPHAN_STREAMING);
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);
} }
@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) { public int upload(ByteBuffer data, int vertexStride) {
final int dataSize = data.remaining(); return upload(data, vertexStride, 1);
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;
} }
/** Sync fences until {@code needed} bytes are available, or no fences remain. */ public int upload(ByteBuffer data, int vertexStride, int vertexAlignment) {
private boolean ensureRemaining(int needed) { final int size = data.remaining();
while (remaining < needed) { final long offset = ring.reserve(size, vertexStride * vertexAlignment);
if (!syncOldest()) return false; if (offset < 0) return -1;
} memCopy(memAddress0(data) + data.position(), ring.address() + offset, size);
return true; RENDER_BACKEND.onPersistentBufferWrite(ring.bufferId(), offset, size);
return (int) (offset / vertexStride);
} }
private int writeAt(int offset, ByteBuffer data, int dataSize, int waste, int vertexStride) { public int getBufferId() { return ring.bufferId(); }
memCopy(memAddress0(data) + data.position(), mappedAddress + offset, dataSize); 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; ByteBuffer getMappedBuffer() { return ring.mapped(); }
writePos = offset + dataSize;
remaining -= totalCost;
pendingBytes += totalCost;
return offset / vertexStride;
}
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() { public void postDraw() {
if (pendingBytes > 0) { ring.endFrame();
final long fenceId = RENDER_BACKEND.fenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
fenceQueue.enqueue(new FencedRegion(new GlFence(fenceId), pendingBytes));
pendingBytes = 0;
}
} }
@Override
public void destroy() { public void destroy() {
while (!fenceQueue.isEmpty()) { ring.destroy();
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;
}
} }
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(); return data.remaining();
} }
final int dataSize = data.remaining(); final int dataSize = data.remaining();
final ByteBuffer mapped = RENDER_BACKEND.mapBufferRange(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER); final long dst = RENDER_BACKEND.mapBufferRangeAddress(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
memCopy(memAddress0(data), memAddress0(mapped), dataSize); 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); RENDER_BACKEND.unmapBuffer(GL15.GL_ARRAY_BUFFER);
return capacity; 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.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags; import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat; 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.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter; import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem; 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 net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -16,6 +20,7 @@ import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0; 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.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree; import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
@@ -28,6 +33,8 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
*/ */
public class TessellatorStreamingDrawer { 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 Logger LOGGER = LogManager.getLogger("TessellatorStreamingDrawer");
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE; // 16 private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE; // 16
@@ -43,6 +50,14 @@ public class TessellatorStreamingDrawer {
private static boolean initialized = false; 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 { static {
// Initial repack buffer: 64KB // Initial repack buffer: 64KB
repackCapacity = 0x10000; repackCapacity = 0x10000;
@@ -54,15 +69,7 @@ public class TessellatorStreamingDrawer {
if (initialized) return; if (initialized) return;
initialized = true; initialized = true;
if (RenderSystem.supportsBufferStorage() && !Boolean.getBoolean("angelica.forceOrphanStreaming")) { persistentBuffer = PersistentStreamingBuffer.createOrNull(PersistentStreamingBuffer.DEFAULT_CAPACITY);
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;
}
}
} }
/** /**
@@ -84,7 +91,57 @@ public class TessellatorStreamingDrawer {
// Determine the optimal vertex format from the tessellator's flags // Determine the optimal vertex format from the tessellator's flags
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness); 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 vertexSize = format.getVertexSize();
final int requiredBytes = vertexCount * vertexSize; final int requiredBytes = vertexCount * vertexSize;
@@ -96,7 +153,7 @@ public class TessellatorStreamingDrawer {
repackBuffer.position(0); repackBuffer.position(0);
repackBuffer.limit((int)(writePtr - repackAddress)); repackBuffer.limit((int)(writePtr - repackAddress));
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount); uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
} finally { } finally {
if (locked) GLStateManager.releaseDrawLock(); if (locked) GLStateManager.releaseDrawLock();
} }
@@ -166,9 +223,9 @@ public class TessellatorStreamingDrawer {
} }
if (persistentBuffer != null) { 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()), formatBytes(persistentBuffer.getCapacity()), formatBytes(persistentBuffer.getRemaining()),
orphanCount, formatBytes(orphanBytes)); persistentBuffer.getForcedReclaims(), orphanCount, formatBytes(orphanBytes));
} else { } else {
cachedDebugInfo = String.format("Stream: Orphan (%d bufs, %s)", orphanCount, formatBytes(orphanBytes)); 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. * 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) { 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(); final boolean locked = GLStateManager.acquireDrawLock();
try { try {
ensureVAO(flags, format); ensureVAO(flags, format);
if (Tracy.ENABLED) {
streamedBytes += packed.remaining();
streamDraws++;
}
int firstVertex = -1; int firstVertex = -1;
if (persistentBuffer != null) { 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]); GLStateManager.glBindVertexArray(persistentVAOs[flags]);
} else { } else {
if (Tracy.ENABLED && persistentBuffer != null) orphanFallbacks++;
GLStateManager.glBindVertexArray(orphanVAOs[flags]); GLStateManager.glBindVertexArray(orphanVAOs[flags]);
orphanBuffers[flags].upload(packed); orphanBuffers[flags].upload(packed);
firstVertex = 0; firstVertex = 0;
} }
if (Tracy.ENABLED) {
if (fromRing && firstVertex == lastStreamEndVertex && drawMode == lastStreamDrawMode) streamContiguous++;
lastStreamEndVertex = fromRing ? firstVertex + vertexCount : -1;
lastStreamDrawMode = drawMode;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount); drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
} finally { } finally {
if (locked) GLStateManager.releaseDrawLock(); 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) { private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
if (drawMode == GL11.GL_QUADS) { if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount); QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
@@ -264,7 +339,6 @@ public class TessellatorStreamingDrawer {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId()); GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
format.setupBufferState(0L); format.setupBufferState(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
} }
if (persistentBuffer != null && persistentVAOs[flags] == 0) { if (persistentBuffer != null && persistentVAOs[flags] == 0) {
@@ -273,7 +347,6 @@ public class TessellatorStreamingDrawer {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId()); GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
format.setupBufferState(0L); format.setupBufferState(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); 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.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
@@ -77,4 +77,12 @@ public enum DepthBufferFormat {
public boolean isCombinedStencil() { public boolean isCombinedStencil() {
return combinedStencil; 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 { public enum GlVersion {
GL_11, GL_11,
Loaded 100 of 647 files, more files were not shown because too many files have changed in this diff. Show more