mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 17:55:08 +00:00
Compare commits
168
Commits
1.0.0-beta67
...
2.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb5e2ed7d4 | ||
|
|
e914ef7563 | ||
|
|
748dbd55a1 | ||
|
|
140c0c2285 | ||
|
|
af26442b98 | ||
|
|
ef07ee9f77 | ||
|
|
1b8087e8a3 | ||
|
|
9107ea9ae5 | ||
|
|
938a6bda90 | ||
|
|
e9f6f72741 | ||
|
|
379330f997 | ||
|
|
2fecb3a05b | ||
|
|
c085af35bd | ||
|
|
a36aec36a2 | ||
|
|
bdd6259c3f | ||
|
|
d7d2228a6c | ||
|
|
cf9993fce4 | ||
|
|
3ae57ca594 | ||
|
|
9816cb2148 | ||
|
|
b1e496f56d | ||
|
|
c8e50227fb | ||
|
|
066381a7c0 | ||
|
|
9a0ea1ad65 | ||
|
|
8e26a0179b | ||
|
|
1d96af8e3a | ||
|
|
f7eb86cdb6 | ||
|
|
b38fafa831 | ||
|
|
7dc2625f8c | ||
|
|
3d97977050 | ||
|
|
5f41c60bc2 | ||
|
|
0325a96bc2 | ||
|
|
ad123d0bd5 | ||
|
|
4b89997985 | ||
|
|
f5c9fa3301 | ||
|
|
d6a6d3b525 | ||
|
|
f079bed977 | ||
|
|
c9ec40e94b | ||
|
|
8380f3853d | ||
|
|
534ae6b05c | ||
|
|
68d523d3df | ||
|
|
2f22a7b3ad | ||
|
|
7b79b90dd6 | ||
|
|
417f23f826 | ||
|
|
ca3c46c873 | ||
|
|
82fa9ade2b | ||
|
|
aaab8fd0bc | ||
|
|
bd56ccc4b8 | ||
|
|
e17f60308d | ||
|
|
1dd98978ad | ||
|
|
dae986eb0b | ||
|
|
5fdf84edd0 | ||
|
|
b19d023c11 | ||
|
|
41eafbd151 | ||
|
|
dd970b4c6a | ||
|
|
f79ad7f22a | ||
|
|
6947ffa3e2 | ||
|
|
969d2c3710 | ||
|
|
c5988d29c3 | ||
|
|
dc68c2c265 | ||
|
|
51533a81ec | ||
|
|
90eabb5d40 | ||
|
|
74843cb1f9 | ||
|
|
5e0344a529 | ||
|
|
3cdd934987 | ||
|
|
9d90ff137c | ||
|
|
bee9541797 | ||
|
|
b75a5d8f7d | ||
|
|
8d3ab41c53 | ||
|
|
48d032e654 | ||
|
|
1968914582 | ||
|
|
a70bd76ff3 | ||
|
|
db919b5f37 | ||
|
|
cde47ea423 | ||
|
|
5ef6cb11c0 | ||
|
|
b7015bf10e | ||
|
|
46602ab921 | ||
|
|
63e9fa0df1 | ||
|
|
dcea970a77 | ||
|
|
b29240d6da | ||
|
|
9d5a02ed21 | ||
|
|
b454483aae | ||
|
|
72b13543c6 | ||
|
|
36d98fcb22 | ||
|
|
44c5b9617a | ||
|
|
f3f1677db8 | ||
|
|
d396b611ad | ||
|
|
08f4c0f9db | ||
|
|
50b0d22dc2 | ||
|
|
7fed54264e | ||
|
|
be87401be6 | ||
|
|
94254006df | ||
|
|
f79774df65 | ||
|
|
2f2fc22df3 | ||
|
|
26300bbb1b | ||
|
|
88e4d2fd6c | ||
|
|
bb52476d27 | ||
|
|
6d60477b93 | ||
|
|
362724610c | ||
|
|
ea4a3cdf0b | ||
|
|
65959268d0 | ||
|
|
58d820cd7e | ||
|
|
685d68bd9c | ||
|
|
ad63d2d62c | ||
|
|
d2730ccca8 | ||
|
|
e9257b8739 | ||
|
|
3984225faa | ||
|
|
52db6b56d6 | ||
|
|
34bfe513f1 | ||
|
|
42d7ed58aa | ||
|
|
9a4f0f9b99 | ||
|
|
be90a01725 | ||
|
|
e5e8250d44 | ||
|
|
062a03ffc2 | ||
|
|
9f5afaaec3 | ||
|
|
b05f17a477 | ||
|
|
57caf08212 | ||
|
|
085ec79c21 | ||
|
|
38641268ae | ||
|
|
fe8e723638 | ||
|
|
16debfd518 | ||
|
|
8cfc3e1f4a | ||
|
|
2c808fae43 | ||
|
|
1649c3c0c6 | ||
|
|
b84286ba3b | ||
|
|
ee6c79802a | ||
|
|
d3366ad31a | ||
|
|
92b6dcc010 | ||
|
|
89132f3ef9 | ||
|
|
f0d45b0d62 | ||
|
|
173d986a5b | ||
|
|
473a690054 | ||
|
|
2515b3520f | ||
|
|
a32f9ca244 | ||
|
|
941fc88484 | ||
|
|
4106dd3865 | ||
|
|
61f6aa19a8 | ||
|
|
77875f7f91 | ||
|
|
241c32def1 | ||
|
|
a8107f97e8 | ||
|
|
8bec433699 | ||
|
|
2d39fb2397 | ||
|
|
cfd8eb1411 | ||
|
|
59c270a5b9 | ||
|
|
87ac03281c | ||
|
|
36b82fa2ce | ||
|
|
2fc2e2e38d | ||
|
|
33de1de9c4 | ||
|
|
55811b9d7b | ||
|
|
a10d92f52e | ||
|
|
7d2fe12ce9 | ||
|
|
006ded801e | ||
|
|
03afd6939e | ||
|
|
adc5421e00 | ||
|
|
dfe60e4b3e | ||
|
|
72c6cdfa60 | ||
|
|
c5daea86f8 | ||
|
|
f8958ee990 | ||
|
|
073260060b | ||
|
|
771fee124d | ||
|
|
680285bf54 | ||
|
|
df8784536e | ||
|
|
c8991d810c | ||
|
|
e3b02642ac | ||
|
|
2759dd6689 | ||
|
|
996dbaef2c | ||
|
|
72ca675c93 | ||
|
|
e6e230e270 | ||
|
|
43d7282ba5 |
No files matched your search
@@ -39,6 +39,7 @@ body:
|
||||
- Java 19
|
||||
- Java 20
|
||||
- Java 21
|
||||
- Java 25
|
||||
- Other (Please Specify)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -36,3 +36,4 @@ addon.local.gradle
|
||||
addon.local.gradle.kts
|
||||
addon.late.local.gradle
|
||||
addon.late.local.gradle.kts
|
||||
local/
|
||||
@@ -9,13 +9,21 @@
|
||||
|
||||
## **Beta Quality - Here there be Dragons**
|
||||
* Shaders - Beta
|
||||
* Complementary & BSL mostly function, but have some bugs
|
||||
* Sildurs, Seus, and any other shader probably doesn't work
|
||||
* Complementary + Euphoria Patches is the primary compatibility target
|
||||
* Sildurs, Seus, and any other shaders might work
|
||||
|
||||
## Core Profile
|
||||
Targets GL 3.3+ core profile
|
||||
* FFP emulation via generated GLSL shaders
|
||||
* Display list emulation
|
||||
* Software matrix stacks and state management through GLSM
|
||||
* GL_QUADS → GL_TRIANGLES conversion
|
||||
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
|
||||
|
||||
|
||||
# Required Dependencies
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.19
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.7.0
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.9.0
|
||||
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
|
||||
|
||||
# Known (temporary) Incompatibilities
|
||||
@@ -38,7 +46,7 @@
|
||||
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
|
||||
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
|
||||
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
|
||||
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 0.4.5 or above
|
||||
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
|
||||
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
|
||||
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
|
||||
* SmoothFont - Equivalent functionality added
|
||||
@@ -56,9 +64,9 @@
|
||||
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
|
||||
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
|
||||
* Backported code under the LGPL
|
||||
* Sodium
|
||||
* Sodium / Celeritas
|
||||
* JellySquid & the CaffeineMC team, for making Sodium in the first place
|
||||
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
|
||||
* Embeddedt for Celeritas used under the LGPL 3.0
|
||||
* Reese's Sodium Options
|
||||
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
|
||||
* Neodymium
|
||||
@@ -83,3 +91,10 @@
|
||||
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
|
||||
* FalseTweaks
|
||||
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
|
||||
* Beddium
|
||||
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
|
||||
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
+35
-2
@@ -1,3 +1,5 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
@@ -5,6 +7,9 @@ plugins {
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
|
||||
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
|
||||
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
|
||||
}
|
||||
|
||||
@@ -13,17 +18,24 @@ tasks.test {
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
}
|
||||
// On macOS ARM64, use an ARM64-compatible JVM for tests
|
||||
val isMacOsArm64 = System.getProperty("os.name").lowercase().contains("mac") &&
|
||||
System.getProperty("os.arch") == "aarch64"
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (isMacOsArm64) {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
dependsOn(tasks.extractNatives2)
|
||||
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
inputs.property("version", project.version.toString())
|
||||
val projectVersion = project.version.toString()
|
||||
inputs.property("version", projectVersion)
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to project.version.toString())
|
||||
expand("version" to projectVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,3 +45,24 @@ tasks.register<Copy>("copyDependencies") {
|
||||
from(configurations.default)
|
||||
into("testDependencies")
|
||||
}
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
|
||||
// Downgrade embedOnly jars for the tests
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
dependsOn(downgradeEmbedOnlyForTest)
|
||||
classpath = classpath
|
||||
.minus(embedOnly)
|
||||
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
from(embedOnly.map(::zipTree))
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
}
|
||||
|
||||
+44
-17
@@ -46,28 +46,47 @@ configurations {
|
||||
// Can be enabled for testing if desired
|
||||
// runtimeClasspath.extendsFrom(transformedMod)
|
||||
// testRuntimeClasspath.extendsFrom(transformedMod)
|
||||
|
||||
// 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.11")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
|
||||
|
||||
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.34") { transitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.34")
|
||||
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.8.0:dev")
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.9.36:dev")
|
||||
|
||||
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
|
||||
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-4.g6b42bca") {
|
||||
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
|
||||
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.2-GTNH") { transitive = false }
|
||||
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.2-GTNH") { transitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.31-GTNH:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
|
||||
|
||||
// Notfine Deps
|
||||
@@ -90,28 +109,36 @@ dependencies {
|
||||
|
||||
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev") // Mixin Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
|
||||
|
||||
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5593_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(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
|
||||
// For testing alternative splash screens, can be sourced from https://github.com/MalTeeez/ModernSplash/releases
|
||||
//runtimeOnly(rfg.deobf(files("dependencies/modernsplash-1.7.10-1.2.2-dev.jar")))
|
||||
// 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.0-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.1-GTNH:dev")
|
||||
|
||||
// Tests
|
||||
testImplementation(platform('org.junit:junit-bom:5.11.4'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testImplementation("org.mockito:mockito-core:3.+")
|
||||
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
}
|
||||
+32
-13
@@ -39,9 +39,33 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co
|
||||
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
|
||||
developmentEnvironmentUserName = Developer
|
||||
|
||||
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8.
|
||||
# See https://github.com/bsideup/jabel for details on how this works.
|
||||
enableModernJavaSyntax = true
|
||||
# Enables modern Java syntax support. Valid values:
|
||||
# - false: No modern syntax, Java 8 only
|
||||
# - jabel: Jabel syntax-only support, compiles to J8 bytecode
|
||||
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
|
||||
# - modern: Native modern Java bytecode, no downgrading
|
||||
enableModernJavaSyntax = jvmDowngrader
|
||||
|
||||
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
|
||||
# and should *not* be used in most situations. -1 disables this.
|
||||
# forceToolchainVersion = -1
|
||||
|
||||
# Target JVM version for JVM Downgrader bytecode downgrading.
|
||||
# Only used when enableModernJavaSyntax = jvmDowngrader
|
||||
# downgradeTargetVersion = 8
|
||||
|
||||
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
|
||||
# Classes will be available in META-INF/versions/N/ for each version N in this list.
|
||||
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
|
||||
jvmDowngraderMultiReleaseVersions = 17, 21, 25
|
||||
|
||||
# Specifies how JVM Downgrader API stubs are provided. Options:
|
||||
# - shade: Shade minimized stubs into the jar
|
||||
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
|
||||
# - external: Another dependency provides stubs (no constraint, no warning)
|
||||
# - (empty): Warning reminding you to configure stubs
|
||||
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
|
||||
jvmDowngraderStubsProvider = gtnhlib
|
||||
|
||||
# Enables injecting missing generics into the decompiled source code for a better coding experience.
|
||||
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
|
||||
@@ -55,22 +79,13 @@ generateGradleTokenClass = com.gtnewhorizons.angelica.Tags
|
||||
# Name of the token containing the project's current version to generate/replace.
|
||||
gradleTokenVersion = VERSION
|
||||
|
||||
# [DEPRECATED] Mod ID replacement token.
|
||||
gradleTokenModId =
|
||||
|
||||
# [DEPRECATED] Mod name replacement token.
|
||||
gradleTokenModName =
|
||||
|
||||
# [DEPRECATED] Mod Group replacement token.
|
||||
gradleTokenGroupName =
|
||||
|
||||
# [DEPRECATED]
|
||||
# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java
|
||||
# public static final String VERSION = "GRADLETOKEN_VERSION";
|
||||
# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's
|
||||
# version in @Mod([...], version = VERSION, [...]).
|
||||
# Leave these properties empty to skip individual token replacements.
|
||||
replaceGradleTokenInFile =
|
||||
# replaceGradleTokenInFile =
|
||||
|
||||
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can
|
||||
# leave this property empty.
|
||||
@@ -209,3 +224,7 @@ disableSpotless = true
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# ideaCheckSpotlessOnBuild = true
|
||||
|
||||
# Non-GTNH properties
|
||||
org.gradle.configuration-cache = true
|
||||
org.gradle.jvmargs = -Xmx2g
|
||||
org.gradle.parallel = true
|
||||
@@ -0,0 +1,12 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e1118b4c7c2858d28154d086dbff010e/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/59a9771cad43219260d9aac9a8ec4d6a/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
|
||||
toolchainVersion=25
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -57,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -172,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+1
-2
@@ -70,11 +70,10 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+13
-17
@@ -15,23 +15,6 @@ repositories {
|
||||
includeGroup 'mega'
|
||||
}
|
||||
}
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
ivy {
|
||||
name = 'CoreTweaks releases'
|
||||
url = 'https://github.com/makamys/CoreTweaks/releases/download/'
|
||||
patternLayout {
|
||||
artifact '[revision]/[module]-1.7.10-[revision]+nomixin(-[classifier])(.[ext])'
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup('CoreTweaks')
|
||||
}
|
||||
}
|
||||
maven {
|
||||
name = 'taumc'
|
||||
url = 'https://maven.taumc.org/releases'
|
||||
@@ -39,5 +22,18 @@ repositories {
|
||||
includeGroup 'org.taumc'
|
||||
}
|
||||
}
|
||||
ivy {
|
||||
name = 'modernsplash-github'
|
||||
url = 'https://github.com/MalTeeez/ModernSplash/releases/download/'
|
||||
patternLayout {
|
||||
artifact '[revision]/[module]-[revision](-[classifier]).[ext]'
|
||||
}
|
||||
content {
|
||||
includeModule 'com.github.MalTeeez', 'modernsplash'
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
}
|
||||
mavenLocal()
|
||||
}
|
||||
@@ -2,8 +2,8 @@ pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
// RetroFuturaGradle
|
||||
name "GTNH Maven"
|
||||
url "https://nexus.gtnewhorizons.com/repository/public/"
|
||||
name = "GTNH Maven"
|
||||
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
|
||||
mavenContent {
|
||||
includeGroup("com.gtnewhorizons")
|
||||
includeGroupByRegex("com\\.gtnewhorizons\\..+")
|
||||
@@ -16,5 +16,5 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.48'
|
||||
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.23")
|
||||
}
|
||||
@@ -9,10 +9,14 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@LateMixin
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaLateMixins implements ILateMixinLoader {
|
||||
|
||||
@Override
|
||||
public String getMixinConfig() {
|
||||
int v = Runtime.version().feature();
|
||||
if (v >= 21) return "mixins.angelica.late.j21.json";
|
||||
if (v >= 17) return "mixins.angelica.late.j17.json";
|
||||
return "mixins.angelica.late.json";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.gtnewhorizons.angelica;
|
||||
|
||||
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
|
||||
|
||||
import com.gtnewhorizons.angelica.common.BlockError;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.config.ConfigMigrator;
|
||||
import com.gtnewhorizons.angelica.proxy.CommonProxy;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationMode;
|
||||
import com.gtnewhorizons.angelica.utils.ManagedEnum;
|
||||
@@ -12,13 +11,18 @@ import cpw.mods.fml.common.SidedProxy;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
|
||||
|
||||
@Mod(
|
||||
modid = MOD_ID,
|
||||
name = "Angelica",
|
||||
version = Tags.VERSION,
|
||||
dependencies = """
|
||||
required-after:gtnhlib@[0.6.6,);\
|
||||
required-after:gtnhlib@[0.8.21,);\
|
||||
before:lwjgl3ify@[1.5.3,);\
|
||||
after:hodgepodge@[2.4.4,);\
|
||||
after:CodeChickenCore@[1.2.0,);\
|
||||
@@ -29,6 +33,16 @@ import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
guiFactory = "com.gtnewhorizons.angelica.config.AngelicaGuiConfigFactory")
|
||||
public class AngelicaMod {
|
||||
public static final String MOD_ID = "angelica";
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static SodiumGameOptions CONFIG;
|
||||
|
||||
public static SodiumGameOptions options() {
|
||||
if (CONFIG == null) {
|
||||
CONFIG = SodiumGameOptions.load(ConfigMigrator.handleConfigMigration("angelica-options.json"));
|
||||
}
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
@SidedProxy(clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy", serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
|
||||
public static CommonProxy proxy;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IPatchedTextureAtlasSprite;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ITexturesCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.util.IIcon;
|
||||
|
||||
@@ -20,8 +20,8 @@ public class TextureServices {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void updateTextureAnimation(IIcon icon) {
|
||||
if(icon instanceof IPatchedTextureAtlasSprite patchedSprite) {
|
||||
patchedSprite.markNeedsAnimationUpdate();
|
||||
if (icon instanceof SpriteExtension ext) {
|
||||
ext.celeritas$markActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.coderbot.iris.gl.program.Program;
|
||||
import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
@@ -12,17 +14,20 @@ import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
|
||||
/**
|
||||
@@ -35,20 +40,18 @@ public class BatchingFontRenderer {
|
||||
/** The underlying FontRenderer object that's being accelerated */
|
||||
protected FontRenderer underlying;
|
||||
/** Array of width of all the characters in default.png */
|
||||
protected int[] charWidth = new int[256];
|
||||
/** Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory. */
|
||||
protected byte[] glyphWidth;
|
||||
protected int[] charWidth;
|
||||
/**
|
||||
* Array of RGB triplets defining the 16 standard chat colors followed by 16 darker version of the same colors for
|
||||
* drop shadows.
|
||||
*/
|
||||
private int[] colorCode;
|
||||
private final int[] colorCode;
|
||||
/** Location of the primary font atlas to bind. */
|
||||
protected final ResourceLocation locationFontTexture;
|
||||
|
||||
private final int AAMode;
|
||||
private final int AAStrength;
|
||||
private final int texBoundAttrLocation;
|
||||
private final int mvpMatrixLocation;
|
||||
private final int fontShaderId;
|
||||
|
||||
final boolean isSGA;
|
||||
@@ -74,11 +77,9 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
public BatchingFontRenderer(FontRenderer underlying, int[] charWidth, byte[] glyphWidth,
|
||||
int[] colorCode, ResourceLocation locationFontTexture) {
|
||||
public BatchingFontRenderer(FontRenderer underlying, int[] charWidth, int[] colorCode, ResourceLocation locationFontTexture) {
|
||||
this.underlying = underlying;
|
||||
this.charWidth = charWidth;
|
||||
this.glyphWidth = glyphWidth;
|
||||
this.colorCode = colorCode;
|
||||
this.locationFontTexture = locationFontTexture;
|
||||
|
||||
@@ -91,87 +92,134 @@ public class BatchingFontRenderer {
|
||||
|
||||
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
|
||||
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
|
||||
FontProviderUnicode.get().glyphWidth = this.glyphWidth;
|
||||
|
||||
//noinspection deprecation
|
||||
fontShaderId = FontAAShader.getProgram().getProgramId();
|
||||
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
|
||||
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
|
||||
texBoundAttrLocation = GL20.glGetAttribLocation(fontShaderId, "texBounds");
|
||||
mvpMatrixLocation = GL20.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
|
||||
if (ebo == null) {
|
||||
ebo = new IndexBuffer();
|
||||
vbo = GL15.glGenBuffers();
|
||||
allocateBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
// === Batched rendering
|
||||
|
||||
private int batchDepth = 0;
|
||||
|
||||
private int vtxWriterIndex = 0;
|
||||
private int idxWriterIndex = 0;
|
||||
private static final int INITIAL_BATCH_SIZE = 256;
|
||||
private static final int INITIAL_BATCH_SIZE = 2048;
|
||||
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
|
||||
"this is invalid!");
|
||||
private FloatBuffer batchVtxPositions = memAllocFloat(INITIAL_BATCH_SIZE * 2);
|
||||
private ByteBuffer batchVtxColors = memAlloc(INITIAL_BATCH_SIZE * 4);
|
||||
private FloatBuffer batchVtxTexCoords = memAllocFloat(INITIAL_BATCH_SIZE * 2);
|
||||
private IntBuffer batchIndices = memAllocInt(INITIAL_BATCH_SIZE / 2 * 3);
|
||||
private FloatBuffer batchVtxTexBounds = memAllocFloat(INITIAL_BATCH_SIZE * 4);
|
||||
|
||||
// Layout in data:
|
||||
// [v, v, t, t, c, c, c, c, tb, tb, tb, tb]
|
||||
// v, t and tb are floats, c is bytes; 36 bytes total
|
||||
private static final int VERTEX_SIZE = 36;
|
||||
private static int rawCapacity = INITIAL_BATCH_SIZE * VERTEX_SIZE;
|
||||
private static long vertexDataAddress = nmemAllocChecked(rawCapacity);
|
||||
|
||||
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
|
||||
private static int fontVAO = 0;
|
||||
private static int vbo;
|
||||
private static IndexBuffer ebo;
|
||||
|
||||
private int batchDepth = 0;
|
||||
|
||||
private int vertexDataPos = 0;
|
||||
private int idxWriterIndex = 0;
|
||||
|
||||
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
|
||||
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
|
||||
|
||||
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
|
||||
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
|
||||
/** */
|
||||
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
|
||||
final int oldCap = batchVtxPositions.capacity() / 2;
|
||||
if (vtxWriterIndex >= oldCap) {
|
||||
final int newCap = oldCap * 2;
|
||||
batchVtxPositions = memRealloc(batchVtxPositions, newCap * 2);
|
||||
batchVtxColors = memRealloc(batchVtxColors, newCap * 4);
|
||||
batchVtxTexCoords = memRealloc(batchVtxTexCoords, newCap * 2);
|
||||
batchVtxTexBounds = memRealloc(batchVtxTexBounds, newCap * 4);
|
||||
final int oldIdxCap = batchIndices.capacity();
|
||||
final int newIdxCap = oldIdxCap * 2;
|
||||
batchIndices = memRealloc(batchIndices, newIdxCap);
|
||||
|
||||
private void allocateBuffers() {
|
||||
populateEBO(rawCapacity / VERTEX_SIZE);
|
||||
}
|
||||
|
||||
private void populateEBO(int capacity) {
|
||||
final int quadCount = capacity * 6;
|
||||
final ByteBuffer data = memAlloc(quadCount * 6 * 2);
|
||||
long ptr = memAddress0(data);
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
int base = (i * 4);
|
||||
|
||||
// triangle 1
|
||||
memPutShort(ptr, (short) base);
|
||||
memPutShort(ptr + 2, (short) (base + 1));
|
||||
memPutShort(ptr + 4, (short) (base + 2));
|
||||
|
||||
// triangle 2
|
||||
memPutShort(ptr + 6, (short) (base + 2));
|
||||
memPutShort(ptr + 8, (short) (base + 1));
|
||||
memPutShort(ptr + 10, (short) (base + 3));
|
||||
ptr += 12;
|
||||
}
|
||||
final int idx = vtxWriterIndex;
|
||||
final int idx2 = idx * 2;
|
||||
final int idx4 = idx * 4;
|
||||
batchVtxPositions.put(idx2, x);
|
||||
batchVtxPositions.put(idx2 + 1, y);
|
||||
|
||||
ebo.upload(data);
|
||||
|
||||
memFree(data);
|
||||
|
||||
}
|
||||
|
||||
private void ensureCapacity() {
|
||||
if (vertexDataPos + (4 * VERTEX_SIZE) > rawCapacity) {
|
||||
rawCapacity *= 2;
|
||||
vertexDataAddress = nmemReallocChecked(vertexDataAddress, rawCapacity);
|
||||
|
||||
allocateBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
|
||||
final long ptr = vertexDataAddress + vertexDataPos;
|
||||
|
||||
// v, v
|
||||
memPutFloat(ptr, x);
|
||||
memPutFloat(ptr + 4, y);
|
||||
|
||||
// t, t
|
||||
memPutFloat(ptr + 8, u);
|
||||
memPutFloat(ptr + 12, v);
|
||||
|
||||
// c, c, c, c
|
||||
// 0xAARRGGBB
|
||||
batchVtxColors.put(idx4, (byte) ((rgba >> 16) & 0xFF));
|
||||
batchVtxColors.put(idx4 + 1, (byte) ((rgba >> 8) & 0xFF));
|
||||
batchVtxColors.put(idx4 + 2, (byte) (rgba & 0xFF));
|
||||
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
|
||||
batchVtxTexCoords.put(idx2, u);
|
||||
batchVtxTexCoords.put(idx2 + 1, v);
|
||||
batchVtxTexBounds.put(idx4, uMin);
|
||||
batchVtxTexBounds.put(idx4 + 1, uMax);
|
||||
batchVtxTexBounds.put(idx4 + 2, vMin);
|
||||
batchVtxTexBounds.put(idx4 + 3, vMax);
|
||||
vtxWriterIndex++;
|
||||
memPutByte(ptr + 16, (byte) ((rgba >> 16) & 0xFF));
|
||||
memPutByte(ptr + 17, (byte) ((rgba >> 8) & 0xFF));
|
||||
memPutByte(ptr + 18, (byte) (rgba & 0xFF));
|
||||
memPutByte(ptr + 19, (byte) ((rgba >> 24) & 0xFF));
|
||||
|
||||
// tb, tb, tb, tb
|
||||
memPutFloat(ptr + 20, uMin);
|
||||
memPutFloat(ptr + 24, uMax);
|
||||
memPutFloat(ptr + 28, vMin);
|
||||
memPutFloat(ptr + 32, vMax);
|
||||
|
||||
vertexDataPos += VERTEX_SIZE;
|
||||
}
|
||||
|
||||
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
|
||||
final int vtxId = vtxWriterIndex;
|
||||
ensureCapacity();
|
||||
pushVtx(x, y, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x, y + h, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x + w, y, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x + w, y + h, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushQuadIdx(vtxId);
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
private int pushQuadIdx(int startV) {
|
||||
final int idx = idxWriterIndex;
|
||||
batchIndices.put(idx, startV);
|
||||
batchIndices.put(idx + 1, startV + 1);
|
||||
batchIndices.put(idx + 2, startV + 2);
|
||||
//
|
||||
batchIndices.put(idx + 3, startV + 2);
|
||||
batchIndices.put(idx + 4, startV + 1);
|
||||
batchIndices.put(idx + 5, startV + 3);
|
||||
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
|
||||
ensureCapacity();
|
||||
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
private void pushQuadIdx() {
|
||||
idxWriterIndex += 6;
|
||||
return idx;
|
||||
}
|
||||
|
||||
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
|
||||
@@ -246,9 +294,6 @@ public class BatchingFontRenderer {
|
||||
* allow for easier optimizing of blocks of font rendering code.
|
||||
*/
|
||||
public void beginBatch() {
|
||||
if (batchDepth == Integer.MAX_VALUE) {
|
||||
throw new StackOverflowError("More than Integer.MAX_VALUE nested font rendering batch operations");
|
||||
}
|
||||
batchDepth++;
|
||||
}
|
||||
|
||||
@@ -264,10 +309,23 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
int lastActiveProgram;
|
||||
int fontAAModeLast = -1;
|
||||
int fontAAStrengthLast = -1;
|
||||
private static final Matrix4f scratchMvp = new Matrix4f();
|
||||
private int fontAAModeLast = -1;
|
||||
private int fontAAStrengthLast = -1;
|
||||
|
||||
private void flushBatch() {
|
||||
if (vertexDataPos == 0) {
|
||||
clearBatch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload first (to reduce stalls)
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
|
||||
streamUpload(vertexDataAddress, vertexDataPos);
|
||||
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
// Sort&Draw
|
||||
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
@@ -282,27 +340,48 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
|
||||
if (FontConfig.fontAAMode != 0) {
|
||||
GL20.glVertexAttribPointer(texBoundAttrLocation, 4, false, 0, batchVtxTexBounds);
|
||||
GL20.glEnableVertexAttribArray(texBoundAttrLocation);
|
||||
lastActiveProgram = GLStateManager.getActiveProgram();
|
||||
GLStateManager.glUseProgram(fontShaderId);
|
||||
if (FontConfig.fontAAMode != fontAAModeLast) {
|
||||
fontAAModeLast = FontConfig.fontAAMode;
|
||||
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
|
||||
}
|
||||
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
|
||||
fontAAStrengthLast = FontConfig.fontAAStrength;
|
||||
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
|
||||
}
|
||||
GLStateManager.glUseProgram(fontShaderId);
|
||||
if (FontConfig.fontAAMode != fontAAModeLast) {
|
||||
fontAAModeLast = FontConfig.fontAAMode;
|
||||
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
|
||||
}
|
||||
GL11.glTexCoordPointer(2, 0, batchVtxTexCoords);
|
||||
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 0, batchVtxColors);
|
||||
GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
|
||||
GL11.glVertexPointer(2, 0, batchVtxPositions);
|
||||
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
|
||||
fontAAStrengthLast = FontConfig.fontAAStrength;
|
||||
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
|
||||
}
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), scratchMvp);
|
||||
scratchMvp.get(mvpBuf);
|
||||
GL20.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
}
|
||||
|
||||
if (fontVAO == 0) {
|
||||
fontVAO = GL30.glGenVertexArrays();
|
||||
|
||||
GLStateManager.glBindVertexArray(fontVAO);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
|
||||
ebo.bind();
|
||||
|
||||
// position
|
||||
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 0);
|
||||
GL20.glEnableVertexAttribArray(0);
|
||||
|
||||
// texcoords
|
||||
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 8);
|
||||
GL20.glEnableVertexAttribArray(1);
|
||||
|
||||
// color
|
||||
GL20.glVertexAttribPointer(2, 4, GL11.GL_UNSIGNED_BYTE, true, VERTEX_SIZE, 16);
|
||||
GL20.glEnableVertexAttribArray(2);
|
||||
|
||||
// tex bounds
|
||||
GL20.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, VERTEX_SIZE, 20);
|
||||
GL20.glEnableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
GLStateManager.glBindVertexArray(fontVAO);
|
||||
|
||||
// Use plain for loop to avoid allocations
|
||||
final FontDrawCmd[] cmdsData = batchCommands.elements();
|
||||
@@ -321,19 +400,14 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
lastTexture = cmd.texture;
|
||||
}
|
||||
batchIndices.limit(cmd.startVtx + cmd.idxCount);
|
||||
batchIndices.position(cmd.startVtx);
|
||||
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, batchIndices);
|
||||
}
|
||||
if (FontConfig.fontAAMode != 0) {
|
||||
GLStateManager.glUseProgram(lastActiveProgram);
|
||||
GL20.glDisableVertexAttribArray(texBoundAttrLocation);
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, cmd.idxCount, GL11.GL_UNSIGNED_SHORT, (long) cmd.startVtx * 2L);
|
||||
}
|
||||
|
||||
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
|
||||
GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
|
||||
GLStateManager.glUseProgram(prevProgram);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
if (isTextureEnabledBefore) {
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
@@ -342,13 +416,21 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
|
||||
}
|
||||
|
||||
clearBatch();
|
||||
}
|
||||
|
||||
private void clearBatch() {
|
||||
// Clear for the next batch
|
||||
batchCommandPool.addAll(batchCommands);
|
||||
batchCommands.clear();
|
||||
vtxWriterIndex = 0;
|
||||
vertexDataPos = 0;
|
||||
idxWriterIndex = 0;
|
||||
batchIndices.limit(batchIndices.capacity());
|
||||
batchIndices.position(0);
|
||||
}
|
||||
|
||||
private static void streamUpload(long address, int bytes) {
|
||||
final ByteBuffer data = memByteBuffer(address, bytes);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bytes, GL15.GL_STREAM_DRAW);
|
||||
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
|
||||
}
|
||||
|
||||
// === Actual text mesh generation
|
||||
@@ -362,11 +444,11 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
|
||||
public float getGlyphScaleX() {
|
||||
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale * Math.pow(2, FontConfig.glyphAspect)) * (FontStrategist.customFontInUse ? 1.5f : 1);
|
||||
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale * Math.pow(2, FontConfig.glyphAspect));
|
||||
}
|
||||
|
||||
public float getGlyphScaleY() {
|
||||
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale / Math.pow(2, FontConfig.glyphAspect)) * (FontStrategist.customFontInUse ? 1.5f : 1);
|
||||
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale / Math.pow(2, FontConfig.glyphAspect));
|
||||
}
|
||||
|
||||
public float getGlyphSpacing() {
|
||||
@@ -413,13 +495,14 @@ public class BatchingFontRenderer {
|
||||
boolean curStrikethrough = false;
|
||||
boolean curUnderline = false;
|
||||
|
||||
final float glyphScaleY = getGlyphScaleY();
|
||||
final float heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY / 2);
|
||||
final float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
|
||||
float glyphScaleY = getGlyphScaleY();
|
||||
float glyphScaleX = getGlyphScaleX();
|
||||
float heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY / 2);
|
||||
|
||||
final float underlineY = heightNorth + (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
|
||||
float underlineStartX = 0.0f;
|
||||
float underlineEndX = 0.0f;
|
||||
|
||||
final float strikethroughY = heightNorth + ((float) (underlying.FONT_HEIGHT / 2) - 1.0f) * glyphScaleY;
|
||||
float strikethroughStartX = 0.0f;
|
||||
float strikethroughEndX = 0.0f;
|
||||
@@ -489,12 +572,28 @@ public class BatchingFontRenderer {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
|
||||
String chrReplacement = GlyphReplacements.customGlyphs.get(String.valueOf(chr));
|
||||
if (chrReplacement != null) {
|
||||
char replacement = chrReplacement.charAt(0);
|
||||
boolean isReplacementCharAvailable =
|
||||
FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|
||||
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement);
|
||||
if (isReplacementCharAvailable) {
|
||||
chr = replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curRandom) {
|
||||
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
|
||||
}
|
||||
|
||||
FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
|
||||
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
|
||||
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
|
||||
|
||||
// Check ASCII space, NBSP, NNBSP
|
||||
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
|
||||
curX += 4 * this.getWhitespaceScale();
|
||||
@@ -503,55 +602,50 @@ public class BatchingFontRenderer {
|
||||
|
||||
final float uStart = fontProvider.getUStart(chr);
|
||||
final float vStart = fontProvider.getVStart(chr);
|
||||
final float xAdvance = fontProvider.getXAdvance(chr) * getGlyphScaleX();
|
||||
final float glyphW = fontProvider.getGlyphW(chr) * getGlyphScaleX();
|
||||
final float xAdvance = fontProvider.getXAdvance(chr) * glyphScaleX;
|
||||
final float glyphW = fontProvider.getGlyphW(chr) * glyphScaleX;
|
||||
final float uSz = fontProvider.getUSize(chr);
|
||||
final float vSz = fontProvider.getVSize(chr);
|
||||
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
|
||||
final float shadowOffset = fontProvider.getShadowOffset();
|
||||
final int shadowCopies = FontConfig.shadowCopies;
|
||||
final int boldCopies = FontConfig.boldCopies;
|
||||
final ResourceLocation texture = fontProvider.getTexture(chr);
|
||||
|
||||
final int vtxId = vtxWriterIndex;
|
||||
final int idxId = idxWriterIndex;
|
||||
|
||||
int vtxCount = 0;
|
||||
|
||||
if (enableShadow) {
|
||||
pushVtx(curX + itOff + shadowOffset, heightNorth + shadowOffset, curShadowColor, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX - itOff + shadowOffset, heightNorth + heightSouth + shadowOffset, curShadowColor, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff + shadowOffset, heightNorth + shadowOffset, curShadowColor, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset, heightNorth + heightSouth + shadowOffset, curShadowColor, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
for (int n = 1; n <= shadowCopies; n++) {
|
||||
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
|
||||
pushTexRect(curX + shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
|
||||
|
||||
if (curBold) {
|
||||
final float shadowOffset2 = 2.0f * shadowOffset;
|
||||
pushVtx(curX + itOff + shadowOffset2, heightNorth + shadowOffset, curShadowColor, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX - itOff + shadowOffset2, heightNorth + heightSouth + shadowOffset, curShadowColor, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff + shadowOffset2, heightNorth + shadowOffset, curShadowColor, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset2, heightNorth + heightSouth + shadowOffset, curShadowColor, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
if (curBold) {
|
||||
pushTexRect(curX + 2.0f * shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pushVtx(curX + itOff, heightNorth, curColor, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX - itOff, heightNorth + heightSouth, curColor, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff, heightNorth, curColor, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff, heightNorth + heightSouth, curColor, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
pushTexRect(curX, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
|
||||
|
||||
if (curBold) {
|
||||
pushVtx(shadowOffset + curX + itOff, heightNorth, curColor, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(shadowOffset + curX - itOff, heightNorth + heightSouth, curColor, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(shadowOffset + curX + glyphW - 1.0F + itOff, heightNorth, curColor, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(shadowOffset + curX + glyphW - 1.0F - itOff, heightNorth + heightSouth, curColor, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
for (int n = 1; n <= boldCopies; n++) {
|
||||
final float shadowOffsetPart = shadowOffset * ((float) n / boldCopies);
|
||||
pushTexRect(curX + shadowOffsetPart, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Vertex-per-char counts for different configurations
|
||||
default: 4
|
||||
shadow only: 4(1 + shadowCopies)
|
||||
bold only: 4(1 + boldCopies)
|
||||
both: 4(1 + 2 * shadowCopies + boldCopies)
|
||||
*/
|
||||
int charCount = 1;
|
||||
if (enableShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
|
||||
if (curBold) { charCount += boldCopies; }
|
||||
final int vtxCount = 4 * charCount;
|
||||
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
|
||||
|
||||
curX += (xAdvance + (curBold ? shadowOffset : 0.0f)) + getGlyphSpacing();
|
||||
underlineEndX = curX;
|
||||
strikethroughEndX = curX;
|
||||
|
||||
@@ -17,4 +17,5 @@ public interface FontProvider {
|
||||
float getVSize(char chr);
|
||||
float getShadowOffset();
|
||||
ResourceLocation getTexture(char chr);
|
||||
float getYScaleMultiplier();
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
@@ -40,19 +39,13 @@ public final class FontProviderCustom implements FontProvider {
|
||||
private FontProviderCustom(byte id) {
|
||||
this.id = id;
|
||||
Font[] availableFonts = FontStrategist.getAvailableFonts();
|
||||
if (availableFonts.length == 0) {
|
||||
LOGGER.fatal("There seem to be no fonts available on this system! Disabling custom font and throwing an exception in an attempt to restore the session.");
|
||||
FontConfig.enableCustomFont = false;
|
||||
ConfigurationManager.save(FontConfig.class);
|
||||
throw new RuntimeException();
|
||||
}
|
||||
String myFontName = switch (this.id) {
|
||||
case 0 -> FontConfig.customFontNamePrimary;
|
||||
case 1 -> FontConfig.customFontNameFallback;
|
||||
default -> null;
|
||||
};
|
||||
if (Objects.equals(myFontName, "(none)")) {
|
||||
font = null;
|
||||
this.font = null;
|
||||
return;
|
||||
}
|
||||
int fontPos = -1;
|
||||
@@ -64,10 +57,10 @@ public final class FontProviderCustom implements FontProvider {
|
||||
}
|
||||
if (fontPos == -1) {
|
||||
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
|
||||
font = null;
|
||||
this.font = null;
|
||||
return;
|
||||
}
|
||||
font = availableFonts[fontPos].deriveFont(currentFontQuality);
|
||||
this.font = availableFonts[fontPos].deriveFont(this.currentFontQuality);
|
||||
}
|
||||
private static class InstLoader {
|
||||
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
|
||||
@@ -77,8 +70,8 @@ public final class FontProviderCustom implements FontProvider {
|
||||
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
|
||||
|
||||
public void reloadFont(int fontID) {
|
||||
currentFontQuality = FontConfig.customFontQuality;
|
||||
font = FontStrategist.getAvailableFonts()[fontID].deriveFont(currentFontQuality);
|
||||
this.currentFontQuality = FontConfig.customFontQuality;
|
||||
this.font = FontStrategist.getAvailableFonts()[fontID].deriveFont(this.currentFontQuality);
|
||||
|
||||
File[] files = new File(getFontDir()).listFiles();
|
||||
if (files != null) {
|
||||
@@ -100,7 +93,7 @@ public final class FontProviderCustom implements FontProvider {
|
||||
}
|
||||
}
|
||||
|
||||
fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
this.fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
}
|
||||
|
||||
private String getFontDir() {
|
||||
@@ -140,50 +133,44 @@ public final class FontProviderCustom implements FontProvider {
|
||||
}
|
||||
|
||||
void construct(Font font) {
|
||||
float separator = currentFontQuality / 3f;
|
||||
int atlasChars = 0;
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char) (i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) { atlasChars++; }
|
||||
}
|
||||
if (atlasChars == 0) { return; }
|
||||
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
g2d.setFont(font);
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
g2d.dispose();
|
||||
int width = 0;
|
||||
int actualChars = 0;
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char)(i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) {
|
||||
width += (int) (separator + fm.charWidth(ch));
|
||||
actualChars++;
|
||||
}
|
||||
}
|
||||
if (actualChars == 0) {
|
||||
return;
|
||||
}
|
||||
final int atlasTilesX = (int) Math.ceil(Math.sqrt(actualChars) * 1.5f);
|
||||
final int atlasTilesY = (int) Math.ceil((double) actualChars / atlasTilesX);
|
||||
LOGGER.info("constructing custom font atlas (ID {}) of nominal size {} chars, actual: {}, {} rows by {} columns",
|
||||
this.id, ATLAS_SIZE, actualChars, atlasTilesX, atlasTilesY);
|
||||
width = 0;
|
||||
actualChars = 0;
|
||||
|
||||
final int atlasTilesX = (int) Math.ceil(Math.sqrt(atlasChars) * 1.5f);
|
||||
final int atlasTilesY = (int) Math.ceil((double) atlasChars / atlasTilesX);
|
||||
final float charSeparator = currentFontQuality / 3f;
|
||||
int rowWidth = 0;
|
||||
int maxRowWidth = 0;
|
||||
atlasChars = 0;
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
if (actualChars % atlasTilesX == 0) {
|
||||
maxRowWidth = Math.max(maxRowWidth, width);
|
||||
width = 0;
|
||||
if (atlasChars % atlasTilesX == 0) {
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
rowWidth = 0;
|
||||
}
|
||||
final char ch = (char)(i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) {
|
||||
width += (int) (separator + fm.charWidth(ch));
|
||||
actualChars++;
|
||||
rowWidth += (int) (charSeparator + fm.charWidth(ch));
|
||||
atlasChars++;
|
||||
}
|
||||
}
|
||||
maxRowWidth = Math.max(maxRowWidth, width);
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
|
||||
final int lineHeight = fm.getHeight();
|
||||
final float desc = fm.getDescent();
|
||||
|
||||
final int imageWidth = (int) (maxRowWidth + separator);
|
||||
final int imageHeight = (int) ((separator + lineHeight) * atlasTilesY + separator);
|
||||
final int imageWidth = (int) (maxRowWidth + charSeparator);
|
||||
final int imageHeight = (int) ((charSeparator + lineHeight) * atlasTilesY + charSeparator);
|
||||
|
||||
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
g2d = image.createGraphics();
|
||||
@@ -198,35 +185,34 @@ public final class FontProviderCustom implements FontProvider {
|
||||
fm = g2d.getFontMetrics();
|
||||
|
||||
int tileX = 0, tileY = 0; // position in atlas tiles
|
||||
int imgX = (int) separator; // position in pixels
|
||||
int imgX = (int) charSeparator; // position in pixels
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char)(i + ATLAS_SIZE * this.id);
|
||||
final char ch = (char) (i + ATLAS_SIZE * this.id);
|
||||
if (!font.canDisplay(ch)) { continue; }
|
||||
|
||||
if (tileX >= atlasTilesX) {
|
||||
tileX = 0;
|
||||
imgX = (int) separator;
|
||||
imgX = (int) charSeparator;
|
||||
tileY++;
|
||||
}
|
||||
|
||||
final int charWidth = fm.charWidth(ch);
|
||||
final float charAspectRatio = (float) charWidth / lineHeight;
|
||||
final float inset = currentFontQuality / 16;
|
||||
g2d.drawString(Character.toString(ch), imgX, (lineHeight + separator) * (tileY + 1) - desc);
|
||||
g2d.drawString(Character.toString(ch), imgX, (lineHeight + charSeparator) * (tileY + 1) - desc);
|
||||
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
|
||||
final float vStart = ((lineHeight + separator) * (tileY + 1) - lineHeight - inset) / imageHeight;
|
||||
final float vStart = ((lineHeight + charSeparator) * (tileY + 1) - lineHeight - inset) / imageHeight;
|
||||
final float xAdvance = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
|
||||
final float glyphW = charAspectRatio * 8 + 1;
|
||||
final float uSz = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
|
||||
final float vSz = (float) (lineHeight + 2 * inset) / imageHeight;
|
||||
glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
|
||||
imgX += (int) (charWidth + separator);
|
||||
this.glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
|
||||
imgX += (int) (charWidth + charSeparator);
|
||||
tileX++;
|
||||
}
|
||||
g2d.dispose();
|
||||
try {
|
||||
LOGGER.info("writing custom font atlas texture ({}x{} px) to {}", imageWidth, imageHeight, getAtlasFullPath(this.id));
|
||||
Files.createDirectories(Paths.get(getFontDir()));
|
||||
ImageIO.write(image, "png", new File(getAtlasFullPath(this.id)));
|
||||
} catch (IOException e) {
|
||||
@@ -238,18 +224,18 @@ public final class FontProviderCustom implements FontProvider {
|
||||
|
||||
private FontAtlas getAtlas(char chr) {
|
||||
int id = chr / ATLAS_SIZE;
|
||||
FontAtlas fa = fontAtlases[id];
|
||||
FontAtlas fa = this.fontAtlases[id];
|
||||
if (fa == null) {
|
||||
fa = new FontAtlas(id);
|
||||
fa.construct(font);
|
||||
fa.construct(this.font);
|
||||
this.fontAtlases[id] = fa;
|
||||
}
|
||||
fontAtlases[id] = fa;
|
||||
return fa;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
if (font == null) { return false; }
|
||||
if (this.font == null) { return false; }
|
||||
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
|
||||
}
|
||||
|
||||
@@ -270,12 +256,12 @@ public final class FontProviderCustom implements FontProvider {
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance;
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW;
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -297,4 +283,9 @@ public final class FontProviderCustom implements FontProvider {
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
return getAtlas(chr).texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return FontConfig.customFontScale;
|
||||
}
|
||||
}
|
||||
@@ -28,16 +28,14 @@ public final class FontProviderMC implements FontProvider {
|
||||
public int[] charWidth;
|
||||
|
||||
static {
|
||||
MCFONT_ASCII_MAP.defaultReturnValue((short) -1);
|
||||
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
public int lookupMcFontPosition(char ch) {
|
||||
if (isGlyphAvailable(ch)) {
|
||||
return MCFONT_ASCII_MAP.get(ch);
|
||||
}
|
||||
return -1;
|
||||
return MCFONT_ASCII_MAP.get(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,4 +101,9 @@ public final class FontProviderMC implements FontProvider {
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
return locationFontTexture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,31 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
private FontProviderUnicode() {}
|
||||
private FontProviderUnicode() {
|
||||
try {
|
||||
InputStream inputstream = Minecraft.getMinecraft().getResourceManager()
|
||||
.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputstream.read(this.glyphWidth);
|
||||
}
|
||||
catch (IOException ioexception) {
|
||||
throw new RuntimeException(ioexception);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InstLoader { static final FontProviderUnicode instance = new FontProviderUnicode(); }
|
||||
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
|
||||
|
||||
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
|
||||
public byte[] glyphWidth;
|
||||
private final byte[] glyphWidth = new byte[65536];
|
||||
|
||||
private ResourceLocation getUnicodePageLocation(int page) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
@@ -37,7 +52,7 @@ public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
final float startColumnF = (float)(this.glyphWidth[chr] >>> 4);
|
||||
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
|
||||
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@@ -48,7 +63,7 @@ public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
final int startColumn = this.glyphWidth[chr] >>> 4;
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
@@ -57,7 +72,7 @@ public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
final int startColumn = this.glyphWidth[chr] >>> 4;
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
@@ -67,7 +82,7 @@ public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
final int startColumn = this.glyphWidth[chr] >>> 4;
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
@@ -90,4 +105,9 @@ public final class FontProviderUnicode implements FontProvider {
|
||||
final int uniPage = chr / 256;
|
||||
return getUnicodePageLocation(uniPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -25,37 +25,42 @@ public class FontStrategist {
|
||||
@Getter
|
||||
private static final Font[] availableFonts;
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
public static boolean customFontInUse;
|
||||
|
||||
static {
|
||||
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
|
||||
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
|
||||
HashMap<String, Font> fontSet = new HashMap<>();
|
||||
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
|
||||
if (GraphicsEnvironment.isHeadless()) {
|
||||
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Custom fonts will be unavailable. This is likely a MacOS issue.");
|
||||
availableFonts = new Font[0];
|
||||
} else {
|
||||
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
|
||||
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
|
||||
HashMap<String, Font> fontSet = new HashMap<>();
|
||||
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
|
||||
|
||||
for (Font font : availableFontsDirty) {
|
||||
String fontName = font.getFontName();
|
||||
if (fontSet.containsKey(fontName)) {
|
||||
duplicates.add(fontName);
|
||||
} else {
|
||||
fontSet.put(fontName, font);
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicates.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder(duplicates.size() + " duplicate font(s) found in the list reported by Java: ");
|
||||
for (Iterator<String> iter = duplicates.stream().distinct().iterator(); iter.hasNext(); ) {
|
||||
String dupe = iter.next();
|
||||
sb.append(duplicates.count(dupe)).append("x ").append(dupe);
|
||||
if (iter.hasNext()) {
|
||||
sb.append(", ");
|
||||
for (Font font : availableFontsDirty) {
|
||||
String fontName = font.getFontName();
|
||||
if (fontSet.containsKey(fontName)) {
|
||||
duplicates.add(fontName);
|
||||
} else {
|
||||
fontSet.put(fontName, font);
|
||||
}
|
||||
}
|
||||
sb.append(". Some fonts may be missing from the font selection menu.");
|
||||
LOGGER.warn(sb.toString());
|
||||
}
|
||||
|
||||
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
|
||||
if (!duplicates.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder(duplicates.size() + " duplicate font(s) found in the list reported by Java: ");
|
||||
for (Iterator<String> iter = duplicates.stream().distinct().iterator(); iter.hasNext(); ) {
|
||||
String dupe = iter.next();
|
||||
sb.append(duplicates.count(dupe)).append("x ").append(dupe);
|
||||
if (iter.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(". Some fonts may be missing from the font selection menu.");
|
||||
LOGGER.warn(sb.toString());
|
||||
}
|
||||
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
|
||||
|
||||
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
|
||||
}
|
||||
|
||||
// create and add the resource pack that provides fonts
|
||||
HashMap<String, File> packMap = new HashMap<>();
|
||||
@@ -67,22 +72,6 @@ public class FontStrategist {
|
||||
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
|
||||
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
|
||||
defaultResourcePacks.add(fontResourcePack);
|
||||
Minecraft.getMinecraft().refreshResources();
|
||||
|
||||
// determine if a custom font is in use based on config values
|
||||
boolean primaryFontFound = false;
|
||||
boolean fallbackFontFound = false;
|
||||
for (Font availableFont : availableFonts) {
|
||||
if (Objects.equals(FontConfig.customFontNamePrimary, availableFont.getFontName())) {
|
||||
primaryFontFound = true;
|
||||
break;
|
||||
}
|
||||
if (Objects.equals(FontConfig.customFontNameFallback, availableFont.getFontName())) {
|
||||
fallbackFontFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
customFontInUse = (FontConfig.enableCustomFont && (primaryFontFound || fallbackFontFound));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,20 +101,14 @@ public class FontStrategist {
|
||||
public static void reloadCustomFontProviders() {
|
||||
FontProviderCustom.getPrimary().setFont(null);
|
||||
FontProviderCustom.getFallback().setFont(null);
|
||||
boolean primaryFontFound = false;
|
||||
boolean fallbackFontFound = false;
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(FontConfig.customFontNamePrimary, availableFonts[i].getFontName())) {
|
||||
FontProviderCustom.getPrimary().reloadFont(i);
|
||||
primaryFontFound = true;
|
||||
}
|
||||
if (Objects.equals(FontConfig.customFontNameFallback, availableFonts[i].getFontName())) {
|
||||
FontProviderCustom.getFallback().reloadFont(i);
|
||||
fallbackFontFound = true;
|
||||
}
|
||||
if (primaryFontFound && fallbackFontFound) { break; }
|
||||
}
|
||||
customFontInUse = (FontConfig.enableCustomFont && (primaryFontFound || fallbackFontFound));
|
||||
}
|
||||
|
||||
public static boolean isSplashFontRendererActive(FontRenderer fontRenderer) {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfigStorage;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityTypeEntry;
|
||||
import jss.notfine.core.Settings;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.Option;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionImpl;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionImpact;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.control.TickBoxControl;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DynamicLightsOptionPages {
|
||||
|
||||
public static OptionPage dynamicLights() {
|
||||
if (!DynamicLights.configEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<OptionGroup> groups = new ArrayList<>();
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_SHADER_FORCE.option)
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS_FRUSTUM_CULLING.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_ADAPTIVE_TICKING.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_CULL_TIMEOUT.option)
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS_SLOW_DIST.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_SLOWER_DIST.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_BACKGROUND_DIST.option)
|
||||
.build());
|
||||
|
||||
// Entity type toggles - grouped by mod ID
|
||||
List<EntityTypeEntry> allEntities = EntityLightConfig.getAllEntityTypes();
|
||||
Map<String, List<EntityTypeEntry>> byMod = new LinkedHashMap<>();
|
||||
|
||||
for (EntityTypeEntry entry : allEntities) {
|
||||
byMod.computeIfAbsent(entry.getModId(), _ -> new ArrayList<>()).add(entry);
|
||||
}
|
||||
|
||||
// Create a group for each mod's entities
|
||||
for (Map.Entry<String, List<EntityTypeEntry>> modEntry : byMod.entrySet()) {
|
||||
String modId = modEntry.getKey();
|
||||
List<EntityTypeEntry> entities = modEntry.getValue();
|
||||
|
||||
OptionGroup.Builder groupBuilder = OptionGroup.createBuilder();
|
||||
|
||||
for (EntityTypeEntry entity : entities) {
|
||||
Option<Boolean> option = createEntityOption(entity);
|
||||
if (option != null) {
|
||||
groupBuilder.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
// Only add group if it has options
|
||||
OptionGroup group = groupBuilder.build();
|
||||
if (!group.getOptions().isEmpty()) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
|
||||
return new OptionPage(I18n.format("options.dynamiclights.page"), ImmutableList.copyOf(groups));
|
||||
}
|
||||
|
||||
private static Option<Boolean> createEntityOption(EntityTypeEntry entry) {
|
||||
final String className = entry.getClassName();
|
||||
|
||||
// Resolve class once during option creation, not on every get/set
|
||||
Class<?> resolvedClass;
|
||||
try {
|
||||
resolvedClass = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Class not loaded, skip this option
|
||||
return null;
|
||||
}
|
||||
|
||||
final Class<?> entityClass = resolvedClass;
|
||||
|
||||
return OptionImpl.createBuilder(Boolean.class, EntityLightConfigStorage.INSTANCE)
|
||||
.setName(entry.getDisplayName() + " (" + entry.getModId() + ")")
|
||||
.setTooltip(I18n.format("options.dynamiclights.entity.tooltip", entry.getDisplayName()))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setBinding(
|
||||
(_, value) -> EntityLightConfig.setEntityTypeEnabled(entityClass, value),
|
||||
storage -> EntityLightConfig.isEntityTypeEnabled(entityClass)
|
||||
)
|
||||
.setImpact(OptionImpact.LOW)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import net.minecraft.client.gui.GuiSlot;
|
||||
import net.minecraft.client.gui.GuiTextField;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import java.awt.Font;
|
||||
@@ -26,6 +27,9 @@ public class FontConfigScreen extends GuiScreen {
|
||||
private final GuiScreen parent;
|
||||
private final String title;
|
||||
private final String searchPrompt;
|
||||
private final String testAreaPrompt;
|
||||
private List<String> testAreaInfo;
|
||||
private List<String> noFontsWarning;
|
||||
private String currentPrimaryFontName;
|
||||
private String currentFallbackFontName;
|
||||
private FontList fontList;
|
||||
@@ -33,10 +37,32 @@ public class FontConfigScreen extends GuiScreen {
|
||||
private int selectedFallbackFontListPos = -1;
|
||||
private ArrayList<Font> displayedFonts;
|
||||
private GuiTextField searchBox;
|
||||
private GuiTextField testArea;
|
||||
private int sliderPages = 0;
|
||||
private int displayedSliderPage = 0;
|
||||
private final int sliderWidth = 160;
|
||||
private final int sliderHeight = 20;
|
||||
|
||||
// need to save these separately
|
||||
private IrisButton fwdButton;
|
||||
private IrisButton backButton;
|
||||
|
||||
SliderClone.Option optFontQuality = new SliderClone.Option(6, 72, 6);
|
||||
SliderClone.Option optShadowOffset = new SliderClone.Option(0, 2, 0.05f);
|
||||
SliderClone.Option optShadowCopies = new SliderClone.Option(1, 8, 1);
|
||||
SliderClone.Option optBoldCopies = new SliderClone.Option(1, 8, 1);
|
||||
SliderClone.Option optGlyphAspect = new SliderClone.Option(-1, 1, 0.05f);
|
||||
SliderClone.Option optGlyphScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optWhitespaceScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optGlyphSpacing = new SliderClone.Option(-2f, 2f, 0.05f);
|
||||
SliderClone.Option optFontAAMode = new SliderClone.Option(0, 2, 1);
|
||||
SliderClone.Option optFontAAStrength = new SliderClone.Option(1, 24, 1);
|
||||
SliderClone.Option optCustomFontScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
|
||||
public FontConfigScreen(GuiScreen parent) {
|
||||
this.title = I18n.format("options.angelica.fontconfig.title");
|
||||
this.searchPrompt = I18n.format("options.angelica.fontconfig.searchprompt");
|
||||
this.testAreaPrompt = I18n.format("options.angelica.fontconfig.testareaprompt");
|
||||
this.parent = parent;
|
||||
this.currentPrimaryFontName = FontConfig.customFontNamePrimary;
|
||||
this.currentFallbackFontName = FontConfig.customFontNameFallback;
|
||||
@@ -51,65 +77,185 @@ public class FontConfigScreen extends GuiScreen {
|
||||
}
|
||||
}
|
||||
|
||||
SliderClone.Option optFontQuality = new SliderClone.Option(6, 72, 6);
|
||||
SliderClone.Option optShadowOffset = new SliderClone.Option(0, 2, 0.05f);
|
||||
SliderClone.Option optGlyphAspect = new SliderClone.Option(-1, 1, 0.05f);
|
||||
SliderClone.Option optGlyphScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optWhitespaceScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optGlyphSpacing = new SliderClone.Option(-2f, 2f, 0.05f);
|
||||
SliderClone.Option optFontAAMode = new SliderClone.Option(0, 2, 1);
|
||||
SliderClone.Option optFontAAStrength = new SliderClone.Option(1, 24, 1);
|
||||
|
||||
public void initGui() {
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
searchBox = new GuiTextField(this.fontRendererObj, this.width / 2 - 120, 24, 240, 20);
|
||||
searchBox.setMaxStringLength(64);
|
||||
fontList = new FontList();
|
||||
testArea = new GuiTextField(this.fontRendererObj, this.width * 5 / 6 - this.width / 7, 63 - 10, this.width * 2 / 7, 20);
|
||||
testArea.setMaxStringLength(512);
|
||||
testArea.setText(I18n.format("options.angelica.fontconfig.testareaexample"));
|
||||
initButtons();
|
||||
}
|
||||
|
||||
private void initButtons() {
|
||||
this.buttonList.add(new IrisButton(this.width / 2 - 81 - 165, this.height - 20 - 7, 162, 20,
|
||||
final int halfWidth = this.width / 2;
|
||||
final int sliderSpacing = 4;
|
||||
|
||||
final int x1 = halfWidth - sliderWidth - sliderSpacing;
|
||||
final int x2 = halfWidth;
|
||||
final int x3 = halfWidth + sliderWidth + sliderSpacing;
|
||||
final int y3 = this.height - 65 + 2 * (sliderHeight + sliderSpacing);
|
||||
|
||||
this.buttonList.add(new IrisButton(x1 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
FontConfig.enableCustomFont ? I18n.format("options.angelica.fontconfig.disable_custom_font") :
|
||||
I18n.format("options.angelica.fontconfig.enable_custom_font"), this::toggleCustomFont));
|
||||
this.buttonList.add(new IrisButton(this.width / 2 - 80, this.height - 20 - 7, 160, 20,
|
||||
this.buttonList.add(new IrisButton(x2 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
I18n.format("options.angelica.fontconfig.reset_values"), this::resetValues));
|
||||
this.buttonList.add(new IrisButton(this.width / 2 - 81 + 165, this.height - 20 - 7, 162, 20,
|
||||
this.buttonList.add(new IrisButton(x3 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
I18n.format("gui.done"), button -> this.onClose()));
|
||||
|
||||
// might refactor later, feeling too lazy at the moment
|
||||
this.buttonList.add(new SliderClone( this.width / 2 - 60 - 186, this.height - 40 - 11, 120, 20,
|
||||
optFontQuality, FontConfig.customFontQuality, this::setFontQuality,
|
||||
value -> I18n.format("options.angelica.fontconfig.font_quality", String.format("%2.0f", value)),
|
||||
"options.angelica.fontconfig.font_quality.tooltip"));
|
||||
this.buttonList.add(new SliderClone( this.width / 2 - 60 - 186, this.height - 60 - 15, 120, 20,
|
||||
optShadowOffset, FontConfig.fontShadowOffset, value -> FontConfig.fontShadowOffset = value,
|
||||
value -> I18n.format("options.angelica.fontconfig.shadow_offset", String.format("x%3.2f", value)),
|
||||
"options.angelica.fontconfig.shadow_offset.tooltip"));
|
||||
this.buttonList.add(new SliderClone( this.width / 2 - 60 - 62, this.height - 40 - 11, 120, 20,
|
||||
optGlyphAspect, FontConfig.glyphAspect, value -> FontConfig.glyphAspect = value,
|
||||
value -> I18n.format("options.angelica.fontconfig.glyph_aspect", String.format("%3.2f", value)),
|
||||
"options.angelica.fontconfig.glyph_aspect.tooltip"));
|
||||
this.buttonList.add(new SliderClone( this.width / 2 - 60 - 62, this.height - 60 - 15, 120, 20,
|
||||
optGlyphScale, FontConfig.glyphScale, value -> FontConfig.glyphScale = value,
|
||||
value -> I18n.format("options.angelica.fontconfig.glyph_scale", String.format("x%3.2f", value)),
|
||||
"options.angelica.fontconfig.glyph_scale.tooltip"));
|
||||
this.buttonList.add(new SliderClone( this.width / 2 - 60 + 62, this.height - 40 - 11, 120, 20,
|
||||
optWhitespaceScale, FontConfig.whitespaceScale, value -> FontConfig.whitespaceScale = value,
|
||||
value -> I18n.format("options.angelica.fontconfig.whitespace_scale", String.format("x%3.2f", value)),
|
||||
"options.angelica.fontconfig.whitespace_scale.tooltip"));
|
||||
this.buttonList.add(new SliderClone( this.width / 2 - 60 + 62, this.height - 60 - 15, 120, 20,
|
||||
optGlyphSpacing, FontConfig.glyphSpacing, value -> FontConfig.glyphSpacing = value,
|
||||
value -> I18n.format("options.angelica.fontconfig.glyph_spacing", String.format("%3.2f", value)),
|
||||
"options.angelica.fontconfig.glyph_spacing.tooltip"));
|
||||
this.buttonList.add(new SliderClone( this.width / 2 - 60 + 186, this.height - 40 - 11, 120, 20,
|
||||
optFontAAStrength, FontConfig.fontAAStrength, value -> FontConfig.fontAAStrength = value.intValue(),
|
||||
value -> I18n.format("options.angelica.fontconfig.font_aa_strength", String.format("%.0f", value)),
|
||||
"options.angelica.fontconfig.font_aa_strength.tooltip"));
|
||||
this.buttonList.add(new SliderClone( this.width / 2 - 60 + 186, this.height - 60 - 15, 120, 20,
|
||||
optFontAAMode, FontConfig.fontAAMode, value -> FontConfig.fontAAMode = value.intValue(),
|
||||
this::fontAAModeFormat,
|
||||
"options.angelica.fontconfig.aamode.tooltip"));
|
||||
backButton = new IrisButton(halfWidth - 20 - 224, this.height - 89 - 10, 40, 20,
|
||||
"<", button -> this.switchPage(-1));
|
||||
fwdButton = new IrisButton(halfWidth - 20 + 224, this.height - 89 - 10, 40, 20,
|
||||
">", button -> this.switchPage(1));
|
||||
this.buttonList.add(fwdButton);
|
||||
this.buttonList.add(backButton);
|
||||
|
||||
initSliders();
|
||||
}
|
||||
|
||||
private void initSliders() {
|
||||
ArrayList<SliderClone> sliders = new ArrayList<>();
|
||||
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontQuality)
|
||||
.initialValue(FontConfig.customFontQuality)
|
||||
.setter(this::setFontQuality)
|
||||
.langKey("options.angelica.fontconfig.font_quality")
|
||||
.formatString("%2.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optShadowOffset)
|
||||
.initialValue(FontConfig.fontShadowOffset)
|
||||
.setter(value -> FontConfig.fontShadowOffset = value)
|
||||
.langKey("options.angelica.fontconfig.shadow_offset")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphAspect)
|
||||
.initialValue(FontConfig.glyphAspect)
|
||||
.setter(value -> FontConfig.glyphAspect = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_aspect")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphScale)
|
||||
.initialValue(FontConfig.glyphScale)
|
||||
.setter(value -> FontConfig.glyphScale = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontAAStrength)
|
||||
.initialValue(FontConfig.fontAAStrength)
|
||||
.setter(value -> FontConfig.fontAAStrength = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.font_aa_strength")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontAAMode)
|
||||
.initialValue(FontConfig.fontAAMode)
|
||||
.setter(value -> FontConfig.fontAAMode = value.intValue())
|
||||
.formatter(this::fontAAModeFormat)
|
||||
.langKey("options.angelica.fontconfig.aamode")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optWhitespaceScale)
|
||||
.initialValue(FontConfig.whitespaceScale)
|
||||
.setter(value -> FontConfig.whitespaceScale = value)
|
||||
.langKey("options.angelica.fontconfig.whitespace_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphSpacing)
|
||||
.initialValue(FontConfig.glyphSpacing)
|
||||
.setter(value -> FontConfig.glyphSpacing = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_spacing")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optShadowCopies)
|
||||
.initialValue(FontConfig.shadowCopies)
|
||||
.setter(value -> FontConfig.shadowCopies = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.shadow_copies")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optBoldCopies)
|
||||
.initialValue(FontConfig.boldCopies)
|
||||
.setter(value -> FontConfig.boldCopies = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.bold_copies")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optCustomFontScale)
|
||||
.initialValue(FontConfig.customFontScale)
|
||||
.setter(value -> FontConfig.customFontScale = value)
|
||||
.langKey("options.angelica.fontconfig.custom_font_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
|
||||
final int halfWidth = this.width / 2;
|
||||
final int sliderSpacing = 4;
|
||||
|
||||
final int x1 = halfWidth - sliderWidth - sliderSpacing;
|
||||
final int x2 = halfWidth;
|
||||
final int x3 = halfWidth + sliderWidth + sliderSpacing;
|
||||
final int y1 = this.height - 65;
|
||||
final int y2 = this.height - 65 + sliderHeight + sliderSpacing;
|
||||
|
||||
this.sliderPages = sliders.size() / 6;
|
||||
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.sliderPages);
|
||||
|
||||
this.backButton.enabled = (this.displayedSliderPage != 0);
|
||||
this.fwdButton.enabled = (this.displayedSliderPage != this.sliderPages);
|
||||
|
||||
final int start = 6 * this.displayedSliderPage;
|
||||
final int end = Math.min(6 * (this.displayedSliderPage + 1), sliders.size());
|
||||
List<SliderClone> s = sliders.subList(start, end);
|
||||
try {
|
||||
s.get(0).setCenterPosition(x1, y1);
|
||||
s.get(1).setCenterPosition(x1, y2);
|
||||
s.get(2).setCenterPosition(x2, y1);
|
||||
s.get(3).setCenterPosition(x2, y2);
|
||||
s.get(4).setCenterPosition(x3, y1);
|
||||
s.get(5).setCenterPosition(x3, y2);
|
||||
} catch (IndexOutOfBoundsException ignored) {
|
||||
// we're on the last page and it has less than 6 sliders to display
|
||||
}
|
||||
this.buttonList.addAll(s);
|
||||
}
|
||||
|
||||
private void onClose() {
|
||||
@@ -141,9 +287,10 @@ public class FontConfigScreen extends GuiScreen {
|
||||
} catch (ConfigException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
currentPrimaryFontName = null;
|
||||
currentFallbackFontName = null;
|
||||
selectedPrimaryFontListPos = -1;
|
||||
selectedFallbackFontListPos = -1;
|
||||
FontStrategist.customFontInUse = false;
|
||||
super.buttonList.clear();
|
||||
this.initButtons();
|
||||
}
|
||||
@@ -156,6 +303,12 @@ public class FontConfigScreen extends GuiScreen {
|
||||
I18n.format("options.angelica.fontconfig.enable_custom_font");
|
||||
}
|
||||
|
||||
private void switchPage(int offset) {
|
||||
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.sliderPages);
|
||||
this.buttonList.removeIf(guiButton -> guiButton instanceof SliderClone);
|
||||
this.initSliders();
|
||||
}
|
||||
|
||||
private int qualityLast = FontConfig.customFontQuality;
|
||||
|
||||
private void setFontQuality(float quality) {
|
||||
@@ -179,10 +332,23 @@ public class FontConfigScreen extends GuiScreen {
|
||||
private long lastStillTime = 0;
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float delta) {
|
||||
super.drawDefaultBackground();
|
||||
fontList.drawScreen(mouseX, mouseY, delta);
|
||||
searchBox.drawTextBox();
|
||||
// I bet you thought this was drawn inside the search box, not on top of it.
|
||||
drawBackground(0);
|
||||
|
||||
this.fontList.drawScreen(mouseX, mouseY, delta);
|
||||
if (availableFonts.length == 0) {
|
||||
if (this.noFontsWarning == null) {
|
||||
this.noFontsWarning = this.fontRendererObj.listFormattedStringToWidth(
|
||||
I18n.format("options.angelica.fontconfig.no_fonts_warning"), this.width / 2
|
||||
);
|
||||
}
|
||||
int y = 80;
|
||||
for (String s : this.noFontsWarning) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width / 3, y, 0xFFFFFF);
|
||||
y += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
this.searchBox.drawTextBox();
|
||||
if (!this.searchBox.isFocused() && this.searchBox.getText().isEmpty()) {
|
||||
this.drawCenteredString(this.fontRendererObj, this.searchPrompt,
|
||||
this.searchBox.xPosition + this.fontRendererObj.getStringWidth(this.searchPrompt) / 2 + 4,
|
||||
@@ -191,6 +357,34 @@ public class FontConfigScreen extends GuiScreen {
|
||||
drawCenteredString(this.fontRendererObj, this.title, (int) (this.width * 0.5), 8, 0xFFFFFF);
|
||||
drawCenteredString(this.fontRendererObj, I18n.format("options.angelica.fontconfig.currentfonts",
|
||||
FontConfig.customFontNamePrimary, FontConfig.customFontNameFallback), (int) (this.width * 0.5), this.height - 92, 0xFFFFFF);
|
||||
|
||||
if (this.testAreaInfo == null) {
|
||||
this.testAreaInfo = this.fontRendererObj.listFormattedStringToWidth(
|
||||
I18n.format("options.angelica.fontconfig.testareainfo"), this.width / 4
|
||||
);
|
||||
}
|
||||
int cumulativeY = 73;
|
||||
for (String s : this.testAreaInfo) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
|
||||
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
cumulativeY += 4;
|
||||
this.testArea.yPosition = cumulativeY;
|
||||
this.testArea.drawTextBox();
|
||||
if (!this.testArea.isFocused() && this.testArea.getText().isEmpty()) {
|
||||
this.drawCenteredString(this.fontRendererObj, this.testAreaPrompt,
|
||||
this.testArea.xPosition + this.fontRendererObj.getStringWidth(this.testAreaPrompt) / 2 + 4,
|
||||
this.testArea.yPosition + this.testArea.height / 2 - 4, 0xFFFFFF);
|
||||
}
|
||||
List<String> testText = this.fontRendererObj.listFormattedStringToWidth(
|
||||
this.testArea.getText().replace('&', '§'), this.width / 4
|
||||
);
|
||||
cumulativeY += this.testArea.height + 4;
|
||||
for (String s : testText) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
|
||||
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
|
||||
super.drawScreen(mouseX, mouseY, delta);
|
||||
|
||||
for (GuiButton guiButton : buttonList) {
|
||||
@@ -235,10 +429,18 @@ public class FontConfigScreen extends GuiScreen {
|
||||
if (this.searchBox.isFocused()) {
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.searchBox.setFocused(false);
|
||||
return;
|
||||
}
|
||||
this.searchBox.textboxKeyTyped(typedChar, keyCode);
|
||||
this.displayedFonts = filterFonts(this.searchBox.getText().toLowerCase());
|
||||
}
|
||||
if (this.testArea.isFocused()) {
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.testArea.setFocused(false);
|
||||
return;
|
||||
}
|
||||
this.testArea.textboxKeyTyped(typedChar, keyCode);
|
||||
}
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.onClose();
|
||||
}
|
||||
@@ -248,12 +450,14 @@ public class FontConfigScreen extends GuiScreen {
|
||||
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
super.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.searchBox.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.testArea.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.fontList.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScreen() {
|
||||
this.searchBox.updateCursorCounter();
|
||||
this.testArea.updateCursorCounter();
|
||||
}
|
||||
|
||||
// this could be more efficient, but it's good enough as is
|
||||
@@ -281,7 +485,7 @@ public class FontConfigScreen extends GuiScreen {
|
||||
class FontList extends GuiSlot {
|
||||
|
||||
public FontList() {
|
||||
super(FontConfigScreen.this.mc, FontConfigScreen.this.width, FontConfigScreen.this.height, 52, FontConfigScreen.this.height - 100, 18);
|
||||
super(FontConfigScreen.this.mc, FontConfigScreen.this.width * 2 / 3, FontConfigScreen.this.height, 52, FontConfigScreen.this.height - 102, 18);
|
||||
}
|
||||
|
||||
private void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
@@ -328,7 +532,7 @@ public class FontConfigScreen extends GuiScreen {
|
||||
|
||||
@Override
|
||||
protected int getScrollBarX() {
|
||||
return this.width * 11 / 12 - 5;
|
||||
return this.width * 15 / 16 - 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -340,7 +544,7 @@ public class FontConfigScreen extends GuiScreen {
|
||||
protected void elementClicked(int index, boolean doubleClicked, int mouseX, int mouseY) {}
|
||||
|
||||
protected void drawBackground() {
|
||||
drawDefaultBackground();
|
||||
// do nothing, the font list doesn't occupy the entire screen
|
||||
}
|
||||
|
||||
protected void drawSlot(int index, int x, int y, int p_148126_4_, Tessellator tessellator, int p_148126_6_, int p_148126_7_) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -43,6 +46,53 @@ public class SliderClone extends GuiButton {
|
||||
this.tooltipKey = tooltipKey;
|
||||
}
|
||||
|
||||
public static SliderCloneBuilder builder() {
|
||||
return new SliderCloneBuilder();
|
||||
}
|
||||
|
||||
public void setCenterPosition(int x, int y) {
|
||||
this.xPosition = x - this.width / 2;
|
||||
this.yPosition = y - this.height / 2;
|
||||
}
|
||||
|
||||
@Accessors(fluent = true) @Setter
|
||||
public static class SliderCloneBuilder {
|
||||
private int x = 0, y = 0; // center position, not a corner
|
||||
private int width = 144, height = 20;
|
||||
private Option option;
|
||||
private float initialValue;
|
||||
private Consumer<Float> setter;
|
||||
private String formatString = "%3.2f";
|
||||
private String langKey;
|
||||
|
||||
// overrides: if null, these get set to default values
|
||||
private String tooltipKey;
|
||||
private Function<Float, String> formatter;
|
||||
|
||||
public SliderClone build() {
|
||||
if (this.formatter == null) {
|
||||
this.formatter = value -> I18n.format(this.langKey, String.format(this.formatString, value));
|
||||
}
|
||||
|
||||
if (this.tooltipKey == null) {
|
||||
this.tooltipKey = this.langKey + ".tooltip";
|
||||
}
|
||||
|
||||
return new SliderClone(
|
||||
this.x - this.width / 2,
|
||||
this.y - this.height / 2,
|
||||
this.width,
|
||||
this.height,
|
||||
this.option,
|
||||
this.initialValue,
|
||||
this.setter,
|
||||
this.formatter,
|
||||
this.tooltipKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
|
||||
* this button.
|
||||
@@ -73,7 +123,7 @@ public class SliderClone extends GuiButton {
|
||||
this.displayString = displayStringFormatter.apply(f);
|
||||
}
|
||||
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20);
|
||||
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.gtnewhorizons.angelica.commands;
|
||||
|
||||
// Debug commands adapted from Beddium by Ven and FalsePattern
|
||||
|
||||
import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.ICommand;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AngelicaCommand extends CommandBase {
|
||||
|
||||
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "help");
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "angelica";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandUsage(ICommandSender sender) {
|
||||
return "/angelica <wireframe|fog|minimap|help>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequiredPermissionLevel() {
|
||||
return 0; // Available to all players (client-side only)
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> addTabCompletionOptions(ICommandSender sender, String[] args) {
|
||||
if (args.length == 1) {
|
||||
return getListOfStringsMatchingLastWord(args, SUBCOMMANDS.toArray(new String[0]));
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCommand(ICommandSender sender, String[] args) {
|
||||
if (args.length == 0) {
|
||||
sendHelp(sender);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "wireframe" -> handleWireframe(sender);
|
||||
case "fog" -> handleFog(sender);
|
||||
case "minimap" -> handleMinimap(sender);
|
||||
default -> sendHelp(sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWireframe(ICommandSender sender) {
|
||||
CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE = !CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE;
|
||||
final String state = CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE ? "ON" : "OFF";
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Wireframe mode: " + state));
|
||||
}
|
||||
|
||||
private void handleFog(ICommandSender sender) {
|
||||
CeleritasDebugScreenHandler.showFogDebug = !CeleritasDebugScreenHandler.showFogDebug;
|
||||
final String f3State = CeleritasDebugScreenHandler.showFogDebug ? "ON" : "OFF";
|
||||
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Fog debug (F3): " + f3State));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " " + CeleritasDebugScreenHandler.getFogDebugString()));
|
||||
}
|
||||
|
||||
private void handleMinimap(ICommandSender sender) {
|
||||
ChunkDebugMinimap.toggle();
|
||||
final String state = ChunkDebugMinimap.isEnabled() ? "ON" : "OFF";
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
|
||||
}
|
||||
|
||||
private void sendHelp(ICommandSender sender) {
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] Debug Commands:"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica wireframe" + EnumChatFormatting.WHITE + " - Toggle wireframe rendering"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica fog" + EnumChatFormatting.WHITE + " - Toggle fog debug on F3"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
if (o instanceof ICommand cmd) {
|
||||
return this.getCommandName().compareTo(cmd.getCommandName());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.ARBDebugOutput;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
/**
|
||||
* Checks for known problematic driver configurations and applies workarounds. Adapted from Embeddium/Sodium's LateDriverScanner.
|
||||
*
|
||||
* <p>The NVIDIA workaround can be disabled via: {@code -Dangelica.disableNvidiaWorkaround=true}
|
||||
*/
|
||||
public class DriverCompatabilityCheck {
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
|
||||
/**
|
||||
* Should be called after GL context is created and GLStateManager is initialized.
|
||||
*/
|
||||
public static void checkDriverCompatibility() {
|
||||
final String vendor = GL11.glGetString(GL11.GL_VENDOR);
|
||||
final String renderer = GL11.glGetString(GL11.GL_RENDERER);
|
||||
final String version = GL11.glGetString(GL11.GL_VERSION);
|
||||
|
||||
LOGGER.info("OpenGL Vendor: {}", vendor);
|
||||
LOGGER.info("OpenGL Renderer: {}", renderer);
|
||||
LOGGER.info("OpenGL Version: {}", version);
|
||||
|
||||
if (vendor != null && vendor.contains("NVIDIA")) {
|
||||
if (DISABLE_NVIDIA_WORKAROUND) {
|
||||
LOGGER.info("NVIDIA workaround disabled via -Dangelica.disableNvidiaWorkaround=true");
|
||||
} else {
|
||||
applyNvidiaWorkaround();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/godotengine/godot/issues/33969#issuecomment-917846774
|
||||
* Attempt to force the driver to disable its submission thread by enabling synchronous debug output.
|
||||
* This usually suppresses bad behavior from NVIDIA's "Threaded Optimizations" feature.
|
||||
*/
|
||||
private static void applyNvidiaWorkaround() {
|
||||
if (!GLStateManager.capabilities.GL_ARB_debug_output) {
|
||||
LOGGER.warn("Cannot apply NVIDIA workaround: ARB_debug_output not available");
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Enabling workaround for NVIDIA threaded optimizations");
|
||||
GL11.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.gtnewhorizons.neid.mixins.interfaces.IExtendedBlockStorageMixin;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.NibbleArray;
|
||||
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
||||
import ru.fewizz.idextender.Hooks;
|
||||
|
||||
public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
public boolean hasSky;
|
||||
@@ -39,12 +38,6 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BMetaArray(), 0, block16BMetaArray, 0, block16BMetaArray.length);
|
||||
}
|
||||
}
|
||||
else if (ModStatus.isOldNEIDLoaded){
|
||||
final short[] blockLSBArray = Hooks.get(this);
|
||||
System.arraycopy(Hooks.get(storage), 0, blockLSBArray, 0, blockLSBArray.length);
|
||||
// getBlockMSBArray is nuked in asm version
|
||||
arrayLen = blockLSBArray.length;
|
||||
}
|
||||
else {
|
||||
final byte[] blockLSBArray = this.getBlockLSBArray();
|
||||
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
|
||||
|
||||
@@ -11,16 +11,8 @@ import org.apache.logging.log4j.Logger;
|
||||
public class ModStatus {
|
||||
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
|
||||
|
||||
public static BackhandReflectionCompat backhandCompat;
|
||||
/**
|
||||
* Mixin Version
|
||||
*/
|
||||
public static boolean isNEIDLoaded;
|
||||
/**
|
||||
* ASM Version
|
||||
*/
|
||||
public static boolean isOldNEIDLoaded;
|
||||
public static boolean isBetterCrashesLoaded;
|
||||
public static boolean isNEIDLoaded;
|
||||
public static boolean isNEIDMetadataExtended;
|
||||
public static boolean isLotrLoaded;
|
||||
public static boolean isChunkAPILoaded;
|
||||
@@ -34,15 +26,13 @@ public class ModStatus {
|
||||
public static boolean isBaublesLoaded;
|
||||
public static boolean isFluidLoggedLoaded;
|
||||
|
||||
public static void preInit(){
|
||||
isBackhandLoaded = Loader.isModLoaded("backhand");
|
||||
if(isBackhandLoaded) {
|
||||
public static void preInit() {
|
||||
if (Loader.isModLoaded("backhand")) {
|
||||
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
|
||||
}
|
||||
|
||||
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
|
||||
isNEIDLoaded = Loader.isModLoaded("neid");
|
||||
isOldNEIDLoaded = Loader.isModLoaded("notenoughIDs");
|
||||
isLotrLoaded = Loader.isModLoaded("lotr");
|
||||
isChunkAPILoaded = Loader.isModLoaded("chunkapi");
|
||||
isEIDBiomeLoaded = Loader.isModLoaded("endlessids_biome");
|
||||
|
||||
+8
-4
@@ -55,8 +55,10 @@ public class BackhandReflectionCompat {
|
||||
if (isLoaded && renderOffhandReturn != null) {
|
||||
try {
|
||||
renderOffhandReturn.invokeExact(partialTicks);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Failed to invoke Backhand renderOffhandReturn", e);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
@@ -66,8 +68,10 @@ public class BackhandReflectionCompat {
|
||||
if (isLoaded && getOffhandItem != null) {
|
||||
try {
|
||||
return (ItemStack) getOffhandItem.invokeExact(player);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Failed to invoke Backhand getOffhandItem", e);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ public class BetterCrashesCompat implements StateManager.IResettable {
|
||||
@Override
|
||||
public void resetState() {
|
||||
LOGGER.info("Reloading SodiumRenderer");
|
||||
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).reload();
|
||||
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
|
||||
LOGGER.info("Resetting GLStateManager");
|
||||
GLStateManager.reset();
|
||||
}
|
||||
|
||||
+8
-4
@@ -75,8 +75,10 @@ public class HoloInventoryReflectionCompat {
|
||||
} else {
|
||||
renderEventMethod.invokeExact(event);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", e);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
@@ -85,8 +87,10 @@ public class HoloInventoryReflectionCompat {
|
||||
if (!isLoaded) return;
|
||||
try {
|
||||
angelicaOverrideSetter.invokeExact(value);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Failed to set HoloInventory angelicaOverride", e);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.compat.iris;
|
||||
|
||||
/**
|
||||
* Interface implemented by BiomeGenBase via mixin to cache biome category.
|
||||
* This avoids recomputing the category every frame for shader uniforms.
|
||||
*/
|
||||
public interface BiomeCategoryCache {
|
||||
/**
|
||||
* Gets the cached biome category ordinal, or -1 if not yet computed.
|
||||
*/
|
||||
int iris$getCachedCategory();
|
||||
|
||||
/**
|
||||
* Sets the cached biome category ordinal.
|
||||
*/
|
||||
void iris$setCachedCategory(int category);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.gtnewhorizons.angelica.compat.iris;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import net.coderbot.iris.parsing.BiomeCategories;
|
||||
import net.minecraft.world.biome.BiomeGenBase;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Detects and categorizes biomes from popular modded biome mods using cached class checks.
|
||||
* Currently supports: BiomesOPlenty (BOP), Realistic World Gen (RWG), lotr (b36.15)
|
||||
*/
|
||||
public class ModdedBiomeDetector {
|
||||
private static boolean initialized = false;
|
||||
|
||||
// BiomesOPlenty (BOP) cache - dimension-specific base classes
|
||||
// BOP 1.7.10 hierarchy: BOPBiome -> BOPEndBiome/BOPNetherBiome/BOPOverworldBiome -> specific biomes
|
||||
private static Class<?> bopEndBiomeClass = null;
|
||||
private static Class<?> bopNetherBiomeClass = null;
|
||||
private static Class<?> bopOceanBiomeClass = null;
|
||||
|
||||
// Realistic World Gen (RWG) cache - base biome classes that extend BiomeGenBase
|
||||
// LinkedHashMap maintains insertion order for deterministic behavior
|
||||
private static final Map<Class<?>, BiomeCategories> rwgClassMap = new LinkedHashMap<>();
|
||||
|
||||
private static Class<?> lotrBiomeClass = null;
|
||||
|
||||
private static synchronized void initialize() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
initializeBOP();
|
||||
initializeRWG();
|
||||
initializeLOTR();
|
||||
}
|
||||
|
||||
private static void initializeBOP() {
|
||||
try {
|
||||
bopEndBiomeClass = Class.forName("biomesoplenty.common.biome.BOPEndBiome");
|
||||
bopNetherBiomeClass = Class.forName("biomesoplenty.common.biome.BOPNetherBiome");
|
||||
bopOceanBiomeClass = Class.forName("biomesoplenty.common.biome.BOPOceanBiome");
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// BOP not installed
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeRWG() {
|
||||
// RWG adds actual BiomeGenBase subclasses in rwg.biomes.base package
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeOcean", BiomeCategories.OCEAN);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeRiver", BiomeCategories.RIVER);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomePlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdPlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotPlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotDesert", BiomeCategories.DESERT);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowDesert", BiomeCategories.DESERT); // Cold desert, but still desert
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeJungle", BiomeCategories.JUNGLE);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeTropicalIsland", BiomeCategories.JUNGLE); // Tropical islands are jungle-like
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeRedwood", BiomeCategories.FOREST); // Redwood/Sequoia forests
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeTemperateForest", BiomeCategories.FOREST);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdForest", BiomeCategories.TAIGA); // Cold forests are taiga-like
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotForest", BiomeCategories.FOREST);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowForest", BiomeCategories.TAIGA); // Snow forests are taiga
|
||||
}
|
||||
|
||||
private static void initializeLOTR() {
|
||||
if (ModStatus.isLotrLoaded) {
|
||||
try {
|
||||
lotrBiomeClass = Class.forName("lotr.common.world.biome.LOTRBiome");
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// lotr not installed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryAddRWGClass(String className, BiomeCategories category) {
|
||||
try {
|
||||
final Class<?> clazz = Class.forName(className);
|
||||
rwgClassMap.put(clazz, category);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// Class not available, RWG not installed or different version
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect the biome category for modded biomes.
|
||||
* Returns null if no modded biome detection applies.
|
||||
*/
|
||||
public static BiomeCategories detectModdedBiome(BiomeGenBase biome) {
|
||||
if (!initialized) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
// BiomesOPlenty detection - dimension-specific classes give us clear categories
|
||||
if (bopEndBiomeClass != null && bopEndBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.THE_END;
|
||||
}
|
||||
if (bopNetherBiomeClass != null && bopNetherBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.NETHER;
|
||||
}
|
||||
if (bopOceanBiomeClass != null && bopOceanBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.OCEAN;
|
||||
}
|
||||
// For other BOP overworld biomes (e.g., Alps, Arctic, BambooForest),
|
||||
// fall through to name/property-based detection
|
||||
|
||||
// Realistic World Gen detection
|
||||
if (biome.getBiomeClass().getName().contains("rwg.biomes")) {
|
||||
for (Map.Entry<Class<?>, BiomeCategories> entry : rwgClassMap.entrySet()) {
|
||||
if (entry.getKey().isInstance(biome)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lotr
|
||||
if (lotrBiomeClass!= null && lotrBiomeClass.isInstance(biome)) {
|
||||
return detectLOTRBiome(biome);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static BiomeCategories detectLOTRBiome(BiomeGenBase biome) {
|
||||
return switch (biome.biomeName) {
|
||||
case "river", "farHaradJungleLake", "lake" -> BiomeCategories.RIVER;
|
||||
case "rohan", "rivendell", "rhunIsland", "rhunLandHills", "rhunLandSteppe", "rhunLand", "andrast",
|
||||
"blackrootVale", "lamedon", "imlothMelui", "towerHills", "dorwinion", "dale", "shireMoors", "wold",
|
||||
"anduinVale", "rhun", "lebennin", "pertorogwaith", "breeland", "island", "pinnathGelin",
|
||||
"minhiriath", "pelennor", "adornland", "nanCurunir", "ithilienWasteland", "fangornClearing",
|
||||
"barrowDowns", "dorEnErnilHills", "dorEnErnil", "nurn", "wilderland", "eastBight", "lindon",
|
||||
"eregion", "enedwaith", "meneltarma", "anduinHills", "eriador", "angle", "loneLandsHills",
|
||||
"loneLands", "pelargir", "rohanUrukHighlands", "celebrant", "gondor", "shire", "nurnen", "farHarad" ->
|
||||
BiomeCategories.PLAINS;
|
||||
case "mistyMountains", "windMountains", "farHaradVolcano", "redMountains", "haradMountains",
|
||||
"angmarMountains", "blueMountains", "greyMountains", "whiteMountains", "mordorMountains" ->
|
||||
BiomeCategories.MOUNTAIN;
|
||||
case "shireWoodlands", "rhunIslandForest", "rhunRedForest", "pukel", "lossarnach", "gulfHaradForest",
|
||||
"nearHaradFertileForest", "dolGuldur", "rhunForest", "tauredainClearing", "umbarForest",
|
||||
"farHaradForest", "chetwood", "woodlandRealmHills", "mirkwoodNorth", "whiteDowns", "ithilienHills",
|
||||
"gondorWoodlands", "rohanWoodlands", "fangornWasteland", "mirkwoodMountains", "lindonWoodlands",
|
||||
"lothlorienEdge", "erynVorn", "oldForest", "fangorn", "dunland", "ithilien", "mirkwoodCorrupted",
|
||||
"woodlandRealm", "trollshaws", "lothlorien", "farHaradKanuka" -> BiomeCategories.FOREST;
|
||||
case "mordor", "lastDesert", "harnedor", "easternDesolation", "morgulVale", "gorgoroth", "udun",
|
||||
"nearHaradRedDesert", "nearHaradSemiDesert", "lostladen",
|
||||
"nearHaradHills", "nearHarad", "nanUngol", "dagorlad", "brownLands" -> BiomeCategories.DESERT;
|
||||
case "ironHills", "rivendellHills", "windMountainsFoothills", "lamedonHills", "redMountainsFoothills",
|
||||
"tolfalas", "dorwinionHills", "whiteMountainsFoothills", "blueMountainsFoothills",
|
||||
"greyMountainsFoothills", "mistyMountainsFoothills", "erebor", "angmar", "eriadorDowns",
|
||||
"ettenmoors", "emynMuil" -> BiomeCategories.EXTREME_HILLS;
|
||||
case "deadMarshes", "shireMarshes", "farHaradMangrove", "farHaradSwamp", "swanfleet", "nindalf",
|
||||
"longMarshes", "entwashMouth", "nurnMarshes", "gladdenFields", "midgewater", "anduinMouth" ->
|
||||
BiomeCategories.SWAMP;
|
||||
case "harondor", "nearHaradOasis", "nearHaradRiverbank", "farHaradBushlandHills", "farHaradBushland",
|
||||
"gulfHarad", "nearHaradFertile", "umbarHills", "umbar", "farHaradAridHills", "farHaradArid" ->
|
||||
BiomeCategories.SAVANNA;
|
||||
case "ocean" -> BiomeCategories.OCEAN;
|
||||
case "forodwaith", "forodwaithGlacier", "tundra", "forodwaithMountains" -> BiomeCategories.ICY;
|
||||
case "lindonCoast", "beachWhite", "farHaradCoast", "forodwaithCoast", "beachGravel", "beach" ->
|
||||
BiomeCategories.BEACH;
|
||||
case "coldfells", "wilderlandNorth", "taiga" -> BiomeCategories.TAIGA;
|
||||
case "farHaradJungle", "halfTrollForest", "farHaradJungleMountains",
|
||||
"farHaradCloudForest", "farHaradJungleEdge" -> BiomeCategories.JUNGLE;
|
||||
case "utumno" -> BiomeCategories.NONE;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -9,35 +9,65 @@ import net.minecraft.util.MathHelper;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector4f;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* Singleton camera instance that tracks the current camera position and orientation.
|
||||
*/
|
||||
@Getter
|
||||
public class Camera {
|
||||
final Vector3d pos = new Vector3d();
|
||||
final BlockPos blockPos = new BlockPos();
|
||||
float pitch;
|
||||
float yaw;
|
||||
EntityLivingBase entity;
|
||||
boolean thirdPerson;
|
||||
final float partialTicks;
|
||||
public static final Camera INSTANCE = new Camera();
|
||||
|
||||
public Camera(EntityLivingBase entity, float partialTicks) {
|
||||
private final Vector3d pos = new Vector3d();
|
||||
private final Vector3d entityPos = new Vector3d();
|
||||
private final BlockPos blockPos = new BlockPos();
|
||||
private final Vector3f offset = new Vector3f();
|
||||
private final Matrix4f inverseModelView = new Matrix4f();
|
||||
|
||||
private float pitch;
|
||||
private float yaw;
|
||||
private EntityLivingBase entity;
|
||||
private boolean thirdPerson;
|
||||
private float partialTicks;
|
||||
|
||||
private Camera() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the camera position and orientation based on the current entity and partial ticks.
|
||||
*
|
||||
* @param entity The entity to track (usually the render view entity)
|
||||
* @param partialTicks The partial tick time for interpolation
|
||||
*/
|
||||
public void update(EntityLivingBase entity, float partialTicks) {
|
||||
this.partialTicks = partialTicks;
|
||||
final Vector4f offset = new Vector4f(); // third person offset
|
||||
final Matrix4f inverseModelView = new Matrix4f(RenderingState.INSTANCE.getModelViewMatrix()).invert();
|
||||
inverseModelView.transform(offset);
|
||||
|
||||
final double camX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks + offset.x;
|
||||
final double camY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks + offset.y;
|
||||
final double camZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks + offset.z;
|
||||
this.entity = entity;
|
||||
|
||||
// Entity position (interpolated, at eye level)
|
||||
final double entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks;
|
||||
final double entityY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks;
|
||||
final double entityZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks;
|
||||
final double eyeHeight = entity.getEyeHeight();
|
||||
|
||||
entityPos.set(entityX, entityY + eyeHeight, entityZ);
|
||||
|
||||
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView > 0;
|
||||
|
||||
final double camX, camY, camZ;
|
||||
offset.set(0, 0, 0);
|
||||
|
||||
if (thirdPerson) {
|
||||
// Third person: use inverse modelview to find camera offset
|
||||
inverseModelView.set(RenderingState.INSTANCE.getModelViewMatrix()).invert();
|
||||
inverseModelView.transformPosition(offset);
|
||||
}
|
||||
camX = entityX + offset.x;
|
||||
camY = entityY + eyeHeight + offset.y;
|
||||
camZ = entityZ + offset.z;
|
||||
|
||||
pos.set(camX, camY, camZ);
|
||||
blockPos.set(MathHelper.floor_double(camX), MathHelper.floor_double(camY), MathHelper.floor_double(camZ));
|
||||
pitch = entity.cameraPitch;
|
||||
yaw = entity.rotationYaw;
|
||||
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView == 1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.PlayerControllerMP;
|
||||
|
||||
public final class GameModeUtil {
|
||||
private GameModeUtil() {
|
||||
}
|
||||
|
||||
public static boolean isSpectator() {
|
||||
final PlayerControllerMP controller = Minecraft.getMinecraft().playerController;
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
return controller.currentGameType.getID() == 3;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
@@ -9,14 +10,10 @@ public enum InteractionHand {
|
||||
MAIN_HAND,
|
||||
OFF_HAND;
|
||||
|
||||
InteractionHand() {
|
||||
}
|
||||
|
||||
public ItemStack getItemInHand(EntityPlayer player){
|
||||
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND){ // off hand (requires backhand)
|
||||
return ModStatus.backhandCompat.getOffhandItem(player);
|
||||
}
|
||||
else { // main hand
|
||||
public ItemStack getItemInHand(EntityPlayer player) {
|
||||
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND) { // off hand (requires backhand)
|
||||
return BackhandReflectionCompat.getOffhandItem(player);
|
||||
} else { // main hand
|
||||
return player.getHeldItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,17 @@ public class MatrixStack {
|
||||
public void pop() {
|
||||
this.matrixStack.removeLast();
|
||||
}
|
||||
public boolean clear() {
|
||||
public boolean isEmpty() {
|
||||
return this.matrixStack.size() == 1;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
while (this.matrixStack.size() > 1) {
|
||||
this.matrixStack.removeLast();
|
||||
}
|
||||
loadIdentity();
|
||||
}
|
||||
|
||||
public void translate(double d, double e, double f) {
|
||||
final Entry lv = (Entry)this.matrixStack.getLast();
|
||||
lv.model.translate((float)d, (float)e, (float)f);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import me.jellysquid.mods.sodium.client.SodiumClientMod;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
@@ -147,7 +147,7 @@ public abstract class RenderPhase {
|
||||
FOG = new Fog("fog", () -> {
|
||||
// Unclear what this should do
|
||||
// BackgroundRenderer.setFogBlack(), also levelFogColor()
|
||||
SodiumClientMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
AngelicaMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
GLStateManager.enableFog();
|
||||
}, GLStateManager::disableFog);
|
||||
BLACK_FOG = new Fog("black_fog", () -> {
|
||||
@@ -156,7 +156,7 @@ public abstract class RenderPhase {
|
||||
}, () -> {
|
||||
// Unclear what this should do
|
||||
// BackgroundRenderer.setFogBlack(), also levelFogColor()
|
||||
SodiumClientMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
AngelicaMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
GLStateManager.disableFog();
|
||||
});
|
||||
MAIN_TARGET = new Target("main_target", () -> {
|
||||
@@ -164,7 +164,7 @@ public abstract class RenderPhase {
|
||||
});
|
||||
OUTLINE_TARGET = new Target("outline_target", () -> {
|
||||
// TODO: Sodium
|
||||
SodiumClientMod.LOGGER.debug("NOT enabling the entity outline framebuffer");
|
||||
AngelicaMod.LOGGER.debug("NOT enabling the entity outline framebuffer");
|
||||
//MinecraftClient.getInstance().worldRenderer.getEntityOutlinesFramebuffer().beginWrite(false);
|
||||
}, () -> {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
@@ -172,7 +172,7 @@ public abstract class RenderPhase {
|
||||
TRANSLUCENT_TARGET = new Target("translucent_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
// TODO: Sodium
|
||||
SodiumClientMod.LOGGER.debug("NOT enabling the translucent framebuffer");
|
||||
AngelicaMod.LOGGER.debug("NOT enabling the translucent framebuffer");
|
||||
// MinecraftClient.getInstance().worldRenderer.getTranslucentFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ public abstract class RenderPhase {
|
||||
super("lightmap", () -> {
|
||||
if (lightmap) {
|
||||
// TODO: Sodium - LightmapTextureManager
|
||||
SodiumClientMod.LOGGER.debug("Lightmap - enable (not implemented)");
|
||||
AngelicaMod.LOGGER.debug("Lightmap - enable (not implemented)");
|
||||
// throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().enable();
|
||||
|
||||
@@ -354,7 +354,7 @@ public abstract class RenderPhase {
|
||||
}, () -> {
|
||||
if (lightmap) {
|
||||
// TODO: Sodium - LightmapTextureManager
|
||||
SodiumClientMod.LOGGER.debug("Lightmap - disable (not implemented)");
|
||||
AngelicaMod.LOGGER.debug("Lightmap - disable (not implemented)");
|
||||
// throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().disable();
|
||||
}
|
||||
|
||||
@@ -4,10 +4,21 @@ import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", filename = "angelica-modules")
|
||||
public class AngelicaConfig {
|
||||
@Config.Comment("Enable Sodium rendering")
|
||||
@Config.Comment("Enable Celeritas terrain rendering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableSodium;
|
||||
public static boolean enableCeleritas;
|
||||
|
||||
@Config.Comment("Enable multi-threaded chunk building for improved performance [Requires Celeritas]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresWorldRestart
|
||||
public static boolean enableThreadedChunkBuilding;
|
||||
|
||||
@Config.Comment("Number of chunk builder threads. 0 = auto-detect, -1 = use single-threaded fallback")
|
||||
@Config.DefaultInt(0)
|
||||
@Config.RangeInt(min = -1, max = 16)
|
||||
@Config.RequiresWorldRestart
|
||||
public static int chunkBuilderThreadCount;
|
||||
|
||||
@Config.Comment("Enable NotFine Options")
|
||||
@Config.DefaultBoolean(false)
|
||||
@@ -17,11 +28,6 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean enableReesesSodiumOptions;
|
||||
|
||||
@Config.Comment("Enable Sodium fluid rendering")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableSodiumFluidRendering;
|
||||
|
||||
@Config.Comment("Inject BakedModel rendering into some vanilla blocks")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
@@ -32,7 +38,7 @@ public class AngelicaConfig {
|
||||
@Config.Ignore()
|
||||
public static boolean enableTestBlocks;
|
||||
|
||||
@Config.Comment("Enable Iris Shaders [Requires Sodium]")
|
||||
@Config.Comment("Enable Iris Shaders [Requires Celeritas]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableIris;
|
||||
@@ -42,10 +48,20 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableMCPatcherForgeFeatures;
|
||||
|
||||
@Config.Comment("Replace some vanilla render paths with more optimized versions. Disable if you encounter mixin conflicts.")
|
||||
@Config.Comment("Replace main menu panorama with modern equivalent.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVBO;
|
||||
public static boolean enablePanoramaBlurShader;
|
||||
|
||||
@Config.Comment("Replace cloud renderer with a VBO version.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVBOClouds;
|
||||
|
||||
@Config.Comment("Uses cached attributes for VBO rendering, resulting in less CPU overhead. Disable if you notice any graphical issues.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVAO;
|
||||
|
||||
@Config.Comment("Enable NotFine features")
|
||||
@Config.DefaultBoolean(true)
|
||||
@@ -77,12 +93,16 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableHudCachingEventTransformer;
|
||||
|
||||
@Config.Comment("Enable HUD Caching at runtime. Requires enableHudCaching to be on at startup. [Experimental]")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean hudCachingActive;
|
||||
|
||||
@Config.Comment("The amount of frames to wait before updating the HUD elements. [Experimental]")
|
||||
@Config.DefaultInt(20)
|
||||
@Config.RangeInt(min = 1, max = 60)
|
||||
public static int hudCachingFPS = 20;
|
||||
|
||||
@Config.Comment("Batch drawScreen fonts [Experimental]")
|
||||
@Config.Comment("Batch drawScreen fonts")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableFontRenderer;
|
||||
@@ -97,7 +117,7 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeWorldUpdateLight;
|
||||
|
||||
@Config.Comment("Speedup Animations. [From Hodgepodge]")
|
||||
@Config.Comment("Optimize Texture Animations. [From Hodgepodge]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean speedupAnimations;
|
||||
@@ -167,7 +187,72 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean removeUnicodeEvenScaling;
|
||||
|
||||
@Config.Comment({"Block corners and edges between chunks might have \"cracks\" (various lines/dots) in them.",
|
||||
"While using \"Compact Vertex Format\" makes the situation even worse.",
|
||||
"This option fixes it.",
|
||||
"Requires texture reloading (F3 + T) after changing this option to take effect"})
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean fixBlockCrack;
|
||||
|
||||
@Config.Comment({
|
||||
"The \"epsilon\" value for the fixBlockCrack option. ",
|
||||
"Set this a bit higher if you can still see lines/dots between solid blocks in dark areas.",
|
||||
"May cause intense flickering (z-fighting) between blocks if the value is too high"
|
||||
})
|
||||
@Config.RangeDouble(min = 0, max = 0.005)
|
||||
@Config.DefaultDouble(0.001)
|
||||
public static double blockCrackFixEpsilon;
|
||||
|
||||
@Config.Comment("Block classes that have bugs when rendering with the fixBlockCrack can be put here to avoid manipulating them")
|
||||
@Config.DefaultStringList({
|
||||
"net.minecraft.block.BlockCauldron",
|
||||
"net.minecraft.block.BlockStairs"
|
||||
})
|
||||
public static String[] blockCrackFixBlacklist;
|
||||
|
||||
@Config.Comment({"Block classes that have render pass other than 0 but still need to be manipulated.",
|
||||
"Add a block class here if you see flickering (z-fighting) with fixBlockCrack enabled"
|
||||
})
|
||||
@Config.DefaultStringList({
|
||||
"gregtech.common.blocks.BlockOres",
|
||||
"gregtech.common.blocks.GTBlockOre",
|
||||
"shukaro.artifice.block.world.BlockOre",
|
||||
"bartworks.system.material.BWMetaGeneratedOres",
|
||||
"gtPlusPlus.core.block.base.BlockBaseOre",
|
||||
"org.pfaa.geologica.block.BrokenGeoBlock",
|
||||
"org.pfaa.geologica.block.BrickGeoBlock",
|
||||
})
|
||||
public static String[] blockCrackFixRenderPassWhitelist__;
|
||||
|
||||
@Config.Comment("Register HardcodedCustomUniforms in Iris Shaders. May help with compatibility in certain shader packs")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableHardcodedCustomUniforms;
|
||||
|
||||
@Config.Comment("Define IS_IRIS in shader macros.")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean defineIsIris;
|
||||
|
||||
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean useVanillaChunkTracking;
|
||||
|
||||
@Config.Comment("Disables additional F3 information added by Angelica.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean disableF3Additions;
|
||||
|
||||
@Config.Comment("Replaces various FFP uploads with statically allocated VBO's.")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean replaceFFPUploads;
|
||||
|
||||
@Config.Comment("Pinned OpenGL version an integer (e.g. 46, 41, 33). 0 = auto-detect. [33, 46]. (Disable with disableGLVersionPinning=true)")
|
||||
@Config.DefaultInt(0)
|
||||
@Config.RangeInt(min = 0, max = 46)
|
||||
@Config.RequiresMcRestart
|
||||
public static int pinnedGLVersion;
|
||||
|
||||
@Config.Comment("Disable automatic GL version pinning. When true, always probes from highest on every launch.")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean disableGLVersionPinning;
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public class AngelicaGuiConfig extends SimpleGuiConfig {
|
||||
"Angelica",
|
||||
true,
|
||||
AngelicaConfig.class,
|
||||
CompatConfig.class,
|
||||
FontConfig.class
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", filename = "angelica-compat")
|
||||
@Config(modid = "angelica", filename = "angelica-compat", category = "compat")
|
||||
public class CompatConfig {
|
||||
|
||||
@Config.Comment("Apply fixes to the LOTR mod")
|
||||
@@ -44,4 +44,24 @@ public class CompatConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixSecurityCraft;
|
||||
|
||||
@Config.Comment("Apply tweaks to NTM:Space")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean tweakNTMSpace;
|
||||
|
||||
@Config.Comment("Apply fixes to NTM:Space")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixNTMSpace;
|
||||
|
||||
@Config.Comment("Disables rendering of the planet below the player at high altitudes [Requires tweakNTMSpace]")
|
||||
@Config.DefaultEnum("ShadersOnly")
|
||||
public static altitudePlanetRendererState NTMSpace_disableAltitudePlanetRenderer;
|
||||
public enum altitudePlanetRendererState {
|
||||
Always,
|
||||
ShadersOnly,
|
||||
Never
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,16 @@ public class FontConfig {
|
||||
@Config.RangeFloat(min = 0F, max = 2F)
|
||||
public static float fontShadowOffset;
|
||||
|
||||
@Config.Comment("The number of shadows to be drawn behind text at various offsets.")
|
||||
@Config.DefaultInt(1)
|
||||
@Config.RangeInt(min = 1, max = 8)
|
||||
public static int shadowCopies;
|
||||
|
||||
@Config.Comment("The number of bold copies to be drawn behind text at various offsets.")
|
||||
@Config.DefaultInt(2)
|
||||
@Config.RangeInt(min = 1, max = 8)
|
||||
public static int boldCopies;
|
||||
|
||||
@Config.Comment("Influences the aspect ratio of each glyph.")
|
||||
@Config.DefaultFloat(0F)
|
||||
@Config.RangeFloat(min = -1F, max = 1F)
|
||||
@@ -55,4 +65,16 @@ public class FontConfig {
|
||||
@Config.DefaultInt(7)
|
||||
@Config.RangeInt(min = 1, max = 24)
|
||||
public static int fontAAStrength;
|
||||
|
||||
@Config.Comment("Custom font scale multiplier, for bugfixes.")
|
||||
@Config.DefaultFloat(1.5F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float customFontScale;
|
||||
|
||||
@Config.Comment({"Mods can provide Angelica with a mapping of special characters to either get replaced with " +
|
||||
"different characters or rendered using the default Unicode font. Setting this to false disables the " +
|
||||
"replacement, which is useful in cases where custom fonts have direct support for these special characters. ",
|
||||
"See for additional details: https://github.com/GTNewHorizons/Angelica/issues/1239#issuecomment-3729877936"})
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean enableGlyphReplacements;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Adapated from: Beddium for usage in Angelica
|
||||
*
|
||||
* Copyright (C) 2025 Ven, FalsePattern
|
||||
* All Rights Reserved
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, only version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderSectionManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ChunkProviderClient;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.world.chunk.EmptyChunk;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Debug overlay that renders a minimap showing chunk loading and rendering state. Toggle via /angelica minimap
|
||||
*/
|
||||
public class ChunkDebugMinimap {
|
||||
private static final ChunkDebugMinimap INSTANCE = new ChunkDebugMinimap();
|
||||
@Getter
|
||||
private static volatile boolean enabled = false;
|
||||
|
||||
private static final int RANGE = 32; // Chunks in each direction from player
|
||||
|
||||
public static synchronized void toggle() {
|
||||
if (enabled) disable();
|
||||
else enable();
|
||||
}
|
||||
|
||||
public static synchronized void enable() {
|
||||
if (enabled) return;
|
||||
enabled = true;
|
||||
MinecraftForge.EVENT_BUS.register(INSTANCE);
|
||||
}
|
||||
|
||||
public static synchronized void disable() {
|
||||
if (!enabled) return;
|
||||
enabled = false;
|
||||
MinecraftForge.EVENT_BUS.unregister(INSTANCE);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRenderOverlay(RenderGameOverlayEvent.Text event) {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
if (mc.theWorld == null || mc.thePlayer == null) return;
|
||||
|
||||
final CeleritasWorldRenderer renderer;
|
||||
try {
|
||||
renderer = CeleritasWorldRenderer.getInstance();
|
||||
} catch (IllegalStateException e) {
|
||||
return; // Renderer not initialized
|
||||
}
|
||||
|
||||
final AngelicaRenderSectionManager manager = renderer.getRenderSectionManager();
|
||||
if (manager == null) return;
|
||||
|
||||
final float partialTicks = event.partialTicks;
|
||||
final double pX = mc.thePlayer.prevPosX + (mc.thePlayer.posX - mc.thePlayer.prevPosX) * partialTicks;
|
||||
final double pZ = mc.thePlayer.prevPosZ + (mc.thePlayer.posZ - mc.thePlayer.prevPosZ) * partialTicks;
|
||||
|
||||
final int playerChunkX = ((int) Math.floor(pX)) >> 4;
|
||||
final int playerChunkZ = ((int) Math.floor(pZ)) >> 4;
|
||||
|
||||
// Save GL state
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
// Setup rendering
|
||||
GLStateManager.glScaled(2, 2, 1);
|
||||
GLStateManager.glTranslated(32, 32, 0);
|
||||
|
||||
GLStateManager.disableLighting();
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawingQuads();
|
||||
|
||||
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
|
||||
|
||||
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
|
||||
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
|
||||
drawChunk(tess, x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
|
||||
}
|
||||
}
|
||||
|
||||
tess.draw();
|
||||
|
||||
// Restore GL state
|
||||
GLStateManager.enableLighting();
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
|
||||
private void drawChunk(Tessellator tess, int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
|
||||
boolean initialized = false;
|
||||
float r = 0, g = 0, b = 0;
|
||||
|
||||
// Check all 16 sections in the chunk column
|
||||
for (int y = 0; y < 16; y++) {
|
||||
if (manager.isSectionBuilt(chunkX, y, chunkZ) && !manager.isSectionVisuallyEmpty(chunkX, y, chunkZ)) {
|
||||
initialized = true;
|
||||
g = 1; // Green = built
|
||||
if (manager.isSectionVisible(chunkX, y, chunkZ)) {
|
||||
b = 1; // Cyan = visible
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
// Check if chunk is loaded
|
||||
final var chunk = chunkProvider.provideChunk(chunkX, chunkZ);
|
||||
if (chunk instanceof EmptyChunk) {
|
||||
r = 1; // Red = unloaded
|
||||
} else {
|
||||
b = 1; // Blue = loaded but not built
|
||||
}
|
||||
}
|
||||
|
||||
tess.setColorRGBA_F(r, g, b, 0.8f);
|
||||
|
||||
final int xStart = chunkX - playerChunkX;
|
||||
final int zStart = chunkZ - playerChunkZ;
|
||||
final int xEnd = xStart + 1;
|
||||
final int zEnd = zStart + 1;
|
||||
|
||||
tess.addVertex(xStart, zStart, 0);
|
||||
tess.addVertex(xStart, zEnd, 0);
|
||||
tess.addVertex(xEnd, zEnd, 0);
|
||||
tess.addVertex(xEnd, zStart, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@@ -10,31 +11,31 @@ import org.lwjgl.opengl.GL11;
|
||||
public class F3Direction {
|
||||
public static void renderWorldDirectionsEvent(Minecraft mc, RenderGameOverlayEvent.Pre event) {
|
||||
if (mc.gameSettings.showDebugInfo && mc.gameSettings.thirdPersonView == 0) {
|
||||
ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int width = scaledresolution.getScaledWidth();
|
||||
int height = scaledresolution.getScaledHeight();
|
||||
final ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
final int width = scaledresolution.getScaledWidth();
|
||||
final int height = scaledresolution.getScaledHeight();
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
GL11.glTranslatef((float) (width / 2), (float) (height / 2), -90);
|
||||
GLStateManager.glTranslatef((float) (width / 2), (float) (height / 2), -90);
|
||||
|
||||
Entity entity = mc.renderViewEntity;
|
||||
GL11.glRotatef(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * event.partialTicks, -1.0F, 0.0F, 0.0F);
|
||||
GL11.glRotatef(entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * event.partialTicks, 0.0F, 1.0F, 0.0F);
|
||||
final Entity entity = mc.renderViewEntity;
|
||||
GLStateManager.glRotatef(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * event.partialTicks, -1.0F, 0.0F, 0.0F);
|
||||
GLStateManager.glRotatef(entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * event.partialTicks, 0.0F, 1.0F, 0.0F);
|
||||
|
||||
GL11.glScalef(-1.0F, -1.0F, -1.0F);
|
||||
GLStateManager.glScalef(-1.0F, -1.0F, -1.0F);
|
||||
|
||||
renderWorldDirections();
|
||||
|
||||
GL11.glPopMatrix();
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
}
|
||||
public static void renderWorldDirections() {
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDepthMask(false);
|
||||
Tessellator tessellator = Tessellator.instance;
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glDepthMask(false);
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
|
||||
GL11.glLineWidth(2.0F);
|
||||
GLStateManager.glLineWidth(2.0F);
|
||||
tessellator.startDrawing(GL11.GL_LINES);
|
||||
|
||||
//X
|
||||
@@ -54,8 +55,8 @@ public class F3Direction {
|
||||
|
||||
tessellator.draw();
|
||||
|
||||
GL11.glLineWidth(1.0F);
|
||||
GL11.glDepthMask(true);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glLineWidth(1.0F);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.enableTexture();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_ALPHA_TEST;
|
||||
import static org.lwjgl.opengl.GL11.GL_BLEND;
|
||||
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
|
||||
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
|
||||
import static org.lwjgl.opengl.GL11.GL_FLOAT;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.opengl.GL11.GL_VERTEX_ARRAY;
|
||||
import static org.lwjgl.opengl.GL11.glBlendFunc;
|
||||
import static org.lwjgl.opengl.GL11.glColor4f;
|
||||
import static org.lwjgl.opengl.GL11.glDisable;
|
||||
import static org.lwjgl.opengl.GL11.glDisableClientState;
|
||||
import static org.lwjgl.opengl.GL11.glDrawArrays;
|
||||
import static org.lwjgl.opengl.GL11.glEnable;
|
||||
import static org.lwjgl.opengl.GL11.glEnableClientState;
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL15.glBufferData;
|
||||
import static org.lwjgl.opengl.GL15.glGenBuffers;
|
||||
import static org.lwjgl.opengl.GL20.glDisableVertexAttribArray;
|
||||
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1f;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
||||
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.*;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
|
||||
import java.nio.FloatBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizons.angelica.glsm.GLStateManager.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL11.GL_FLOAT;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
import static org.lwjgl.opengl.GL15.glBufferData;
|
||||
import static org.lwjgl.opengl.GL15.glGenBuffers;
|
||||
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1f;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
||||
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
|
||||
|
||||
public abstract class F3Graph {
|
||||
private static final int NUM_SAMPLES = 240;
|
||||
@@ -63,6 +52,7 @@ public abstract class F3Graph {
|
||||
private int uHeadIdx;
|
||||
private int uSamples;
|
||||
private int vertBuf;
|
||||
private int vao;
|
||||
private final ResourceLocation texture;
|
||||
private final float pxPerNs;
|
||||
private final boolean left;
|
||||
@@ -117,8 +107,8 @@ public abstract class F3Graph {
|
||||
"shaders/debug_graph.frag.glsl");
|
||||
shader.use();
|
||||
|
||||
// Register attributes
|
||||
aPos = shader.getAttribLocation("pos");
|
||||
// Attribute location is declared in shader via layout(location = 0)
|
||||
aPos = 0;
|
||||
|
||||
// Register uniforms
|
||||
uFBWidth = shader.getUniformLocation("fbWidth");
|
||||
@@ -129,7 +119,9 @@ public abstract class F3Graph {
|
||||
final int uPxPerNs = shader.getUniformLocation("pxPerNs");
|
||||
final int uLeft = shader.getUniformLocation("left");
|
||||
|
||||
// Load vertex buffer
|
||||
vao = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
vertBuf = glGenBuffers();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
|
||||
try (final MemoryStack stack = stackPush()) {
|
||||
@@ -146,7 +138,13 @@ public abstract class F3Graph {
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
// Configure vertex attribute in the VAO
|
||||
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
|
||||
glEnableVertexAttribArray(aPos);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
@@ -186,21 +184,21 @@ public abstract class F3Graph {
|
||||
|
||||
// Setup GL state
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_ALPHA_TEST);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Draw background
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
|
||||
tess.startDrawingQuads();
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
tess.addVertex(getVertX(sr, i), getVertY(sr, i), 0);
|
||||
}
|
||||
tess.draw();
|
||||
glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.enableTexture();
|
||||
|
||||
|
||||
// Draw samples
|
||||
@@ -213,21 +211,16 @@ public abstract class F3Graph {
|
||||
glUniform1i(uHeadIdx, samplesHead);
|
||||
glUniform1(uSamples, sampleBuf);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
|
||||
glEnableVertexAttribArray(aPos);
|
||||
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
// Left side graphs are inverted, so temporarily disable culling
|
||||
if (!left) glDisable(GL_CULL_FACE);
|
||||
if (!left) GLStateManager.disableCull();
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
|
||||
GLStateManager.glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
|
||||
|
||||
if (!left) glEnable(GL_CULL_FACE);
|
||||
if (!left) GLStateManager.enableCull();
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableVertexAttribArray(aPos);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
ShaderProgram.clear();
|
||||
|
||||
@@ -242,9 +235,9 @@ public abstract class F3Graph {
|
||||
tess.draw();
|
||||
|
||||
// Reset GL state
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.disableBlend();
|
||||
|
||||
// Draw text
|
||||
// Ensure running counters are reset on first sample
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Calculates the appropriate tick mode for a dynamic light source based on distance from camera and position relative to camera direction.
|
||||
*/
|
||||
public final class AdaptiveTickCalculator {
|
||||
|
||||
// Distance thresholds in blocks (configurable via settings)
|
||||
private static int slowDistance = 32; // ~2 chunks
|
||||
private static int slowerDistance = 64; // ~4 chunks
|
||||
private static int backgroundDistance = 128; // ~8 chunks
|
||||
|
||||
// Squared thresholds for efficient distance comparison
|
||||
private static int slowDistanceSq = slowDistance * slowDistance;
|
||||
private static int slowerDistanceSq = slowerDistance * slowerDistance;
|
||||
private static int backgroundDistanceSq = backgroundDistance * backgroundDistance;
|
||||
|
||||
private AdaptiveTickCalculator() {}
|
||||
|
||||
/**
|
||||
* Calculate the appropriate tick mode for a light source.
|
||||
*/
|
||||
public static AdaptiveTickMode calculate(@NotNull IDynamicLightSource source, double cameraX, double cameraY, double cameraZ, double lookDirX, double lookDirZ) {
|
||||
double dx = source.angelica$getDynamicLightX() - cameraX;
|
||||
double dy = source.angelica$getDynamicLightY() - cameraY;
|
||||
double dz = source.angelica$getDynamicLightZ() - cameraZ;
|
||||
|
||||
double distSq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
// Check if behind camera (dot product of direction to source with look direction)
|
||||
// Only use X and Z for horizontal check (more stable as player looks up/down)
|
||||
double dot = dx * lookDirX + dz * lookDirZ;
|
||||
boolean behindCamera = dot < 0;
|
||||
|
||||
// Behind camera always gets BACKGROUND mode
|
||||
if (behindCamera) {
|
||||
return AdaptiveTickMode.BACKGROUND;
|
||||
}
|
||||
|
||||
// Distance-based mode selection
|
||||
if (distSq > backgroundDistanceSq) {
|
||||
return AdaptiveTickMode.BACKGROUND;
|
||||
}
|
||||
if (distSq > slowerDistanceSq) {
|
||||
return AdaptiveTickMode.SLOWER;
|
||||
}
|
||||
if (distSq > slowDistanceSq) {
|
||||
return AdaptiveTickMode.SLOW;
|
||||
}
|
||||
|
||||
return AdaptiveTickMode.REAL_TIME;
|
||||
}
|
||||
|
||||
public static void setSlowDistance(int distance) {
|
||||
slowDistance = distance;
|
||||
slowDistanceSq = distance * distance;
|
||||
}
|
||||
|
||||
public static void setSlowerDistance(int distance) {
|
||||
slowerDistance = distance;
|
||||
slowerDistanceSq = distance * distance;
|
||||
}
|
||||
|
||||
public static void setBackgroundDistance(int distance) {
|
||||
backgroundDistance = distance;
|
||||
backgroundDistanceSq = distance * distance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
/**
|
||||
* Defines tick modes for adaptive dynamic light updates. Distant or off-screen light sources update less frequently to save CPU.
|
||||
*/
|
||||
public enum AdaptiveTickMode {
|
||||
/** Updates every tick - for nearby, visible light sources */
|
||||
REAL_TIME(1),
|
||||
/** Updates every 5 ticks - for light sources at moderate distance */
|
||||
SLOW(5),
|
||||
/** Updates every 10 ticks - for light sources at far distance */
|
||||
SLOWER(10),
|
||||
/** Updates every 20 ticks - for light sources behind the camera or very far */
|
||||
BACKGROUND(20);
|
||||
|
||||
private final int delay;
|
||||
|
||||
AdaptiveTickMode(int delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public int getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public boolean shouldTickThisFrame(int worldTick, int sourceHash) {
|
||||
if (delay <= 1) {
|
||||
return true;
|
||||
}
|
||||
return (worldTick % delay) == Math.abs(sourceHash % delay);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Manages deferred chunk rebuilds for dynamic lights with optional frustum culling. Instead of scheduling chunk rebuilds immediately
|
||||
* when light sources move, this tracks pending rebuilds and only schedules visible chunks.
|
||||
*/
|
||||
public class ChunkRebuildManager {
|
||||
|
||||
/** Maps packed chunk position to ticks waiting */
|
||||
private final Long2IntOpenHashMap pendingRebuilds = new Long2IntOpenHashMap();
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
/** Maximum ticks a pending rebuild can wait before being forced through */
|
||||
private static int maxTicksWaiting = 100;
|
||||
|
||||
/** Reusable lists for processVisible - only used from render thread */
|
||||
private final LongArrayList candidates = new LongArrayList();
|
||||
private final LongArrayList toRebuild = new LongArrayList();
|
||||
private final LongArrayList toIncrement = new LongArrayList();
|
||||
|
||||
public static void setMaxTicksWaiting(int ticks) {
|
||||
maxTicksWaiting = Math.max(20, Math.min(ticks, 600)); // Clamp 1-30 seconds (20-600 ticks)
|
||||
}
|
||||
|
||||
public void requestRebuild(int x, int y, int z) {
|
||||
final long packed = CoordinatePacker.pack(x, y, z);
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Reset wait counter if already pending, otherwise add new entry
|
||||
pendingRebuilds.put(packed, 0);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void processVisible(Viewport viewport, @NotNull IDynamicLightWorldRenderer renderer) {
|
||||
candidates.clear();
|
||||
toRebuild.clear();
|
||||
toIncrement.clear();
|
||||
|
||||
// Phase 1: Collect all pending chunks with read lock (fast)
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
if (pendingRebuilds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// Collect candidates that might need processing
|
||||
final var iterator = pendingRebuilds.long2IntEntrySet().fastIterator();
|
||||
while (iterator.hasNext()) {
|
||||
final var entry = iterator.next();
|
||||
final long packed = entry.getLongKey();
|
||||
final int ticksWaiting = entry.getIntValue();
|
||||
|
||||
// Force process if no viewport or waited too long
|
||||
if (viewport == null || ticksWaiting >= maxTicksWaiting) {
|
||||
toRebuild.add(packed);
|
||||
} else {
|
||||
candidates.add(packed);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
// Phase 2: Check visibility
|
||||
for (int i = 0; i < candidates.size(); i++) {
|
||||
long packed = candidates.getLong(i);
|
||||
int x = CoordinatePacker.unpackX(packed);
|
||||
int y = CoordinatePacker.unpackY(packed);
|
||||
int z = CoordinatePacker.unpackZ(packed);
|
||||
|
||||
if (isChunkVisible(viewport, x, y, z)) {
|
||||
toRebuild.add(packed);
|
||||
} else {
|
||||
toIncrement.add(packed);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Update map with write lock (brief)
|
||||
if (!toRebuild.isEmpty() || !toIncrement.isEmpty()) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Remove chunks we're going to rebuild
|
||||
LongIterator rebuildIter = toRebuild.iterator();
|
||||
while (rebuildIter.hasNext()) {
|
||||
pendingRebuilds.remove(rebuildIter.nextLong());
|
||||
}
|
||||
LongIterator incIter = toIncrement.iterator();
|
||||
while (incIter.hasNext()) {
|
||||
long packed = incIter.nextLong();
|
||||
int current = pendingRebuilds.get(packed);
|
||||
if (pendingRebuilds.containsKey(packed)) {
|
||||
pendingRebuilds.put(packed, current + 1);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Schedule rebuilds outside of lock
|
||||
for (int i = 0; i < toRebuild.size(); i++) {
|
||||
final long packed = toRebuild.getLong(i);
|
||||
renderer.scheduleRebuildForChunk(
|
||||
CoordinatePacker.unpackX(packed),
|
||||
CoordinatePacker.unpackY(packed),
|
||||
CoordinatePacker.unpackZ(packed),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isChunkVisible(Viewport viewport, int chunkX, int chunkY, int chunkZ) {
|
||||
// Convert chunk section coords to block coords
|
||||
int blockX = chunkX << 4;
|
||||
int blockY = chunkY << 4;
|
||||
int blockZ = chunkZ << 4;
|
||||
|
||||
// Check if the 16x16x16 chunk section box is visible
|
||||
return viewport.isBoxVisible(
|
||||
blockX, blockY, blockZ,
|
||||
blockX + 16, blockY + 16, blockZ + 16
|
||||
);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
pendingRebuilds.clear();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public int getPendingCount() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return pendingRebuilds.size();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,13 @@ import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
|
||||
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
|
||||
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.PrimedEntityAccessor;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
|
||||
import mods.battlegear2.api.core.IBattlePlayer;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.block.Block;
|
||||
@@ -22,25 +24,37 @@ import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.item.ItemBlock;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class DynamicLights {
|
||||
private static DynamicLights instance;
|
||||
private static IDynamicLightWorldRenderer activeRenderer;
|
||||
public static DynamicLightsMode Mode = DynamicLightsMode.OFF;
|
||||
public static boolean ShaderForce = false;
|
||||
public static boolean FrustumCullingEnabled = true;
|
||||
public static boolean AdaptiveTickingEnabled = true;
|
||||
|
||||
public static final boolean configEnabled = AngelicaConfig.enableDynamicLights;
|
||||
|
||||
private static final double MAX_RADIUS = 7.75;
|
||||
private static final double MAX_RADIUS_SQUARED = MAX_RADIUS * MAX_RADIUS;
|
||||
private final Set<IDynamicLightSource> dynamicLightSources = new ObjectOpenHashSet<>();
|
||||
private final ReentrantReadWriteLock lightSourcesLock = new ReentrantReadWriteLock();
|
||||
private volatile int lightSourceCount = 0;
|
||||
private final ChunkRebuildManager chunkRebuildManager = new ChunkRebuildManager();
|
||||
private long lastUpdate = System.currentTimeMillis();
|
||||
private int lastUpdateCount = 0;
|
||||
|
||||
@@ -51,19 +65,31 @@ public class DynamicLights {
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return AngelicaConfig.enableDynamicLights &&
|
||||
return configEnabled &&
|
||||
Mode.isEnabled() &&
|
||||
// if shader force is enabled then true
|
||||
// if not, then true when shaders are not in use
|
||||
(ShaderForce || !IrisApi.getInstance().isShaderPackInUse());
|
||||
}
|
||||
|
||||
public boolean hasLightSources() {
|
||||
return lightSourceCount > 0;
|
||||
}
|
||||
|
||||
public static IDynamicLightWorldRenderer getActiveRenderer() {
|
||||
return activeRenderer;
|
||||
}
|
||||
|
||||
public static void setActiveRenderer(IDynamicLightWorldRenderer renderer) {
|
||||
activeRenderer = renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all light sources.
|
||||
*
|
||||
* @param renderer the renderer
|
||||
*/
|
||||
public void updateAll(@NotNull SodiumWorldRenderer renderer) {
|
||||
public void updateAll(@NotNull IDynamicLightWorldRenderer renderer) {
|
||||
if (!isEnabled())
|
||||
return;
|
||||
|
||||
@@ -72,8 +98,43 @@ public class DynamicLights {
|
||||
this.lastUpdate = now;
|
||||
this.lastUpdateCount = 0;
|
||||
|
||||
// Get camera info for adaptive ticking
|
||||
double cameraX = 0, cameraY = 0, cameraZ = 0;
|
||||
double lookDirX = 0, lookDirZ = 1;
|
||||
int worldTick = 0;
|
||||
|
||||
if (AdaptiveTickingEnabled) {
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
if (mc.theWorld != null && mc.renderViewEntity != null) {
|
||||
cameraX = mc.renderViewEntity.posX;
|
||||
cameraY = mc.renderViewEntity.posY + mc.renderViewEntity.getEyeHeight();
|
||||
cameraZ = mc.renderViewEntity.posZ;
|
||||
worldTick = (int) (mc.theWorld.getTotalWorldTime() & 0x7FFFFFFF);
|
||||
|
||||
// Calculate look direction from yaw (normalized horizontal direction)
|
||||
float yaw = mc.renderViewEntity.rotationYaw;
|
||||
double yawRad = Math.toRadians(yaw);
|
||||
lookDirX = -Math.sin(yawRad);
|
||||
lookDirZ = Math.cos(yawRad);
|
||||
}
|
||||
}
|
||||
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
for (var lightSource : this.dynamicLightSources) {
|
||||
// Entity type filter
|
||||
if (!EntityLightConfig.isEntityTypeEnabled(lightSource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Adaptive ticking filter
|
||||
if (AdaptiveTickingEnabled) {
|
||||
AdaptiveTickMode mode = AdaptiveTickCalculator.calculate(
|
||||
lightSource, cameraX, cameraY, cameraZ, lookDirX, lookDirZ);
|
||||
if (!mode.shouldTickThisFrame(worldTick, lightSource.hashCode())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (lightSource.angelica$updateDynamicLight(renderer))
|
||||
this.lastUpdateCount++;
|
||||
}
|
||||
@@ -87,7 +148,9 @@ public class DynamicLights {
|
||||
|
||||
public void addLightSource(IDynamicLightSource lightSource) {
|
||||
this.lightSourcesLock.writeLock().lock();
|
||||
this.dynamicLightSources.add(lightSource);
|
||||
if (this.dynamicLightSources.add(lightSource)) {
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
}
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
@@ -105,8 +168,9 @@ public class DynamicLights {
|
||||
it = dynamicLightSources.next();
|
||||
if (it.equals(lightSource)) {
|
||||
dynamicLightSources.remove();
|
||||
if (SodiumWorldRenderer.getInstance() != null)
|
||||
lightSource.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
if (activeRenderer != null)
|
||||
lightSource.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -128,10 +192,11 @@ public class DynamicLights {
|
||||
it = dynamicLightSources.next();
|
||||
if (filter.test(it)) {
|
||||
dynamicLightSources.remove();
|
||||
if (SodiumWorldRenderer.getInstance() != null) {
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
if (activeRenderer != null) {
|
||||
if (it.angelica$getLuminance() > 0)
|
||||
it.angelica$resetDynamicLight();
|
||||
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
|
||||
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -151,12 +216,13 @@ public class DynamicLights {
|
||||
while (dynamicLightSources.hasNext()) {
|
||||
it = dynamicLightSources.next();
|
||||
dynamicLightSources.remove();
|
||||
if (SodiumWorldRenderer.getInstance() != null) {
|
||||
if (activeRenderer != null) {
|
||||
if (it.angelica$getLuminance() > 0)
|
||||
it.angelica$resetDynamicLight();
|
||||
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
|
||||
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
}
|
||||
}
|
||||
this.lightSourceCount = 0;
|
||||
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
@@ -201,6 +267,20 @@ public class DynamicLights {
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets dynamic light level at an exact world position (for smooth per-vertex lighting).
|
||||
*/
|
||||
public double getDynamicLightLevel(double x, double y, double z) {
|
||||
double result = 0;
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
for (var lightSource : this.dynamicLightSources) {
|
||||
result = maxDynamicLightLevelExact(x, y, z, lightSource, result);
|
||||
}
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
public double getDynamicLightLevel(@NotNull BlockPos pos) {
|
||||
return this.getDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
@@ -227,6 +307,29 @@ public class DynamicLights {
|
||||
return currentLightLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates dynamic light level at an exact position (no block center offset).
|
||||
* Used for smooth per-vertex lighting.
|
||||
*/
|
||||
public static double maxDynamicLightLevelExact(double x, double y, double z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
|
||||
int luminance = lightSource.angelica$getLuminance();
|
||||
if (luminance > 0) {
|
||||
double dx = x - lightSource.angelica$getDynamicLightX();
|
||||
double dy = y - lightSource.angelica$getDynamicLightY();
|
||||
double dz = z - lightSource.angelica$getDynamicLightZ();
|
||||
|
||||
double distanceSquared = dx * dx + dy * dy + dz * dz;
|
||||
if (distanceSquared <= MAX_RADIUS_SQUARED) {
|
||||
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
|
||||
double lightLevel = multiplier * (double) luminance;
|
||||
if (lightLevel > currentLightLevel) {
|
||||
return lightLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentLightLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dynamic light level generated by the light source at the specified position.
|
||||
*
|
||||
@@ -243,6 +346,51 @@ public class DynamicLights {
|
||||
return this.getLightmapWithDynamicLight(this.getDynamicLightLevel(x, y, z), lightmap);
|
||||
}
|
||||
|
||||
public List<IDynamicLightSource> getSourcesForChunk(int chunkMinX, int chunkMinY, int chunkMinZ) {
|
||||
// Chunk center
|
||||
final double centerX = chunkMinX + 8.0;
|
||||
final double centerY = chunkMinY + 8.0;
|
||||
final double centerZ = chunkMinZ + 8.0;
|
||||
|
||||
// Max distance from chunk center to any corner is sqrt(3) * 8 ≈ 13.86
|
||||
// Add MAX_RADIUS to get the search radius (13.86 + 7.75)^2 ≈ 467
|
||||
final double searchRadiusSq = 470.0; // Slightly padded for safety
|
||||
|
||||
List<IDynamicLightSource> result = null;
|
||||
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
try {
|
||||
for (IDynamicLightSource source : this.dynamicLightSources) {
|
||||
double dx = source.angelica$getDynamicLightX() - centerX;
|
||||
double dy = source.angelica$getDynamicLightY() - centerY;
|
||||
double dz = source.angelica$getDynamicLightZ() - centerZ;
|
||||
double distSq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
if (distSq <= searchRadiusSq) {
|
||||
if (result == null) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
result.add(source);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
}
|
||||
|
||||
return result != null ? result : Collections.emptyList();
|
||||
}
|
||||
|
||||
public double getDynamicLightLevelFromSources(double x, double y, double z, List<IDynamicLightSource> sources) {
|
||||
if (sources == null || sources.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
double result = 0;
|
||||
for (int i = 0, size = sources.size(); i < size; i++) {
|
||||
result = maxDynamicLightLevelExact(x, y, z, sources.get(i), result);
|
||||
}
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lightmap with combined light levels.
|
||||
*
|
||||
@@ -273,7 +421,7 @@ public class DynamicLights {
|
||||
* @param renderer the renderer
|
||||
* @param chunkPos the chunk position
|
||||
*/
|
||||
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
|
||||
scheduleChunkRebuild(renderer, chunkPos.getX(), chunkPos.getY(), chunkPos.getZ());
|
||||
}
|
||||
|
||||
@@ -283,14 +431,37 @@ public class DynamicLights {
|
||||
* @param renderer the renderer
|
||||
* @param chunkPos the packed chunk position
|
||||
*/
|
||||
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, long chunkPos) {
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
|
||||
scheduleChunkRebuild(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, int x, int y, int z) {
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
|
||||
if (FrustumCullingEnabled && instance != null) {
|
||||
instance.chunkRebuildManager.requestRebuild(x, y, z);
|
||||
} else {
|
||||
renderer.scheduleRebuildForChunk(x, y, z, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuildForRemoval(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
|
||||
renderer.scheduleRebuildForChunk(x, y, z, false);
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuildForRemoval(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
|
||||
scheduleChunkRebuildForRemoval(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
|
||||
}
|
||||
|
||||
public void processChunkRebuilds(@Nullable Viewport viewport) {
|
||||
if (activeRenderer == null) {
|
||||
return;
|
||||
}
|
||||
chunkRebuildManager.processVisible(viewport, activeRenderer);
|
||||
}
|
||||
|
||||
public ChunkRebuildManager getChunkRebuildManager() {
|
||||
return chunkRebuildManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the tracked chunk sets.
|
||||
*
|
||||
@@ -331,10 +502,7 @@ public class DynamicLights {
|
||||
* @param submergedInWater {@code true} if the stack is submerged in water, else {@code false}
|
||||
* @return the luminance of the item
|
||||
*/
|
||||
public static int getLuminanceFromItemStack(@NotNull ItemStack stack, boolean submergedInWater) {
|
||||
// TODO only have certain items not glow in water?
|
||||
if (submergedInWater) return 0;
|
||||
|
||||
public static int getLuminanceFromItemStack(@NotNull ItemStack stack) {
|
||||
Item item = stack.getItem();
|
||||
if (item instanceof ItemBlock itemBlock) {
|
||||
Block block = itemBlock.field_150939_a;
|
||||
@@ -352,49 +520,53 @@ public class DynamicLights {
|
||||
|
||||
public static int getLuminanceFromEntity(@NotNull Entity entity) {
|
||||
|
||||
if (entity.fire > 0) return 15;
|
||||
if (entity.isBurning()) return 15;
|
||||
|
||||
boolean inWater = entity.isInsideOfMaterial(Material.water);
|
||||
|
||||
// TODO only have certain items not glow in water?
|
||||
if (inWater) return 0;
|
||||
|
||||
if (entity instanceof EntityItem item) {
|
||||
return getLuminanceFromItemStack(item.getEntityItem(), item.isInsideOfMaterial(Material.water));
|
||||
return getLuminanceFromItemStack(item.getEntityItem());
|
||||
}
|
||||
|
||||
int luminance = 0;
|
||||
if (entity instanceof EntityLivingBase living) {
|
||||
int luminance = 0;
|
||||
|
||||
boolean inWater = living.isInsideOfMaterial(Material.water);
|
||||
|
||||
// check equipment + hand for light (should work for all entities)
|
||||
ItemStack itemStack;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if ((itemStack = living.getEquipmentInSlot(i)) != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(itemStack, inWater));
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(itemStack));
|
||||
}
|
||||
}
|
||||
|
||||
if (ModStatus.isBattlegearLoaded &&
|
||||
living instanceof EntityPlayer player &&
|
||||
player instanceof IBattlePlayer battlePlayer &&
|
||||
battlePlayer.battlegear2$isBattlemode()
|
||||
) {
|
||||
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
|
||||
if (living instanceof EntityPlayer player) {
|
||||
if (ModStatus.isBattlegearLoaded &&
|
||||
player instanceof IBattlePlayer battlePlayer &&
|
||||
battlePlayer.battlegear2$isBattlemode()
|
||||
) {
|
||||
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ModStatus.isBackhandLoaded && living instanceof EntityPlayer player){
|
||||
ItemStack offhand = ModStatus.backhandCompat.getOffhandItem(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
|
||||
else if (ModStatus.isBackhandLoaded){
|
||||
ItemStack offhand = BackhandReflectionCompat.getOffhandItem(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ModStatus.isBaublesLoaded && living instanceof EntityPlayer player){
|
||||
var playerBaubles = PlayerHandler.getPlayerBaubles(player);
|
||||
if (playerBaubles != null){
|
||||
for (int i = 0; i < playerBaubles.getSizeInventory(); i++){
|
||||
var stack = playerBaubles.getStackInSlot(i);
|
||||
if (stack != null){
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(stack, inWater));
|
||||
if (ModStatus.isBaublesLoaded){
|
||||
var playerBaubles = PlayerHandler.getPlayerBaubles(player);
|
||||
if (playerBaubles != null){
|
||||
for (int i = 0; i < playerBaubles.getSizeInventory(); i++){
|
||||
var stack = playerBaubles.getStackInSlot(i);
|
||||
if (stack != null){
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,7 +576,10 @@ public class DynamicLights {
|
||||
}
|
||||
|
||||
// TODO: Creepers? TNT? Dusts?
|
||||
if (entity instanceof PrimedEntityAccessor primedEntity) {
|
||||
luminance = Math.max(luminance, primedEntity.angelica$getLuminance());
|
||||
}
|
||||
|
||||
return 0;
|
||||
return luminance;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -27,11 +26,13 @@ public interface IDynamicLightSource {
|
||||
double angelica$getDynamicLightZ();
|
||||
|
||||
/**
|
||||
* Returns the dynamic light source world.
|
||||
* Returns the dynamic light source world renderer.
|
||||
*
|
||||
* @return the world instance
|
||||
* @return the world renderer instance
|
||||
*/
|
||||
SodiumWorldRenderer angelica$getDynamicLightWorld();
|
||||
default IDynamicLightWorldRenderer angelica$getDynamicLightWorld() {
|
||||
return DynamicLights.getActiveRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the dynamic light is enabled or not.
|
||||
@@ -72,8 +73,9 @@ public interface IDynamicLightSource {
|
||||
* Executed at each tick.
|
||||
*/
|
||||
void angelica$dynamicLightTick();
|
||||
default void angelica$updateLights() {}
|
||||
|
||||
boolean angelica$updateDynamicLight(@NotNull SodiumWorldRenderer renderer);
|
||||
boolean angelica$updateDynamicLight(@NotNull IDynamicLightWorldRenderer renderer);
|
||||
|
||||
void angelica$scheduleTrackedChunksRebuild(@NotNull SodiumWorldRenderer renderer);
|
||||
void angelica$scheduleTrackedChunksRebuild(@NotNull IDynamicLightWorldRenderer renderer);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
/**
|
||||
* Common interface for world renderers that support dynamic lights chunk rebuilds.
|
||||
* Implemented by CeleritasWorldRenderer.
|
||||
*/
|
||||
public interface IDynamicLightWorldRenderer {
|
||||
/**
|
||||
* Schedules a chunk rebuild for the render section at the given chunk coordinates.
|
||||
*/
|
||||
void scheduleRebuildForChunk(int x, int y, int z, boolean important);
|
||||
|
||||
/**
|
||||
* Returns whether this renderer is currently active (has a world loaded).
|
||||
*/
|
||||
boolean isActive();
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.concurrent.cas.CasMap;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
|
||||
import cpw.mods.fml.common.registry.EntityRegistry;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class EntityLightConfig {
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/DynamicLights");
|
||||
|
||||
private static final Set<String> disabledClassNames = ConcurrentHashMap.newKeySet();
|
||||
private static final CasMap<Class<?>, Boolean> enabledCache = new CasMap<>();
|
||||
|
||||
private static File configFile;
|
||||
private static boolean initialized = false;
|
||||
|
||||
private EntityLightConfig() {}
|
||||
|
||||
public static void init(@NotNull File configDir) {
|
||||
if (initialized) return;
|
||||
|
||||
configFile = new File(configDir, "angelica-dynamiclights-entities.txt");
|
||||
load();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public static boolean isEntityTypeEnabled(@NotNull IDynamicLightSource source) {
|
||||
if (!initialized) return true;
|
||||
return isEntityTypeEnabled(source.getClass());
|
||||
}
|
||||
|
||||
public static boolean isEntityTypeEnabled(@NotNull Class<?> entityClass) {
|
||||
if (!initialized) return true;
|
||||
|
||||
Boolean cached = enabledCache.get(entityClass);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final boolean enabled = !disabledClassNames.contains(entityClass.getName());
|
||||
Boolean existing = enabledCache.putIfAbsent(entityClass, enabled);
|
||||
return existing != null ? existing : enabled;
|
||||
}
|
||||
|
||||
public static void setEntityTypeEnabled(@NotNull Class<?> entityClass, boolean enabled) {
|
||||
enabledCache.put(entityClass, enabled);
|
||||
|
||||
final String className = entityClass.getName();
|
||||
if (enabled) {
|
||||
disabledClassNames.remove(className);
|
||||
} else {
|
||||
disabledClassNames.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<EntityTypeEntry> getAllEntityTypes() {
|
||||
List<EntityTypeEntry> entries = new ArrayList<>();
|
||||
Set<String> seenClasses = new HashSet<>();
|
||||
|
||||
Map<String, Class<? extends Entity>> stringToClass = EntityList.stringToClassMapping;
|
||||
|
||||
for (Map.Entry<String, Class<? extends Entity>> entry : stringToClass.entrySet()) {
|
||||
String entityId = entry.getKey();
|
||||
Class<?> entityClass = entry.getValue();
|
||||
String className = entityClass.getName();
|
||||
|
||||
if (seenClasses.contains(className)) continue;
|
||||
seenClasses.add(className);
|
||||
|
||||
String modId = getModIdForEntity(entityClass, entityId);
|
||||
String displayName = getDisplayName(entityId, entityClass);
|
||||
boolean enabled = !disabledClassNames.contains(className);
|
||||
|
||||
entries.add(new EntityTypeEntry(displayName, className, modId, enabled));
|
||||
}
|
||||
|
||||
// Sort by mod ID, then by display name
|
||||
entries.sort((a, b) -> {
|
||||
int modCompare = a.getModId().compareToIgnoreCase(b.getModId());
|
||||
if (modCompare != 0) return modCompare;
|
||||
return a.getDisplayName().compareToIgnoreCase(b.getDisplayName());
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static String getModIdForEntity(Class<?> entityClass, String entityId) {
|
||||
// Check EntityRegistry for mod entity entries
|
||||
if (Entity.class.isAssignableFrom(entityClass)) {
|
||||
EntityRegistry.EntityRegistration registration = EntityRegistry.instance()
|
||||
.lookupModSpawn((Class<? extends Entity>) entityClass, false);
|
||||
if (registration != null && registration.getContainer() != null) {
|
||||
return registration.getContainer().getModId();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a vanilla entity (no dot in entity ID)
|
||||
if (!entityId.contains(".")) {
|
||||
return "minecraft";
|
||||
}
|
||||
|
||||
// Extract mod ID from entity ID (format: modid.EntityName)
|
||||
int dotIndex = entityId.indexOf('.');
|
||||
if (dotIndex > 0) {
|
||||
return entityId.substring(0, dotIndex);
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static String getDisplayName(String entityId, Class<?> entityClass) {
|
||||
// Use the simple class name, removing package
|
||||
String simpleName = entityClass.getSimpleName();
|
||||
if (simpleName.isEmpty()) {
|
||||
// Anonymous class, use entity ID
|
||||
return entityId;
|
||||
}
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
if (configFile == null || !configFile.exists()) return;
|
||||
|
||||
disabledClassNames.clear();
|
||||
enabledCache.clear();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(configFile))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty() || line.startsWith("#")) continue;
|
||||
|
||||
// Lines are entity class names that are DISABLED
|
||||
disabledClassNames.add(line);
|
||||
}
|
||||
LOGGER.info("Loaded {} disabled entity types from config", disabledClassNames.size());
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to load entity light config: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void save() {
|
||||
if (configFile == null) return;
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(configFile))) {
|
||||
writer.write("# Angelica Dynamic Lights - Disabled Entity Types");
|
||||
writer.newLine();
|
||||
writer.write("# Each line is a fully qualified class name of an entity type that is DISABLED.");
|
||||
writer.newLine();
|
||||
writer.write("# Remove a line to re-enable dynamic lighting for that entity type.");
|
||||
writer.newLine();
|
||||
writer.newLine();
|
||||
|
||||
List<String> sorted = new ArrayList<>(disabledClassNames);
|
||||
Collections.sort(sorted);
|
||||
|
||||
for (String className : sorted) {
|
||||
writer.write(className);
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
LOGGER.info("Saved {} disabled entity types to config", disabledClassNames.size());
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to save entity light config: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights.config;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.OptionStorage;
|
||||
|
||||
public class EntityLightConfigStorage implements OptionStorage<EntityLightConfigStorage> {
|
||||
|
||||
public static final EntityLightConfigStorage INSTANCE = new EntityLightConfigStorage();
|
||||
|
||||
private EntityLightConfigStorage() {}
|
||||
|
||||
@Override
|
||||
public EntityLightConfigStorage getData() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
EntityLightConfig.save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights.config;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class EntityTypeEntry {
|
||||
private final String displayName;
|
||||
private final String className;
|
||||
private final String modId;
|
||||
private boolean enabled;
|
||||
|
||||
public EntityTypeEntry(@NotNull String displayName, @NotNull String className, @NotNull String modId, boolean enabled) {
|
||||
this.displayName = displayName;
|
||||
this.className = className;
|
||||
this.modId = modId;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
public String getModId() {
|
||||
return modId;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return displayName + " (" + modId + ") [" + (enabled ? "ON" : "OFF") + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.embeddedt.embeddium.impl.render.chunk.fog.FogService;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkFogMode;
|
||||
|
||||
public class AngelicaFogService implements FogService {
|
||||
private final float[] fogColorArray = new float[4];
|
||||
|
||||
@Override
|
||||
public float getFogEnd() {
|
||||
return GLStateManager.getFogState().getEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFogStart() {
|
||||
return GLStateManager.getFogState().getStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFogDensity() {
|
||||
return GLStateManager.getFogState().getDensity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFogShapeIndex() {
|
||||
return 0; // 1.7.10 only has one fog shape
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFogCutoff() {
|
||||
return getFogEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getFogColor() {
|
||||
final var fogState = GLStateManager.getFogState();
|
||||
final var color = fogState.getFogColor();
|
||||
fogColorArray[0] = (float) color.x;
|
||||
fogColorArray[1] = (float) color.y;
|
||||
fogColorArray[2] = (float) color.z;
|
||||
fogColorArray[3] = fogState.getFogAlpha();
|
||||
return fogColorArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkFogMode getFogMode() {
|
||||
if (!GLStateManager.getFogMode().isEnabled()) {
|
||||
return ChunkFogMode.NONE;
|
||||
}
|
||||
return ChunkFogMode.fromGLMode(GLStateManager.getFogState().getFogMode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
|
||||
import com.mitchej123.glsm.GLStateManagerService;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class AngelicaGLStateManagerService implements GLStateManagerService {
|
||||
|
||||
private IntBuffer intArrayBuffer = BufferUtils.createIntBuffer(16);
|
||||
|
||||
private IntBuffer getIntArrayBuffer(int size) {
|
||||
if (intArrayBuffer.capacity() < size) {
|
||||
intArrayBuffer = BufferUtils.createIntBuffer(size);
|
||||
}
|
||||
intArrayBuffer.clear();
|
||||
return intArrayBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// ===================== QUERY OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGetInteger(int pname) {
|
||||
return GLStateManager.glGetInteger(pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetString(int pname) {
|
||||
return GL11.glGetString(pname);
|
||||
}
|
||||
|
||||
// ===================== FRAMEBUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenFramebuffers() {
|
||||
return GL30.glGenFramebuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(int framebuffer) {
|
||||
GL30.glDeleteFramebuffers(framebuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindFramebuffer(int target, int framebuffer) {
|
||||
GL30.glBindFramebuffer(target, framebuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCheckFramebufferStatus(int target) {
|
||||
return GL30.glCheckFramebufferStatus(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
|
||||
GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level);
|
||||
}
|
||||
|
||||
// ===================== SHADER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glCreateShader(int type) {
|
||||
return GL20.glCreateShader(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompileShader(int shader) {
|
||||
GL20.glCompileShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetShaderi(int shader, int pname) {
|
||||
return GL20.glGetShaderi(shader, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetShaderInfoLog(int shader, int maxLength) {
|
||||
return GL20.glGetShaderInfoLog(shader, maxLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteShader(int shader) {
|
||||
GL20.glDeleteShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCreateProgram() {
|
||||
return GL20.glCreateProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glAttachShader(int program, int shader) {
|
||||
GL20.glAttachShader(program, shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glLinkProgram(int program) {
|
||||
GL20.glLinkProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetProgrami(int program, int pname) {
|
||||
return GL20.glGetProgrami(program, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUseProgram(int program) {
|
||||
GLStateManager.glUseProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteProgram(int program) {
|
||||
GL20.glDeleteProgram(program);
|
||||
}
|
||||
|
||||
// ===================== UNIFORM/ATTRIBUTE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGetUniformLocation(int program, CharSequence name) {
|
||||
return GL20.glGetUniformLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int value) {
|
||||
GL20.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetAttribLocation(int program, CharSequence name) {
|
||||
return GL20.glGetAttribLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindAttribLocation(int program, int index, CharSequence name) {
|
||||
GL20.glBindAttribLocation(program, index, name);
|
||||
}
|
||||
|
||||
// ===================== VAO OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenVertexArrays() {
|
||||
return UniversalVAO.genVertexArrays();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindVertexArray(int array) {
|
||||
GLStateManager.glBindVertexArray(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteVertexArrays(int array) {
|
||||
GLStateManager.glDeleteVertexArrays(array);
|
||||
}
|
||||
|
||||
// ===================== BUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenBuffers() {
|
||||
return GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindBuffer(int target, int buffer) {
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(int buffer) {
|
||||
GLStateManager.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenTextures() {
|
||||
return GL11.glGenTextures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenTextures(int[] textures) {
|
||||
final IntBuffer buffer = getIntArrayBuffer(textures.length);
|
||||
GL11.glGenTextures(buffer);
|
||||
buffer.get(textures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(int texture) {
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(int[] textures) {
|
||||
final IntBuffer buffer = getIntArrayBuffer(textures.length);
|
||||
buffer.put(textures);
|
||||
buffer.flip();
|
||||
GLStateManager.glDeleteTextures(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetTexLevelParameteri(int target, int level, int pname) {
|
||||
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetTexLevelParameter(int target, int level, int pname) {
|
||||
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
GL11.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glPixelStorei(int pname, int param) {
|
||||
GLStateManager.glPixelStorei(pname, param);
|
||||
}
|
||||
|
||||
// ===================== STATE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void enableCullFace() {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCullFace() {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlend() {
|
||||
GLStateManager.enableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlend() {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableDepthTest() {
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableDepthTest() {
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthFunc(int func) {
|
||||
GLStateManager.glDepthFunc(func);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthMask(boolean flag) {
|
||||
GLStateManager.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
GLStateManager.glColorMask(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLStateManager.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClear(int mask) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
// ===================== MOJANG ADDITIONS =====================
|
||||
|
||||
@Override
|
||||
public void clear(int mask, boolean checkError) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTexture(int texture) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTexture() {
|
||||
return GLStateManager.getActiveTextureUnit() + GL13.GL_TEXTURE0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTextureAccessor() {
|
||||
return GLStateManager.getActiveTextureUnit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBoundTexture(int internalUnit) {
|
||||
return GLStateManager.getBoundTextureForServerState(internalUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveBoundTexture() {
|
||||
return GLStateManager.getBoundTextureForServerState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewportWidth() {
|
||||
return GLStateManager.getViewportState().width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewportHeight() {
|
||||
return GLStateManager.getViewportState().height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDepthStateMask() {
|
||||
return GLStateManager.getDepthState().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlendEnabled() {
|
||||
return GLStateManager.getBlendMode().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoundTexture(int unit, int texture) {
|
||||
final int currentUnit = GLStateManager.getActiveTextureUnit();
|
||||
if (currentUnit != unit) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
}
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
if (currentUnit != unit) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTextureBinding(int unit) {
|
||||
return GLStateManager.getBoundTextureForServerState(unit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.mitchej123.glsm.RenderSystemService;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class AngelicaRenderSystemService implements RenderSystemService {
|
||||
|
||||
// Shader state (Mojang-specific, tracked here)
|
||||
private final int[] shaderTextures = new int[16];
|
||||
private final float[] shaderColor = {1f, 1f, 1f, 1f};
|
||||
private final float[] shaderFogColor = {0f, 0f, 0f, 1f};
|
||||
private float shaderFogStart = 0f;
|
||||
private float shaderFogEnd = 0f;
|
||||
private int fogShape = 0;
|
||||
private float shaderLineWidth = 1f;
|
||||
private final Matrix4f projectionMatrix = new Matrix4f();
|
||||
private final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTexture(int texture) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
// ===================== STATE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void enableCullFace() {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCullFace() {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlend() {
|
||||
GLStateManager.enableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlend() {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defaultBlendFunc() {
|
||||
GLStateManager.defaultBlendFunc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnknownBlendState() {
|
||||
// No-op: Angelica tracks blend state accurately
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableDepthTest() {
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableDepthTest() {
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthFunc(int depthFunc) {
|
||||
GLStateManager.glDepthFunc(depthFunc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthMask(boolean flag) {
|
||||
GLStateManager.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLStateManager.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int mask, boolean checkError) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
// ===================== UNIFORM OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int value) {
|
||||
GL20.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
|
||||
GL20.glUniformMatrix3(location, transpose, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
|
||||
GL20.glUniformMatrix4(location, transpose, value);
|
||||
}
|
||||
|
||||
// ===================== THREAD ASSERTIONS =====================
|
||||
|
||||
@Override
|
||||
public void assertOnRenderThread() {
|
||||
// No-op: 1.7.10 is single-threaded for rendering
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertOnRenderThreadOrInit() {
|
||||
// No-op: 1.7.10 is single-threaded for rendering
|
||||
}
|
||||
|
||||
// ===================== SHADER STATE =====================
|
||||
|
||||
@Override
|
||||
public void setShaderTexture(int shaderTexture, int textureId) {
|
||||
if (shaderTexture >= 0 && shaderTexture < 16) {
|
||||
shaderTextures[shaderTexture] = textureId;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShaderTexture(int shaderTexture) {
|
||||
return (shaderTexture >= 0 && shaderTexture < 16) ? shaderTextures[shaderTexture] : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderColor(float red, float green, float blue, float alpha) {
|
||||
shaderColor[0] = red;
|
||||
shaderColor[1] = green;
|
||||
shaderColor[2] = blue;
|
||||
shaderColor[3] = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getShaderColor() {
|
||||
return shaderColor.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderLineWidth(float lineWidth) {
|
||||
shaderLineWidth = lineWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderLineWidth() {
|
||||
return shaderLineWidth;
|
||||
}
|
||||
|
||||
// ===================== FOG STATE =====================
|
||||
|
||||
@Override
|
||||
public void setShaderFogColor(float red, float green, float blue, float alpha) {
|
||||
shaderFogColor[0] = red;
|
||||
shaderFogColor[1] = green;
|
||||
shaderFogColor[2] = blue;
|
||||
shaderFogColor[3] = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getShaderFogColor() {
|
||||
return shaderFogColor.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderFogStart(float start) {
|
||||
shaderFogStart = start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderFogStart() {
|
||||
return shaderFogStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderFogEnd(float end) {
|
||||
shaderFogEnd = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderFogEnd() {
|
||||
return shaderFogEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFogShape(int shape) {
|
||||
fogShape = shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFogShape() {
|
||||
return fogShape;
|
||||
}
|
||||
|
||||
// ===================== PROJECTION MATRIX =====================
|
||||
|
||||
@Override
|
||||
public Matrix4f getProjectionMatrix() {
|
||||
return new Matrix4f(projectionMatrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectionMatrixOrth(Matrix4f m) {
|
||||
projectionMatrix.set(m);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
projectionMatrix.get(projectionMatrixBuffer);
|
||||
projectionMatrixBuffer.rewind();
|
||||
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectionMatrixOrigin(Matrix4f m) {
|
||||
setProjectionMatrixOrth(m);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Builds final command buffers from raw command buffers and compiled VBO data.
|
||||
* Bridges the gap between VBO compilation (glsm types) and buffer processing (recording types).
|
||||
*/
|
||||
public final class CommandBufferBuilder {
|
||||
|
||||
private CommandBufferBuilder() {}
|
||||
|
||||
/**
|
||||
* Build commands with transform collapsing optimization.
|
||||
* Accumulates MODELVIEW transforms and emits MultMatrix at barriers (draws, CallList).
|
||||
* Uses mergedRanges where consecutive same-transform draws are combined.
|
||||
*/
|
||||
public static void buildFromRawBuffer(
|
||||
CommandBuffer rawBuffer,
|
||||
List<AccumulatedDraw> accumulatedDraws,
|
||||
CommandBuffer finalBuffer) {
|
||||
|
||||
rawBuffer.resetRead();
|
||||
int cmdIndex = 0;
|
||||
int rangeIndex = 0;
|
||||
|
||||
AccumulatedDraw draw;
|
||||
while (rawBuffer.hasRemaining()) {
|
||||
// Read and process the command
|
||||
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
|
||||
|
||||
// Emit draw ranges at this command position
|
||||
while (rangeIndex < accumulatedDraws.size() && (draw = accumulatedDraws.get(rangeIndex)).commandIndex == cmdIndex) {
|
||||
emitDrawRangeToBuffer(draw, finalBuffer, rangeIndex);
|
||||
rangeIndex++;
|
||||
}
|
||||
|
||||
cmdIndex++;
|
||||
}
|
||||
|
||||
while (rangeIndex < accumulatedDraws.size()) {
|
||||
emitDrawRangeToBuffer(accumulatedDraws.get(rangeIndex), finalBuffer, rangeIndex);
|
||||
rangeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a draw range to the output buffer, with transform if needed.
|
||||
*/
|
||||
private static void emitDrawRangeToBuffer(
|
||||
AccumulatedDraw draw,
|
||||
CommandBuffer out,
|
||||
int vboIdx) {
|
||||
|
||||
if (draw.restoreData != null) {
|
||||
out.writeDrawRangeRestore(vboIdx, draw.restoreData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the draw range command
|
||||
out.writeDrawRange(vboIdx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.taumc.glsl.ShaderParser;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
|
||||
/**
|
||||
* Compat transformation for mod shaders running under core profile.
|
||||
*
|
||||
* <p>Handles:
|
||||
* <ul>
|
||||
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
|
||||
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
|
||||
* <li>gl_TexCoord[N] varying array → per-index in/out declarations</li>
|
||||
* <li>Texture function renames (texture2D → texture, etc.)</li>
|
||||
* <li>Fragment output handling (gl_FragColor → layout-qualified out declarations)</li>
|
||||
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
|
||||
* <li>gl_FrontColor → local variable in vertex shaders</li>
|
||||
* <li>shadow2D/shadow2DLod → texture/textureLod with vec4 wrapping</li>
|
||||
* <li>Version upgrade to 330 core minimum</li>
|
||||
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CompatShaderTransformer {
|
||||
|
||||
private static final Pattern VERSION_PATTERN =
|
||||
Pattern.compile("#version\\s+(\\d+)(?:\\s+(\\w+))?");
|
||||
|
||||
private static final Pattern DEFINE_PATTERN =
|
||||
Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
|
||||
|
||||
/** Compat builtins that trigger AST transformation. */
|
||||
private static final Set<String> COMPAT_BUILTINS = Set.of(
|
||||
"gl_ModelView", "gl_Projection", "gl_NormalMatrix", "gl_TextureMatrix",
|
||||
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
|
||||
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
|
||||
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
|
||||
"shadow2D", "gl_FrontLightModelProduct",
|
||||
"gl_LightSource", "gl_FrontMaterial"
|
||||
);
|
||||
|
||||
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
|
||||
String.join("|", COMPAT_BUILTINS) + "|\\b(?:attribute|varying)\\b"
|
||||
);
|
||||
|
||||
private static final Map<String, String> MATRIX_RENAMES = Map.of(
|
||||
"gl_ModelViewMatrix", "angelica_ModelViewMatrix",
|
||||
"gl_ModelViewMatrixInverse", "angelica_ModelViewMatrixInverse",
|
||||
"gl_ProjectionMatrix", "angelica_ProjectionMatrix",
|
||||
"gl_ProjectionMatrixInverse", "angelica_ProjectionMatrixInverse",
|
||||
"gl_NormalMatrix", "angelica_NormalMatrix"
|
||||
);
|
||||
|
||||
private static final Path DUMP_DIR;
|
||||
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
|
||||
static {
|
||||
boolean isDev = false;
|
||||
try {
|
||||
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
|
||||
isDev = Boolean.TRUE.equals(deobfEnv);
|
||||
} catch (Exception ignored) {}
|
||||
DUMP_DIR = (isDev || Boolean.parseBoolean(System.getProperty("angelica.compat.dumpShaders", "false")))
|
||||
? Paths.get("compat_shaders") : null;
|
||||
}
|
||||
|
||||
private static final int CACHE_SIZE = 32;
|
||||
private record CacheKey(String source, boolean isFragment) {}
|
||||
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
|
||||
new LinkedHashMap<>(32, 0.75f, true) {
|
||||
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
|
||||
return size() > CACHE_SIZE;
|
||||
}
|
||||
});
|
||||
|
||||
public static void clearCache() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a mod shader source for core profile compatibility.
|
||||
*/
|
||||
public static String transform(String source, boolean isFragment) {
|
||||
final boolean needsTransform = needsTransformation(source);
|
||||
String result;
|
||||
if (!needsTransform) {
|
||||
result = fixupVersion(source);
|
||||
} else {
|
||||
final CacheKey key = new CacheKey(source, isFragment);
|
||||
final String cached = cache.get(key);
|
||||
if (cached != null) {
|
||||
dumpShader(source, cached, isFragment, needsTransform);
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
result = transformInternal(source, isFragment);
|
||||
cache.put(key, result);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
|
||||
result = fixupVersion(source);
|
||||
}
|
||||
}
|
||||
|
||||
dumpShader(source, result, isFragment, needsTransform);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean needsTransformation(String source) {
|
||||
// Shaders already at 330+ core don't need compat transformation
|
||||
final Matcher vm = VERSION_PATTERN.matcher(source);
|
||||
if (vm.find()) {
|
||||
final int version = Integer.parseInt(vm.group(1));
|
||||
if (version >= 330 && "core".equals(vm.group(2))) return false;
|
||||
}
|
||||
|
||||
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
|
||||
}
|
||||
|
||||
private static String transformInternal(String source, boolean isFragment) {
|
||||
final Matcher versionMatcher = VERSION_PATTERN.matcher(source);
|
||||
int declaredVersion = 110;
|
||||
if (versionMatcher.find()) {
|
||||
declaredVersion = Integer.parseInt(versionMatcher.group(1));
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(declaredVersion, 330);
|
||||
|
||||
// Pre-parse reserved word renaming — prevents ANTLR parse failures
|
||||
source = GlslTransformUtils.replaceTexture(source);
|
||||
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
|
||||
|
||||
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source);
|
||||
final Transformer transformer = new Transformer(parsedShader.full());
|
||||
|
||||
injectMatrixUniforms(transformer);
|
||||
|
||||
transformFog(transformer, isFragment, source);
|
||||
|
||||
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform
|
||||
if (source.contains("gl_FrontLightModelProduct")) {
|
||||
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
|
||||
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
|
||||
}
|
||||
|
||||
// gl_LightSource[i] → struct + uniform array (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_LightSource")) {
|
||||
transformer.injectFunction(
|
||||
"struct angelica_LightSourceParameters {"
|
||||
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
|
||||
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
|
||||
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
|
||||
transformer.rename("gl_LightSource", "angelica_LightSource");
|
||||
}
|
||||
|
||||
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_FrontMaterial")) {
|
||||
transformer.injectFunction(
|
||||
"struct angelica_MaterialParameters {"
|
||||
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
|
||||
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
|
||||
}
|
||||
|
||||
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
|
||||
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
|
||||
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_FrontColor", "angelica_FrontColor");
|
||||
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
|
||||
|
||||
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
|
||||
transformVertexAttributes(transformer, source);
|
||||
} else {
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("in vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_Color", "angelica_FrontColor");
|
||||
}
|
||||
}
|
||||
|
||||
// gl_TexCoord[N] varying array → per-index in/out declarations
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
|
||||
for (Integer i : texCoordIndices) {
|
||||
final String qualifier = isFragment ? "in" : "out";
|
||||
transformer.injectVariable(qualifier + " vec4 angelica_TexCoord" + i + ";");
|
||||
}
|
||||
|
||||
// Fragment output handling + alpha test discard
|
||||
if (isFragment) {
|
||||
transformFragmentOutputs(transformer);
|
||||
}
|
||||
|
||||
// texture-as-variable collision handling
|
||||
if (transformer.containsCall("texture") && transformer.hasVariable("texture")) {
|
||||
transformer.rename("texture", "gtexture");
|
||||
}
|
||||
if (transformer.hasVariable("angelica_renamed_texture")) {
|
||||
transformer.rename("angelica_renamed_texture", "gtexture");
|
||||
}
|
||||
|
||||
transformer.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
|
||||
|
||||
transformer.renameAndWrapShadow("shadow2D", "texture");
|
||||
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
|
||||
|
||||
final String versionDirective = "#version " + targetVersion + " core\n";
|
||||
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
||||
|
||||
// Preserve #define directives
|
||||
final StringBuilder defines = new StringBuilder();
|
||||
final Matcher defineMatcher = DEFINE_PATTERN.matcher(source);
|
||||
while (defineMatcher.find()) {
|
||||
defines.append(defineMatcher.group().trim()).append('\n');
|
||||
}
|
||||
|
||||
final String header = versionDirective + (extensions.isEmpty() ? "" : "\n" + extensions) + (defines.isEmpty() ? "" : "\n" + defines);
|
||||
final StringBuilder result = new StringBuilder();
|
||||
transformer.mutateTree(tree -> result.append(GlslTransformUtils.getFormattedShader(tree, header)));
|
||||
|
||||
// Restore pre-parse renames
|
||||
String output = GlslTransformUtils.restoreReservedWords(result.toString());
|
||||
|
||||
// Core profile: attribute → in, varying → out (vertex) / in (fragment)
|
||||
output = fixupQualifiers(output, isFragment);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject matrix uniforms and rename compat builtins.
|
||||
*/
|
||||
private static void injectMatrixUniforms(Transformer transformer) {
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat3 angelica_NormalMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_LightmapTextureMatrix;");
|
||||
|
||||
transformer.rename(MATRIX_RENAMES);
|
||||
|
||||
// Expression replacements
|
||||
transformer.replaceExpression("gl_ModelViewProjectionMatrix", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[0]", "mat4(1.0)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[1]", "angelica_LightmapTextureMatrix");
|
||||
transformer.replaceExpression("gl_TextureMatrix", "mat4[8](mat4(1.0), angelica_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform fragment outputs for core profile.
|
||||
*/
|
||||
private static void transformFragmentOutputs(Transformer transformer) {
|
||||
if (transformer.containsCall("gl_FragColor")) {
|
||||
transformer.replaceExpression("gl_FragColor", "gl_FragData[0]");
|
||||
}
|
||||
|
||||
final Set<Integer> found = new HashSet<>();
|
||||
transformer.renameArray("gl_FragData", "angelica_FragData", found);
|
||||
|
||||
for (Integer i : found) {
|
||||
transformer.injectVariable("layout (location = " + i + ") out vec4 angelica_FragData" + i + ";");
|
||||
}
|
||||
|
||||
// Core profile: GL_ALPHA_TEST is removed - inject runtime discard using GLSM-tracked alpha reference (uploaded by CompatUniformManager).
|
||||
if (found.contains(0)) {
|
||||
transformer.injectVariable("uniform float angelica_currentAlphaTest;");
|
||||
transformer.appendMain("if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;");
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
|
||||
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
|
||||
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out float angelica_FogFragCoord;");
|
||||
transformer.prependMain("angelica_FogFragCoord = 0.0;");
|
||||
} else {
|
||||
if (source.contains("gl_FogFragCoord")) {
|
||||
transformer.injectVariable("in float angelica_FogFragCoord;");
|
||||
}
|
||||
}
|
||||
|
||||
transformer.rename("gl_Fog", "angelica_Fog");
|
||||
transformer.injectVariable("uniform float angelica_FogDensity;");
|
||||
transformer.injectVariable("uniform float angelica_FogStart;");
|
||||
transformer.injectVariable("uniform float angelica_FogEnd;");
|
||||
transformer.injectVariable("uniform vec4 angelica_FogColor;");
|
||||
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
|
||||
transformer.injectFunction(
|
||||
"angelica_FogParameters angelica_Fog = angelica_FogParameters("
|
||||
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
|
||||
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
|
||||
*/
|
||||
private static void transformVertexAttributes(Transformer transformer, String source) {
|
||||
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
|
||||
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
|
||||
transformer.rename("gl_Vertex", "angelica_Vertex");
|
||||
}
|
||||
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
|
||||
transformer.rename("gl_Color", "angelica_Color");
|
||||
}
|
||||
if (source.contains("gl_MultiTexCoord0")) {
|
||||
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
|
||||
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
|
||||
}
|
||||
if (source.contains("gl_MultiTexCoord1")) {
|
||||
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
|
||||
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
|
||||
}
|
||||
if (source.contains("gl_Normal")) {
|
||||
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
|
||||
transformer.rename("gl_Normal", "angelica_Normal");
|
||||
}
|
||||
if (source.contains("ftransform")) {
|
||||
transformer.replaceExpression("ftransform()", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix * angelica_Vertex)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
|
||||
if (DUMP_DIR == null) return;
|
||||
final int id = dumpCounter.getAndIncrement();
|
||||
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
|
||||
try {
|
||||
Files.createDirectories(DUMP_DIR);
|
||||
// Capture caller info for identification
|
||||
final String caller = identifyCaller();
|
||||
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
|
||||
+ "\n// Transformed: " + wasTransformed
|
||||
+ "\n// Caller: " + caller + "\n\n";
|
||||
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
|
||||
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String identifyCaller() {
|
||||
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
|
||||
final String cls = frame.getClassName();
|
||||
if (!cls.startsWith("com.gtnewhorizons.angelica.glsm.")
|
||||
&& !cls.startsWith("java.")
|
||||
&& !cls.equals("org.lwjgl.opengl.GL20")) {
|
||||
return cls + "." + frame.getMethodName() + ":" + frame.getLineNumber();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\battribute\\b");
|
||||
private static final Pattern VARYING_PATTERN = Pattern.compile("\\bvarying\\b");
|
||||
|
||||
/** Replace legacy storage qualifiers removed in core profile. Safe on AST-serialized output (no comments). */
|
||||
public static String fixupQualifiers(String source, boolean isFragment) {
|
||||
source = ATTRIBUTE_PATTERN.matcher(source).replaceAll("in");
|
||||
source = VARYING_PATTERN.matcher(source).replaceAll(isFragment ? "in" : "out");
|
||||
return source;
|
||||
}
|
||||
|
||||
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
|
||||
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
|
||||
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
|
||||
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
|
||||
|
||||
/**
|
||||
* Generate a passthrough vertex shader for a fragment-only program.
|
||||
*
|
||||
* @param fragmentSource the transformed fragment shader source
|
||||
* @return a complete vertex shader source string
|
||||
*/
|
||||
public static String generatePassthroughVertexShader(String fragmentSource) {
|
||||
final StringBuilder sb = new StringBuilder(512);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
// Always have position
|
||||
sb.append("layout(location = 0) in vec4 a_Position;\n");
|
||||
|
||||
// Matrix uniforms for position transform
|
||||
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
|
||||
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
|
||||
|
||||
final StringBuilder varyings = new StringBuilder();
|
||||
final StringBuilder assignments = new StringBuilder();
|
||||
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
|
||||
sb.append("layout(location = 1) in vec4 a_Color;\n");
|
||||
varyings.append("out vec4 angelica_FrontColor;\n");
|
||||
assignments.append(" angelica_FrontColor = a_Color;\n");
|
||||
}
|
||||
|
||||
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
while (texCoordMatcher.find()) {
|
||||
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
|
||||
}
|
||||
for (int i : texCoordIndices) {
|
||||
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
|
||||
final int location = i == 0 ? 2 : 3;
|
||||
sb.append("layout(location = ").append(location).append(") in vec4 a_TexCoord").append(i).append(";\n");
|
||||
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
|
||||
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
|
||||
}
|
||||
|
||||
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
|
||||
varyings.append("out float angelica_FogFragCoord;\n");
|
||||
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
|
||||
}
|
||||
|
||||
sb.append(varyings);
|
||||
sb.append("\nvoid main() {\n");
|
||||
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
|
||||
sb.append(assignments);
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
|
||||
private static String fixupVersion(String source) {
|
||||
final Matcher m = VERSION_PATTERN.matcher(source);
|
||||
if (!m.find()) {
|
||||
return "#version 330 core\n" + source;
|
||||
}
|
||||
|
||||
final int version = Integer.parseInt(m.group(1));
|
||||
final String profile = m.group(2);
|
||||
|
||||
if (version >= 330 && !"compatibility".equals(profile)) {
|
||||
if ("core".equals(profile)) return source;
|
||||
return m.replaceFirst("#version " + version + " core");
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(version, 330);
|
||||
return m.replaceFirst("#version " + targetVersion + " core");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
|
||||
/**
|
||||
* Per-program compat uniform location cache and upload manager.
|
||||
*
|
||||
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader programs.
|
||||
*/
|
||||
public class CompatUniformManager {
|
||||
|
||||
// Uniform location indices within the cached array (package-private for tests)
|
||||
static final int LOC_MODELVIEW = 0;
|
||||
static final int LOC_MODELVIEW_INVERSE = 1;
|
||||
static final int LOC_PROJECTION = 2;
|
||||
static final int LOC_PROJECTION_INVERSE = 3;
|
||||
static final int LOC_NORMAL = 4;
|
||||
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
|
||||
static final int LOC_FOG_DENSITY = 6;
|
||||
static final int LOC_FOG_START = 7;
|
||||
static final int LOC_FOG_END = 8;
|
||||
static final int LOC_FOG_COLOR = 9;
|
||||
static final int LOC_IRIS_MODELVIEW = 10;
|
||||
static final int LOC_IRIS_PROJECTION = 11;
|
||||
static final int LOC_IRIS_NORMAL = 12;
|
||||
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
|
||||
static final int LOC_ALPHA_TEST_REF = 14;
|
||||
static final int LOC_SCENE_COLOR = 15;
|
||||
static final int LOC_CLIP_PLANES = 16;
|
||||
static final int LOC_CLIP_PLANES_ENABLED = 17;
|
||||
|
||||
// Light source fields: 12 per light × 2 lights = 24 locations
|
||||
static final int LIGHT_FIELDS = 12;
|
||||
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
|
||||
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
|
||||
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
|
||||
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
|
||||
static final int LOC_LIGHT_BASE = 18;
|
||||
|
||||
// Material fields: 5 locations
|
||||
static final int MF_EMISSION = 0, MF_AMBIENT = 1, MF_DIFFUSE = 2, MF_SPECULAR = 3, MF_SHININESS = 4;
|
||||
static final int MAT_FIELDS = 5;
|
||||
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 42
|
||||
|
||||
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 47
|
||||
|
||||
private static final String[] LIGHT_FIELD_NAMES = {
|
||||
"ambient", "diffuse", "specular", "position", "halfVector",
|
||||
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
|
||||
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
|
||||
};
|
||||
private static final String[] MAT_FIELD_NAMES = {
|
||||
"emission", "ambient", "diffuse", "specular", "shininess",
|
||||
};
|
||||
|
||||
private static final String[] UNIFORM_NAMES;
|
||||
static {
|
||||
UNIFORM_NAMES = new String[LOC_COUNT];
|
||||
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
|
||||
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
|
||||
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
|
||||
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
|
||||
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
|
||||
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES_ENABLED] = "angelica_ClipPlanesEnabled";
|
||||
for (int li = 0; li < 2; li++) {
|
||||
for (int fi = 0; fi < LIGHT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] =
|
||||
"angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
for (int fi = 0; fi < MAT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
|
||||
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// Reusable NIO buffers for upload
|
||||
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
|
||||
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
|
||||
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
|
||||
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
|
||||
private static final Matrix3f normalMatrix = new Matrix3f();
|
||||
private static final Matrix4f scratchMatrix = new Matrix4f();
|
||||
|
||||
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
|
||||
private static final FloatBuffer lightmapMatrixBuf;
|
||||
static {
|
||||
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
|
||||
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// Dirty tracking: skip uploads when state hasn't changed and program is the same
|
||||
private static int lastProgram = -1;
|
||||
private static int lastMvGen = -1;
|
||||
private static int lastProjGen = -1;
|
||||
private static int lastTexMatGen = -1;
|
||||
private static int lastFragmentGen = -1;
|
||||
private static int lastLightingGen = -1;
|
||||
private static int lastClipPlaneGen = -1;
|
||||
|
||||
private CompatUniformManager() {}
|
||||
|
||||
public static void onLinkProgram(int program) {
|
||||
int[] locs = new int[LOC_COUNT];
|
||||
boolean hasAny = false;
|
||||
|
||||
for (int i = 0; i < LOC_COUNT; i++) {
|
||||
locs[i] = GL20.glGetUniformLocation(program, UNIFORM_NAMES[i]);
|
||||
if (locs[i] != -1) hasAny = true;
|
||||
}
|
||||
|
||||
if (hasAny) {
|
||||
programLocations.put(program, locs);
|
||||
LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onUseProgram(int program) {
|
||||
if (program == 0) return;
|
||||
|
||||
int[] locs = programLocations.get(program);
|
||||
if (locs == null) return;
|
||||
|
||||
final boolean programChanged = program != lastProgram;
|
||||
lastProgram = program;
|
||||
|
||||
// Matrix uniforms — skip if generation unchanged and same program
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
lastFragmentGen = fragGen;
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
// Lighting-derived uniforms (scene color, light sources, material)
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
lastLightingGen = litGen;
|
||||
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
|
||||
uploadLightSources(locs);
|
||||
uploadMaterial(locs);
|
||||
}
|
||||
|
||||
// Clip plane equations + enabled bool — uploaded when enable state or equations change
|
||||
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
if (mvChanged) {
|
||||
// ModelView
|
||||
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
mv.get(mat4Buf);
|
||||
if (locs[LOC_MODELVIEW] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// ModelView Inverse
|
||||
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
|
||||
mv.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
|
||||
}
|
||||
|
||||
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
|
||||
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
normalMatrix.get(mat3Buf);
|
||||
if (locs[LOC_NORMAL] != -1) {
|
||||
GL20.glUniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_NORMAL] != -1) {
|
||||
GL20.glUniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projChanged) {
|
||||
// Projection
|
||||
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
proj.get(mat4Buf);
|
||||
if (locs[LOC_PROJECTION] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Projection Inverse
|
||||
if (locs[LOC_PROJECTION_INVERSE] != -1) {
|
||||
proj.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (texMatChanged) {
|
||||
// Iris lightmap texture matrix (constant)
|
||||
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
|
||||
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadFragmentUniforms(int[] locs) {
|
||||
// Fog uniforms
|
||||
FogStateStack fog = GLStateManager.getFogState();
|
||||
if (locs[LOC_FOG_DENSITY] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
|
||||
}
|
||||
if (locs[LOC_FOG_START] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_START], fog.getStart());
|
||||
}
|
||||
if (locs[LOC_FOG_END] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_END], fog.getEnd());
|
||||
}
|
||||
if (locs[LOC_FOG_COLOR] != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y)
|
||||
.put((float) fog.getFogColor().z).put(fog.getFogAlpha());
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(locs[LOC_FOG_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
|
||||
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
|
||||
if (locs[LOC_ALPHA_TEST_REF] != -1) {
|
||||
final float ref;
|
||||
if (!GLStateManager.getAlphaTest().isEnabled()
|
||||
|| GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
|
||||
ref = -1.0f;
|
||||
} else {
|
||||
ref = GLStateManager.getAlphaState().getReference();
|
||||
}
|
||||
GL20.glUniform1f(locs[LOC_ALPHA_TEST_REF], ref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a
|
||||
* Same computation as {@code Uniforms.uploadLightingPreComputed()}.
|
||||
*/
|
||||
private static void uploadSceneColor(int[] locs) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
final LightModelState lm = GLStateManager.getLightModel();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
|
||||
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
|
||||
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
|
||||
.put(mat.diffuse.w);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(locs[LOC_SCENE_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
private static void uploadLightSources(int[] locs) {
|
||||
// Early exit: if the first light field isn't present, no lighting uniforms exist
|
||||
if (locs[LOC_LIGHT_BASE] == -1) return;
|
||||
final LightStateStack[] lights = GLStateManager.getLightDataStates();
|
||||
for (int li = 0; li < 2; li++) {
|
||||
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
|
||||
final LightState light = lights[li];
|
||||
|
||||
if (locs[base + LF_AMBIENT] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
|
||||
if (locs[base + LF_DIFFUSE] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
|
||||
if (locs[base + LF_SPECULAR] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
|
||||
if (locs[base + LF_POSITION] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
|
||||
if (locs[base + LF_HALF_VECTOR] != -1) {
|
||||
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
|
||||
float hx, hy, hz;
|
||||
if (light.position.w == 0.0f) {
|
||||
float len = (float) Math.sqrt(light.position.x * light.position.x
|
||||
+ light.position.y * light.position.y + light.position.z * light.position.z);
|
||||
if (len > 0) {
|
||||
hx = light.position.x / len;
|
||||
hy = light.position.y / len;
|
||||
hz = light.position.z / len + 1.0f;
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
|
||||
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
|
||||
GL20.glUniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
|
||||
}
|
||||
if (locs[base + LF_SPOT_DIRECTION] != -1)
|
||||
GL20.glUniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
|
||||
if (locs[base + LF_SPOT_EXPONENT] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
|
||||
if (locs[base + LF_SPOT_CUTOFF] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
|
||||
if (locs[base + LF_SPOT_COS_CUTOFF] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
|
||||
if (locs[base + LF_CONSTANT_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
|
||||
if (locs[base + LF_LINEAR_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
|
||||
if (locs[base + LF_QUADRATIC_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMaterial(int[] locs) {
|
||||
// Early exit: if the first material field isn't present, no material uniforms exist
|
||||
if (locs[LOC_MAT_BASE] == -1) return;
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
|
||||
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
|
||||
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
|
||||
GL20.glUniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
|
||||
}
|
||||
|
||||
private static void uploadClipPlanes(int[] locs) {
|
||||
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
GL20.glUniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
|
||||
}
|
||||
if (locs[LOC_CLIP_PLANES] != -1) {
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
clipPlaneBuf.flip();
|
||||
GL20.glUniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onDeleteProgram(int program) {
|
||||
programLocations.remove(program);
|
||||
}
|
||||
|
||||
public static boolean hasProgram(int program) {
|
||||
return programLocations.containsKey(program);
|
||||
}
|
||||
|
||||
public static int[] getLocations(int program) {
|
||||
return programLocations.get(program);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
/*
|
||||
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
|
||||
*
|
||||
* Original copyright and license:
|
||||
*
|
||||
* Copyright 2017 Valve Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sub license, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the
|
||||
* next paragraph) shall be included in all copies or substantial portions
|
||||
* of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Original author: Samuel Pitoiset <[email protected]>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bitmap ID allocator for display list IDs.
|
||||
*
|
||||
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
|
||||
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
|
||||
*/
|
||||
public class DisplayListIDAllocator {
|
||||
|
||||
private int[] data;
|
||||
private int numElements; // number of uint32 words allocated
|
||||
private int numSetElements; // highest word index with any bit set + 1
|
||||
private int lowestFreeIdx; // optimization: skip fully-allocated words
|
||||
|
||||
public DisplayListIDAllocator() {
|
||||
this(32); // 1024 IDs initially
|
||||
}
|
||||
|
||||
public DisplayListIDAllocator(int initialWords) {
|
||||
if (initialWords < 1) initialWords = 1;
|
||||
this.data = new int[initialWords];
|
||||
this.numElements = initialWords;
|
||||
this.numSetElements = 0;
|
||||
this.lowestFreeIdx = 0;
|
||||
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
|
||||
data[0] = 1;
|
||||
numSetElements = 1;
|
||||
}
|
||||
|
||||
public int allocRange(int count) {
|
||||
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
|
||||
if (count == 1) return allocSingle();
|
||||
return allocMultiple(count);
|
||||
}
|
||||
|
||||
private int allocSingle() {
|
||||
for (int i = lowestFreeIdx; i < numElements; i++) {
|
||||
if (data[i] == 0xFFFFFFFF) continue;
|
||||
|
||||
int bit = Integer.numberOfTrailingZeros(~data[i]);
|
||||
data[i] |= 1 << bit;
|
||||
lowestFreeIdx = i;
|
||||
numSetElements = Math.max(numSetElements, i + 1);
|
||||
return i * 32 + bit;
|
||||
}
|
||||
|
||||
// No slots available, grow. Record old end, then mark first bit there.
|
||||
int oldNumElements = numElements;
|
||||
resize(Math.max(numElements, 1) * 2);
|
||||
lowestFreeIdx = oldNumElements;
|
||||
data[oldNumElements] |= 1;
|
||||
numSetElements = Math.max(numSetElements, oldNumElements + 1);
|
||||
return oldNumElements * 32;
|
||||
}
|
||||
|
||||
private int allocMultiple(int num) {
|
||||
int numWords = divRoundUp(num, 32);
|
||||
int base = findFreeBlock(lowestFreeIdx);
|
||||
boolean found = false;
|
||||
|
||||
while (!found) {
|
||||
int i;
|
||||
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
|
||||
;
|
||||
|
||||
if (i - base == numWords) {
|
||||
found = true;
|
||||
} else if (i == numElements) {
|
||||
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
|
||||
resize(numElements * 2 + numWords);
|
||||
found = true;
|
||||
} else {
|
||||
// Non-zero word at i, skip past it
|
||||
base = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark bits as used
|
||||
int fullWords = num / 32;
|
||||
int remainder = num % 32;
|
||||
for (int i = base; i < base + fullWords; i++) {
|
||||
data[i] = 0xFFFFFFFF;
|
||||
}
|
||||
if (remainder != 0) {
|
||||
data[base + fullWords] |= (1 << remainder) - 1;
|
||||
}
|
||||
|
||||
if (lowestFreeIdx == base) {
|
||||
lowestFreeIdx = base + num / 32;
|
||||
}
|
||||
numSetElements = Math.max(numSetElements, base + numWords);
|
||||
|
||||
return base * 32;
|
||||
}
|
||||
|
||||
private int findFreeBlock(int start) {
|
||||
for (int i = start; i < numElements; i++) {
|
||||
if (data[i] == 0) return i;
|
||||
}
|
||||
return numElements;
|
||||
}
|
||||
|
||||
public void free(int id) {
|
||||
if (id <= 0) return; // ID 0 is reserved
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) return;
|
||||
|
||||
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
|
||||
if (numSetElements == idx + 1) {
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void freeRange(int base, int count) {
|
||||
if (count <= 0 || base <= 0) return;
|
||||
|
||||
int startIdx = base / 32;
|
||||
if (startIdx >= numElements) return;
|
||||
|
||||
int end = base + count;
|
||||
for (int id = base; id < end; id++) {
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) break;
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
}
|
||||
|
||||
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAllocated(int id) {
|
||||
if (id <= 0) return false;
|
||||
int idx = id / 32;
|
||||
if (idx >= numSetElements) return false;
|
||||
return (data[idx] & (1 << (id % 32))) != 0;
|
||||
}
|
||||
|
||||
private void resize(int newNumElements) {
|
||||
if (newNumElements <= numElements) return;
|
||||
int[] newData = new int[newNumElements];
|
||||
System.arraycopy(data, 0, newData, 0, numElements);
|
||||
data = newData;
|
||||
numElements = newNumElements;
|
||||
}
|
||||
|
||||
private static int divRoundUp(int a, int b) {
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -22,6 +24,7 @@ public class Feature {
|
||||
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
|
||||
|
||||
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
|
||||
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static List<IStateStack<?>> maskToFeatures(int mask) {
|
||||
if(maskToFeaturesMap.containsKey(mask)) {
|
||||
@@ -42,6 +45,29 @@ public class Feature {
|
||||
return asList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only non-BooleanStateStack instances for the given mask.
|
||||
* These use traditional push/pop without global depth tracking.
|
||||
*/
|
||||
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
|
||||
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final List<IStateStack<?>> all = maskToFeatures(mask);
|
||||
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
|
||||
for (int i = 0; i < all.size(); i++) {
|
||||
final IStateStack<?> stack = all.get(i);
|
||||
if (!(stack instanceof BooleanStateStack)) {
|
||||
nonBooleans.add(stack);
|
||||
}
|
||||
}
|
||||
cached = nonBooleans.toArray(new IStateStack<?>[0]);
|
||||
maskToNonBooleanStacksMap.put(mask, cached);
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
|
||||
|
||||
/**
|
||||
@@ -62,22 +88,20 @@ public class Feature {
|
||||
GLStateManager.alphaTest // GL_ALPHA_TEST enable bit
|
||||
, GLStateManager.alphaState // Alpha test function and reference value
|
||||
, GLStateManager.blendMode // GL_BLEND enable bit
|
||||
, GLStateManager.blendState // Blending source and destination functions
|
||||
// Constant blend color
|
||||
// Blending equation
|
||||
, GLStateManager.blendState // Blending source/destination functions, equation, blend color
|
||||
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
|
||||
, GLStateManager.ditherState // GL_DITHER enable bit
|
||||
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
|
||||
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
|
||||
// Logic op function
|
||||
, GLStateManager.logicOpMode // Logic op function
|
||||
, GLStateManager.colorMask // Color-mode and index-mode writemasks
|
||||
, GLStateManager.clearColor // Color-mode and index-mode clear values
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
|
||||
GLStateManager.color // Current RGBA color
|
||||
, ShaderManager.getNormalStack() // Current normal vector
|
||||
, ShaderManager.getTexCoordStack() // Current texture coordinates
|
||||
// Current color index
|
||||
// Current normal vector
|
||||
// Current texture coordinates
|
||||
// Current raster position
|
||||
// GL_CURRENT_RASTER_POSITION_VALID flag
|
||||
// RGBA color associated with current raster position
|
||||
@@ -223,10 +247,9 @@ public class Feature {
|
||||
, GLStateManager.shadeModelState // GL_SHADE_MODEL setting
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
|
||||
// GL_LINE_SMOOTH flag
|
||||
// GL_LINE_STIPPLE enable bit
|
||||
// Line stipple pattern and repeat counter
|
||||
// Line width
|
||||
GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
|
||||
, GLStateManager.lineStippleState // GL_LINE_STIPPLE enable bit
|
||||
, GLStateManager.lineState // Line stipple pattern, repeat counter, and width
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
|
||||
// GL_LIST_BASE setting
|
||||
@@ -251,21 +274,17 @@ public class Feature {
|
||||
// GL_READ_BUFFER setting
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
|
||||
// GL_POINT_SMOOTH flag
|
||||
// Point size
|
||||
GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
|
||||
, GLStateManager.pointState // Point size
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
|
||||
GLStateManager.cullState // GL_CULL_FACE enable bit
|
||||
// GL_CULL_FACE_MODE value
|
||||
// GL_FRONT_FACE indicator
|
||||
// GL_POLYGON_MODE setting
|
||||
// GL_POLYGON_SMOOTH flag
|
||||
// GL_POLYGON_STIPPLE enable bit
|
||||
// GL_POLYGON_OFFSET_FILL flag
|
||||
// GL_POLYGON_OFFSET_LINE flag
|
||||
// GL_POLYGON_OFFSET_POINT flag
|
||||
// GL_POLYGON_OFFSET_FACTOR
|
||||
// GL_POLYGON_OFFSET_UNITS
|
||||
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
|
||||
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE enable bit
|
||||
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
|
||||
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
|
||||
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
|
||||
, GLStateManager.polygonState // GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_OFFSET_FACTOR/UNITS
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
|
||||
// Polygon stipple pattern
|
||||
@@ -275,15 +294,12 @@ public class Feature {
|
||||
// Scissor box
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
|
||||
// GL_STENCIL_TEST enable bit
|
||||
// Stencil function and reference value
|
||||
// Stencil value mask
|
||||
// Stencil fail, pass, and depth buffer pass actions
|
||||
// Stencil buffer clear value
|
||||
// Stencil buffer writemask
|
||||
GLStateManager.stencilTest // GL_STENCIL_TEST enable bit
|
||||
, GLStateManager.stencilState // Stencil function, ref, mask, ops, writemask, clear value
|
||||
));
|
||||
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
|
||||
GLStateManager.activeTextureUnit // Active texture unit
|
||||
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
|
||||
// Enable bits for the four texture coordinates
|
||||
|
||||
// Border color for each texture image
|
||||
@@ -291,17 +307,16 @@ public class Feature {
|
||||
// Magnification function for each texture image
|
||||
// Texture coordinates and wrap mode for each texture image
|
||||
|
||||
// Color and mode for each texture environment
|
||||
// Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
|
||||
// GL_TEXTURE_GEN_MODE setting for S, T, R, and Q
|
||||
// glTexGen plane equations for S, T, R, and Q
|
||||
// Current texture bindings (for example, GL_TEXTURE_BINDING_2D) - Below
|
||||
));
|
||||
|
||||
// Current Texture Bindings - GL_TEXTURE_BINDING_2D
|
||||
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i));
|
||||
// textureAttribs.add(GLStateManager.textures.getInfo(i))
|
||||
textureAttribs.add(GLStateManager.textures.getTexEnvState(i));
|
||||
}
|
||||
|
||||
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Software emulation of GL feedback mode
|
||||
*/
|
||||
public class FeedbackManager {
|
||||
|
||||
private static final int FB_3D = 0x01;
|
||||
private static final int FB_4D = 0x02;
|
||||
private static final int FB_COLOR = 0x04;
|
||||
private static final int FB_TEXTURE = 0x08;
|
||||
|
||||
private static int renderMode = GL11.GL_RENDER;
|
||||
private static FloatBuffer feedbackBuffer;
|
||||
private static int feedbackBufferSize;
|
||||
private static int feedbackMask;
|
||||
private static int feedbackCount;
|
||||
|
||||
private static final Matrix4f mvpMatrix = new Matrix4f();
|
||||
private static final Vector4f clipVec = new Vector4f();
|
||||
|
||||
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
|
||||
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
|
||||
|
||||
@FunctionalInterface
|
||||
private interface IndexReader {
|
||||
int read(int i);
|
||||
}
|
||||
|
||||
public static boolean isFeedbackMode() {
|
||||
return renderMode == GL11.GL_FEEDBACK;
|
||||
}
|
||||
|
||||
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
AngelicaTweaker.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
|
||||
return;
|
||||
}
|
||||
feedbackBuffer = buffer;
|
||||
feedbackBufferSize = buffer.remaining();
|
||||
feedbackMask = computeMask(type);
|
||||
feedbackCount = 0;
|
||||
}
|
||||
|
||||
public static int glRenderMode(int mode) {
|
||||
int result = switch (renderMode) {
|
||||
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
|
||||
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
|
||||
default -> 0;
|
||||
};
|
||||
|
||||
if (mode == GL11.GL_SELECT) {
|
||||
AngelicaTweaker.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
|
||||
}
|
||||
|
||||
feedbackCount = 0;
|
||||
renderMode = mode;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void glPassThrough(float token) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
feedbackToken(GL11.GL_PASS_THROUGH_TOKEN);
|
||||
feedbackToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
private static void feedbackToken(float token) {
|
||||
if (feedbackCount < feedbackBufferSize) {
|
||||
feedbackBuffer.put(feedbackBuffer.position() + feedbackCount, token);
|
||||
}
|
||||
feedbackCount++;
|
||||
}
|
||||
|
||||
private static void feedbackVertex(float winX, float winY, float winZ, float winW) {
|
||||
feedbackToken(winX);
|
||||
feedbackToken(winY);
|
||||
if ((feedbackMask & FB_3D) != 0) feedbackToken(winZ);
|
||||
if ((feedbackMask & FB_4D) != 0) feedbackToken(winW);
|
||||
if ((feedbackMask & FB_COLOR) != 0) {
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
}
|
||||
if ((feedbackMask & FB_TEXTURE) != 0) {
|
||||
feedbackToken(0f);
|
||||
feedbackToken(0f);
|
||||
feedbackToken(0f);
|
||||
feedbackToken(1f);
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformAndEmitVertex(float x, float y, float z) {
|
||||
clipVec.set(x, y, z, 1.0f);
|
||||
mvpMatrix.transform(clipVec);
|
||||
|
||||
final float w = clipVec.w;
|
||||
final float ndcX = clipVec.x / w;
|
||||
final float ndcY = clipVec.y / w;
|
||||
final float ndcZ = clipVec.z / w;
|
||||
|
||||
final ViewportState vp = GLStateManager.getViewportState();
|
||||
final float winX = vp.x + vp.width * (ndcX + 1.0f) * 0.5f;
|
||||
final float winY = vp.y + vp.height * (ndcY + 1.0f) * 0.5f;
|
||||
final float winZ = (float) ((vp.depthRangeFar - vp.depthRangeNear) * ndcZ
|
||||
+ (vp.depthRangeFar + vp.depthRangeNear)) * 0.5f;
|
||||
final float winW = 1.0f / w;
|
||||
|
||||
feedbackVertex(winX, winY, winZ, winW);
|
||||
}
|
||||
|
||||
private static int positionStride() {
|
||||
final int s = VertexAttribState.get(0).effectiveStride();
|
||||
return (s != 0) ? s : 12;
|
||||
}
|
||||
|
||||
private static long positionOffset() {
|
||||
return VertexAttribState.get(0).offset;
|
||||
}
|
||||
|
||||
public static void processDrawArrays(int mode, int first, int count) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
final long posOffset = positionOffset();
|
||||
final int byteCount = count * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
|
||||
|
||||
emitAllPrimitives(mode, count, i -> i, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
|
||||
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
|
||||
final int indexByteCount = indexCount * elementSize;
|
||||
ensureIndexReadbackCapacity(indexByteCount);
|
||||
indexReadbackBuf.clear().limit(indexByteCount);
|
||||
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
|
||||
final ByteBuffer indexBuf = indexReadbackBuf;
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < indexCount; i++) {
|
||||
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
|
||||
if (idx < minIdx) minIdx = idx;
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
final long posOffset = positionOffset();
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
|
||||
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, ByteBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFF);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, ShortBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFFFF);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, IntBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i));
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int count, int type, ByteBuffer indices) {
|
||||
final int pos = indices.position();
|
||||
final IndexReader reader = switch (type) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> i -> indices.get(pos + i) & 0xFF;
|
||||
case GL11.GL_UNSIGNED_SHORT -> i -> indices.getShort(pos + i * 2) & 0xFFFF;
|
||||
case GL11.GL_UNSIGNED_INT -> i -> indices.getInt(pos + i * 4);
|
||||
default -> i -> 0;
|
||||
};
|
||||
processDrawElementsImpl(mode, count, reader);
|
||||
}
|
||||
|
||||
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int idx = reader.read(i);
|
||||
if (idx < minIdx) minIdx = idx;
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
readVerticesFromVBO(minIdx, maxIdx, stride);
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
|
||||
}
|
||||
|
||||
private static void computeMVP() {
|
||||
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
|
||||
}
|
||||
|
||||
private static int computeMask(int type) {
|
||||
return switch (type) {
|
||||
case GL11.GL_2D -> 0;
|
||||
case GL11.GL_3D -> FB_3D;
|
||||
case GL11.GL_3D_COLOR -> FB_3D | FB_COLOR;
|
||||
case GL11.GL_3D_COLOR_TEXTURE -> FB_3D | FB_COLOR | FB_TEXTURE;
|
||||
case GL11.GL_4D_COLOR_TEXTURE -> FB_3D | FB_4D | FB_COLOR | FB_TEXTURE;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static void ensureReadbackCapacity(int bytes) {
|
||||
if (readbackBuf.capacity() < bytes) {
|
||||
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureIndexReadbackCapacity(int bytes) {
|
||||
if (indexReadbackBuf.capacity() < bytes) {
|
||||
indexReadbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
|
||||
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
|
||||
final long posOffset = positionOffset();
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
|
||||
}
|
||||
|
||||
private static void emitVertex(int vertexIndex, int stride) {
|
||||
final float x = readbackBuf.getFloat(vertexIndex * stride);
|
||||
final float y = readbackBuf.getFloat(vertexIndex * stride + 4);
|
||||
final float z = readbackBuf.getFloat(vertexIndex * stride + 8);
|
||||
transformAndEmitVertex(x, y, z);
|
||||
}
|
||||
|
||||
private static void emitAllPrimitives(int mode, int count, IndexReader reader, int stride) {
|
||||
switch (mode) {
|
||||
case GL11.GL_POINTS:
|
||||
for (int i = 0; i < count; i++) {
|
||||
feedbackToken(GL11.GL_POINT_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINES:
|
||||
for (int i = 0; i + 1 < count; i += 2) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINE_STRIP:
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINE_LOOP:
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
if (count > 2) {
|
||||
feedbackToken(GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(count - 1), stride);
|
||||
emitVertex(reader.read(0), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLES:
|
||||
for (int i = 0; i + 2 < count; i += 3) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLE_STRIP:
|
||||
for (int i = 0; i + 2 < count; i++) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
if ((i & 1) == 0) {
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
} else {
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLE_FAN:
|
||||
for (int i = 1; i + 1 < count; i++) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
emitVertex(reader.read(0), stride);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_QUADS:
|
||||
for (int i = 0; i + 3 < count; i += 4) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(4);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
emitVertex(reader.read(i + 3), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
|
||||
}
|
||||
}
|
||||
|
||||
static void reset() {
|
||||
renderMode = GL11.GL_RENDER;
|
||||
feedbackBuffer = null;
|
||||
feedbackBufferSize = 0;
|
||||
feedbackMask = 0;
|
||||
feedbackCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,16 @@ import org.lwjgl.opengl.AMDDebugOutput;
|
||||
import org.lwjgl.opengl.AMDDebugOutputCallback;
|
||||
import org.lwjgl.opengl.ARBDebugOutput;
|
||||
import org.lwjgl.opengl.ARBDebugOutputCallback;
|
||||
import org.lwjgl.opengl.EXTBlendColor;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.KHRDebug;
|
||||
import org.lwjgl.opengl.KHRDebugCallback;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
import static org.lwjgl.opengl.ARBDebugOutput.glDebugMessageCallbackARB;
|
||||
|
||||
@@ -31,20 +32,9 @@ public final class GLDebug {
|
||||
LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
|
||||
return 0;
|
||||
}
|
||||
return setupDebugMessageCallback(System.err);
|
||||
return setupDebugMessageCallbackImpl();
|
||||
}
|
||||
|
||||
private static void trace(Consumer<String> output) {
|
||||
/*
|
||||
* We can not just use a fixed stacktrace element offset, because some methods
|
||||
* are intercepted and some are not. So, check the package name.
|
||||
*/
|
||||
StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
|
||||
for (StackTraceElement ste : elems) {
|
||||
output.accept(ste.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static Throwable filterStackTrace(Throwable throwable, int offset) {
|
||||
StackTraceElement[] elems = throwable.getStackTrace();
|
||||
StackTraceElement[] filtered = new StackTraceElement[elems.length];
|
||||
@@ -62,36 +52,48 @@ public final class GLDebug {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
public static void printTrace(PrintStream stream) {
|
||||
trace(new Consumer<String>() {
|
||||
boolean first = true;
|
||||
private static String buildStackTrace() {
|
||||
StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StackTraceElement elem : elems) {
|
||||
sb.append("\n\t").append(elem.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void accept(String str) {
|
||||
if (first) {
|
||||
printDetail(stream, "Stacktrace", str);
|
||||
first = false;
|
||||
} else {
|
||||
printDetailLine(stream, "Stacktrace", str);
|
||||
}
|
||||
}
|
||||
});
|
||||
private static void logDebugMessage(int id, String source, String type, String severity, String message) {
|
||||
String fullMessage = String.format("[GL] %s %s (0x%X) from %s: %s%s", severity, type, id, source, message, buildStackTrace());
|
||||
|
||||
if ("HIGH".equals(severity)) {
|
||||
LOGGER.error(fullMessage);
|
||||
} else if ("MEDIUM".equals(severity)) {
|
||||
LOGGER.warn(fullMessage);
|
||||
} else {
|
||||
LOGGER.info(fullMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static void logDebugMessageAMD(int id, String category, String severity, String message) {
|
||||
String fullMessage = String.format("[GL] %s %s (0x%X): %s%s", severity, category, id, message, buildStackTrace());
|
||||
|
||||
if ("HIGH".equals(severity)) {
|
||||
LOGGER.error(fullMessage);
|
||||
} else if ("MEDIUM".equals(severity)) {
|
||||
LOGGER.warn(fullMessage);
|
||||
} else {
|
||||
LOGGER.info(fullMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up debug callbacks
|
||||
* @return 0 for failure, 1 for success, 2 for restart required.
|
||||
*/
|
||||
public static int setupDebugMessageCallback(PrintStream stream) {
|
||||
private static int setupDebugMessageCallbackImpl() {
|
||||
if (GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_KHR_debug) {
|
||||
LOGGER.info("[GL] Using OpenGL 4.3 for error logging.");
|
||||
KHRDebugCallback proc = new KHRDebugCallback((source, type, id, severity, message) -> {
|
||||
stream.println("[LWJGL] OpenGL debug message");
|
||||
printDetail(stream, "ID", String.format("0x%X", id));
|
||||
printDetail(stream, "Source", getDebugSource(source));
|
||||
printDetail(stream, "Type", getDebugType(type));
|
||||
printDetail(stream, "Severity", getDebugSeverity(severity));
|
||||
printDetail(stream, "Message", message);
|
||||
printTrace(stream);
|
||||
final KHRDebugCallback proc = new KHRDebugCallback((source, type, id, severity, message) -> {
|
||||
logDebugMessage(id, getDebugSource(source), getDebugType(type), getDebugSeverity(severity), message);
|
||||
});
|
||||
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);
|
||||
@@ -99,6 +101,9 @@ public final class GLDebug {
|
||||
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, null, false);
|
||||
GL43.glDebugMessageCallback(proc);
|
||||
|
||||
// Enable synchronous debug output so errors are reported immediately with accurate stack traces
|
||||
GL11.glEnable(GL43.GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
|
||||
if ((GL11.glGetInteger(GL30.GL_CONTEXT_FLAGS) & GL43.GL_CONTEXT_FLAG_DEBUG_BIT) == 0) {
|
||||
LOGGER.warn("[GL] Warning: A non-debug context may not produce any debug output.");
|
||||
GL11.glEnable(GL43.GL_DEBUG_OUTPUT);
|
||||
@@ -107,30 +112,22 @@ public final class GLDebug {
|
||||
return 1;
|
||||
} else if (GLStateManager.capabilities.GL_ARB_debug_output) {
|
||||
LOGGER.info("[GL] Using ARB_debug_output for error logging.");
|
||||
ARBDebugOutputCallback proc = new ARBDebugOutputCallback((source, type, id, severity, message) -> {
|
||||
stream.println("[LWJGL] ARB_debug_output message");
|
||||
printDetail(stream, "ID", String.format("0x%X", id));
|
||||
printDetail(stream, "Source", getSourceARB(source));
|
||||
printDetail(stream, "Type", getTypeARB(type));
|
||||
printDetail(stream, "Severity", getSeverityARB(severity));
|
||||
printDetail(stream, "Message", message);
|
||||
printTrace(stream);
|
||||
final ARBDebugOutputCallback proc = new ARBDebugOutputCallback((source, type, id, severity, message) -> {
|
||||
logDebugMessage(id, getSourceARB(source), getTypeARB(type), getSeverityARB(severity), message);
|
||||
});
|
||||
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
|
||||
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
|
||||
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
|
||||
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, null, false);
|
||||
ARBDebugOutput.glDebugMessageCallbackARB(proc);
|
||||
|
||||
// Enable synchronous debug output so errors are reported immediately with accurate stack traces
|
||||
GL11.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
|
||||
return 1;
|
||||
} else if (GLStateManager.capabilities.GL_AMD_debug_output) {
|
||||
LOGGER.info("[GL] Using AMD_debug_output for error logging.");
|
||||
AMDDebugOutputCallback proc = new AMDDebugOutputCallback((id, category, severity, message) -> {
|
||||
stream.println("[LWJGL] AMD_debug_output message");
|
||||
printDetail(stream, "ID", String.format("0x%X", id));
|
||||
printDetail(stream, "Category", getCategoryAMD(category));
|
||||
printDetail(stream, "Severity", getSeverityAMD(severity));
|
||||
printDetail(stream, "Message", message);
|
||||
printTrace(stream);
|
||||
final AMDDebugOutputCallback proc = new AMDDebugOutputCallback((id, category, severity, message) -> {
|
||||
logDebugMessageAMD(id, getCategoryAMD(category), getSeverityAMD(severity), message);
|
||||
});
|
||||
AMDDebugOutput.glDebugMessageEnableAMD(0, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
|
||||
AMDDebugOutput.glDebugMessageEnableAMD(0, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
|
||||
@@ -166,17 +163,6 @@ public final class GLDebug {
|
||||
}
|
||||
}
|
||||
|
||||
private static void printDetail(PrintStream stream, String type, String message) {
|
||||
stream.printf("\t%s: %s\n", type, message);
|
||||
}
|
||||
|
||||
private static void printDetailLine(PrintStream stream, String type, String message) {
|
||||
stream.append(" ");
|
||||
for (int i = 0; i < type.length(); i++) {
|
||||
stream.append(" ");
|
||||
}
|
||||
stream.append(message).append("\n");
|
||||
}
|
||||
|
||||
private static String getDebugSource(int source) {
|
||||
return switch (source) {
|
||||
@@ -269,6 +255,355 @@ public final class GLDebug {
|
||||
};
|
||||
}
|
||||
|
||||
public static String getMatrixModeName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_MODELVIEW -> "MODELVIEW";
|
||||
case GL11.GL_PROJECTION -> "PROJECTION";
|
||||
case GL11.GL_TEXTURE -> "TEXTURE";
|
||||
case GL11.GL_COLOR -> "COLOR";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getCapabilityName(int cap) {
|
||||
return switch (cap) {
|
||||
case GL11.GL_ALPHA_TEST -> "ALPHA_TEST";
|
||||
case GL11.GL_BLEND -> "BLEND";
|
||||
case GL11.GL_COLOR_MATERIAL -> "COLOR_MATERIAL";
|
||||
case GL11.GL_CULL_FACE -> "CULL_FACE";
|
||||
case GL11.GL_DEPTH_TEST -> "DEPTH_TEST";
|
||||
case GL11.GL_FOG -> "FOG";
|
||||
case GL11.GL_LIGHTING -> "LIGHTING";
|
||||
case GL11.GL_LINE_SMOOTH -> "LINE_SMOOTH";
|
||||
case GL11.GL_NORMALIZE -> "NORMALIZE";
|
||||
case GL11.GL_POINT_SMOOTH -> "POINT_SMOOTH";
|
||||
case GL11.GL_POLYGON_OFFSET_FILL -> "POLYGON_OFFSET_FILL";
|
||||
case GL11.GL_POLYGON_OFFSET_LINE -> "POLYGON_OFFSET_LINE";
|
||||
case GL11.GL_POLYGON_SMOOTH -> "POLYGON_SMOOTH";
|
||||
case GL12.GL_RESCALE_NORMAL -> "RESCALE_NORMAL";
|
||||
case GL11.GL_SCISSOR_TEST -> "SCISSOR_TEST";
|
||||
case GL11.GL_STENCIL_TEST -> "STENCIL_TEST";
|
||||
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
|
||||
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
|
||||
case GL11.GL_LIGHT0 -> "LIGHT0";
|
||||
case GL11.GL_LIGHT1 -> "LIGHT1";
|
||||
case GL11.GL_LIGHT2 -> "LIGHT2";
|
||||
case GL11.GL_LIGHT3 -> "LIGHT3";
|
||||
case GL11.GL_LIGHT4 -> "LIGHT4";
|
||||
case GL11.GL_LIGHT5 -> "LIGHT5";
|
||||
case GL11.GL_LIGHT6 -> "LIGHT6";
|
||||
case GL11.GL_LIGHT7 -> "LIGHT7";
|
||||
default -> String.format("0x%X", cap);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getComparisonFuncName(int func) {
|
||||
return switch (func) {
|
||||
case GL11.GL_NEVER -> "NEVER";
|
||||
case GL11.GL_LESS -> "LESS";
|
||||
case GL11.GL_EQUAL -> "EQUAL";
|
||||
case GL11.GL_LEQUAL -> "LEQUAL";
|
||||
case GL11.GL_GREATER -> "GREATER";
|
||||
case GL11.GL_NOTEQUAL -> "NOTEQUAL";
|
||||
case GL11.GL_GEQUAL -> "GEQUAL";
|
||||
case GL11.GL_ALWAYS -> "ALWAYS";
|
||||
default -> String.format("0x%X", func);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getBlendFactorName(int factor) {
|
||||
return switch (factor) {
|
||||
case GL11.GL_ZERO -> "ZERO";
|
||||
case GL11.GL_ONE -> "ONE";
|
||||
case GL11.GL_SRC_COLOR -> "SRC_COLOR";
|
||||
case GL11.GL_ONE_MINUS_SRC_COLOR -> "ONE_MINUS_SRC_COLOR";
|
||||
case GL11.GL_DST_COLOR -> "DST_COLOR";
|
||||
case GL11.GL_ONE_MINUS_DST_COLOR -> "ONE_MINUS_DST_COLOR";
|
||||
case GL11.GL_SRC_ALPHA -> "SRC_ALPHA";
|
||||
case GL11.GL_ONE_MINUS_SRC_ALPHA -> "ONE_MINUS_SRC_ALPHA";
|
||||
case GL11.GL_DST_ALPHA -> "DST_ALPHA";
|
||||
case GL11.GL_ONE_MINUS_DST_ALPHA -> "ONE_MINUS_DST_ALPHA";
|
||||
case EXTBlendColor.GL_CONSTANT_COLOR_EXT -> "CONSTANT_COLOR";
|
||||
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_COLOR_EXT -> "ONE_MINUS_CONSTANT_COLOR";
|
||||
case EXTBlendColor.GL_CONSTANT_ALPHA_EXT -> "CONSTANT_ALPHA";
|
||||
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_ALPHA_EXT -> "ONE_MINUS_CONSTANT_ALPHA";
|
||||
case GL11.GL_SRC_ALPHA_SATURATE -> "SRC_ALPHA_SATURATE";
|
||||
default -> String.format("0x%X", factor);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getShadeModelName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_FLAT -> "FLAT";
|
||||
case GL11.GL_SMOOTH -> "SMOOTH";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTextureTargetName(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
|
||||
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
|
||||
case GL12.GL_TEXTURE_3D -> "TEXTURE_3D";
|
||||
case GL13.GL_TEXTURE_CUBE_MAP -> "TEXTURE_CUBE_MAP";
|
||||
default -> String.format("0x%X", target);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTexturePnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_MIN_FILTER -> "TEXTURE_MIN_FILTER";
|
||||
case GL11.GL_TEXTURE_MAG_FILTER -> "TEXTURE_MAG_FILTER";
|
||||
case GL11.GL_TEXTURE_WRAP_S -> "TEXTURE_WRAP_S";
|
||||
case GL11.GL_TEXTURE_WRAP_T -> "TEXTURE_WRAP_T";
|
||||
case GL12.GL_TEXTURE_WRAP_R -> "TEXTURE_WRAP_R";
|
||||
case GL12.GL_TEXTURE_MIN_LOD -> "TEXTURE_MIN_LOD";
|
||||
case GL12.GL_TEXTURE_MAX_LOD -> "TEXTURE_MAX_LOD";
|
||||
case GL12.GL_TEXTURE_BASE_LEVEL -> "TEXTURE_BASE_LEVEL";
|
||||
case GL12.GL_TEXTURE_MAX_LEVEL -> "TEXTURE_MAX_LEVEL";
|
||||
case GL11.GL_TEXTURE_BORDER_COLOR -> "TEXTURE_BORDER_COLOR";
|
||||
case GL11.GL_TEXTURE_PRIORITY -> "TEXTURE_PRIORITY";
|
||||
case GL14.GL_TEXTURE_LOD_BIAS -> "TEXTURE_LOD_BIAS";
|
||||
case GL14.GL_GENERATE_MIPMAP -> "GENERATE_MIPMAP";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTextureFormatName(int format) {
|
||||
return switch (format) {
|
||||
case GL11.GL_RED -> "RED";
|
||||
case GL11.GL_GREEN -> "GREEN";
|
||||
case GL11.GL_BLUE -> "BLUE";
|
||||
case GL11.GL_ALPHA -> "ALPHA";
|
||||
case GL11.GL_RGB -> "RGB";
|
||||
case GL11.GL_RGBA -> "RGBA";
|
||||
case GL11.GL_LUMINANCE -> "LUMINANCE";
|
||||
case GL11.GL_LUMINANCE_ALPHA -> "LUMINANCE_ALPHA";
|
||||
case GL12.GL_BGR -> "BGR";
|
||||
case GL12.GL_BGRA -> "BGRA";
|
||||
case GL11.GL_DEPTH_COMPONENT -> "DEPTH_COMPONENT";
|
||||
case GL11.GL_STENCIL_INDEX -> "STENCIL_INDEX";
|
||||
case GL30.GL_DEPTH_STENCIL -> "DEPTH_STENCIL";
|
||||
// Internal formats
|
||||
case GL11.GL_RGB8 -> "RGB8";
|
||||
case GL11.GL_RGBA8 -> "RGBA8";
|
||||
case GL11.GL_ALPHA8 -> "ALPHA8";
|
||||
case GL11.GL_LUMINANCE8 -> "LUMINANCE8";
|
||||
case GL11.GL_LUMINANCE8_ALPHA8 -> "LUMINANCE8_ALPHA8";
|
||||
default -> String.format("0x%X", format);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getDataTypeName(int type) {
|
||||
return switch (type) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
|
||||
case GL11.GL_BYTE -> "BYTE";
|
||||
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
|
||||
case GL11.GL_SHORT -> "SHORT";
|
||||
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
|
||||
case GL11.GL_INT -> "INT";
|
||||
case GL11.GL_FLOAT -> "FLOAT";
|
||||
case GL11.GL_DOUBLE -> "DOUBLE";
|
||||
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
|
||||
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
|
||||
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
|
||||
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
|
||||
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
|
||||
default -> String.format("0x%X", type);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightName(int light) {
|
||||
return switch (light) {
|
||||
case GL11.GL_LIGHT0 -> "LIGHT0";
|
||||
case GL11.GL_LIGHT1 -> "LIGHT1";
|
||||
case GL11.GL_LIGHT2 -> "LIGHT2";
|
||||
case GL11.GL_LIGHT3 -> "LIGHT3";
|
||||
case GL11.GL_LIGHT4 -> "LIGHT4";
|
||||
case GL11.GL_LIGHT5 -> "LIGHT5";
|
||||
case GL11.GL_LIGHT6 -> "LIGHT6";
|
||||
case GL11.GL_LIGHT7 -> "LIGHT7";
|
||||
default -> String.format("0x%X", light);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_POSITION -> "POSITION";
|
||||
case GL11.GL_SPOT_DIRECTION -> "SPOT_DIRECTION";
|
||||
case GL11.GL_SPOT_EXPONENT -> "SPOT_EXPONENT";
|
||||
case GL11.GL_SPOT_CUTOFF -> "SPOT_CUTOFF";
|
||||
case GL11.GL_CONSTANT_ATTENUATION -> "CONSTANT_ATTENUATION";
|
||||
case GL11.GL_LINEAR_ATTENUATION -> "LINEAR_ATTENUATION";
|
||||
case GL11.GL_QUADRATIC_ATTENUATION -> "QUADRATIC_ATTENUATION";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getMaterialPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_EMISSION -> "EMISSION";
|
||||
case GL11.GL_SHININESS -> "SHININESS";
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
|
||||
case GL11.GL_COLOR_INDEXES -> "COLOR_INDEXES";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFaceName(int face) {
|
||||
return switch (face) {
|
||||
case GL11.GL_FRONT -> "FRONT";
|
||||
case GL11.GL_BACK -> "BACK";
|
||||
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
|
||||
default -> String.format("0x%X", face);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getColorMaterialModeName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_EMISSION -> "EMISSION";
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFogPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_FOG_MODE -> "FOG_MODE";
|
||||
case GL11.GL_FOG_DENSITY -> "FOG_DENSITY";
|
||||
case GL11.GL_FOG_START -> "FOG_START";
|
||||
case GL11.GL_FOG_END -> "FOG_END";
|
||||
case GL11.GL_FOG_INDEX -> "FOG_INDEX";
|
||||
case GL11.GL_FOG_COLOR -> "FOG_COLOR";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightModelPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_LIGHT_MODEL_AMBIENT -> "LIGHT_MODEL_AMBIENT";
|
||||
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> "LIGHT_MODEL_LOCAL_VIEWER";
|
||||
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> "LIGHT_MODEL_TWO_SIDE";
|
||||
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> "LIGHT_MODEL_COLOR_CONTROL";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getClearMaskString(int mask) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
sb.append("COLOR");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("DEPTH");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("STENCIL");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_ACCUM_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("ACCUM");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getCullFaceName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_FRONT -> "FRONT";
|
||||
case GL11.GL_BACK -> "BACK";
|
||||
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLogicOpName(int opcode) {
|
||||
return switch (opcode) {
|
||||
case GL11.GL_CLEAR -> "CLEAR";
|
||||
case GL11.GL_AND -> "AND";
|
||||
case GL11.GL_AND_REVERSE -> "AND_REVERSE";
|
||||
case GL11.GL_COPY -> "COPY";
|
||||
case GL11.GL_AND_INVERTED -> "AND_INVERTED";
|
||||
case GL11.GL_NOOP -> "NOOP";
|
||||
case GL11.GL_XOR -> "XOR";
|
||||
case GL11.GL_OR -> "OR";
|
||||
case GL11.GL_NOR -> "NOR";
|
||||
case GL11.GL_EQUIV -> "EQUIV";
|
||||
case GL11.GL_INVERT -> "INVERT";
|
||||
case GL11.GL_OR_REVERSE -> "OR_REVERSE";
|
||||
case GL11.GL_COPY_INVERTED -> "COPY_INVERTED";
|
||||
case GL11.GL_OR_INVERTED -> "OR_INVERTED";
|
||||
case GL11.GL_NAND -> "NAND";
|
||||
case GL11.GL_SET -> "SET";
|
||||
default -> String.format("0x%X", opcode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFramebufferStatusName(int status) {
|
||||
return switch (status) {
|
||||
case GL30.GL_FRAMEBUFFER_COMPLETE -> "COMPLETE";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT -> "INCOMPLETE_ATTACHMENT";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT -> "INCOMPLETE_MISSING_ATTACHMENT";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER -> "INCOMPLETE_DRAW_BUFFER";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER -> "INCOMPLETE_READ_BUFFER";
|
||||
case GL30.GL_FRAMEBUFFER_UNSUPPORTED -> "UNSUPPORTED";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE -> "INCOMPLETE_MULTISAMPLE";
|
||||
case 0 -> "NO_FRAMEBUFFER_BOUND";
|
||||
default -> String.format("UNKNOWN(0x%X)", status);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getInternalFormatName(int format) {
|
||||
return switch (format) {
|
||||
// Basic
|
||||
case GL11.GL_RGBA -> "RGBA";
|
||||
case GL11.GL_RGB -> "RGB";
|
||||
// 8-bit normalized
|
||||
case GL30.GL_R8 -> "R8";
|
||||
case GL30.GL_RG8 -> "RG8";
|
||||
case GL11.GL_RGB8 -> "RGB8";
|
||||
case GL11.GL_RGBA8 -> "RGBA8";
|
||||
// 8-bit signed normalized
|
||||
case 0x8F94 -> "R8_SNORM";
|
||||
case 0x8F95 -> "RG8_SNORM";
|
||||
case 0x8F96 -> "RGB8_SNORM";
|
||||
case 0x8F97 -> "RGBA8_SNORM";
|
||||
// 16-bit normalized
|
||||
case GL30.GL_R16 -> "R16";
|
||||
case GL30.GL_RG16 -> "RG16";
|
||||
case GL11.GL_RGB16 -> "RGB16";
|
||||
case GL11.GL_RGBA16 -> "RGBA16";
|
||||
// 16-bit float
|
||||
case GL30.GL_R16F -> "R16F";
|
||||
case GL30.GL_RG16F -> "RG16F";
|
||||
case GL30.GL_RGB16F -> "RGB16F";
|
||||
case GL30.GL_RGBA16F -> "RGBA16F";
|
||||
// 32-bit float
|
||||
case GL30.GL_R32F -> "R32F";
|
||||
case GL30.GL_RG32F -> "RG32F";
|
||||
case GL30.GL_RGB32F -> "RGB32F";
|
||||
case GL30.GL_RGBA32F -> "RGBA32F";
|
||||
// Packed
|
||||
case GL30.GL_R11F_G11F_B10F -> "R11F_G11F_B10F";
|
||||
case GL11.GL_RGB10_A2 -> "RGB10_A2";
|
||||
default -> String.format("0x%X", format);
|
||||
};
|
||||
}
|
||||
|
||||
private static DebugState debugState;
|
||||
|
||||
private interface DebugState {
|
||||
@@ -285,6 +620,7 @@ public final class GLDebug {
|
||||
private int depth = 0;
|
||||
private static final int maxDepth = GL11.glGetInteger(KHRDebug.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
|
||||
private static final int maxNameLength = GL11.glGetInteger(KHRDebug.GL_MAX_LABEL_LENGTH);
|
||||
private final String[] groupStack = new String[maxDepth + 1];
|
||||
|
||||
@Override
|
||||
public void nameObject(int id, int object, String name) {
|
||||
@@ -293,20 +629,28 @@ public final class GLDebug {
|
||||
|
||||
@Override
|
||||
public void pushGroup(String name) {
|
||||
depth++;
|
||||
if (depth > maxDepth) {
|
||||
throw new RuntimeException("Stack overflow");
|
||||
if (depth + 1 > maxDepth) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Debug group stack overflow (maxDepth=").append(maxDepth).append("), current stack:\n");
|
||||
for (int i = 0; i < depth; i++) {
|
||||
sb.append(" [").append(i).append("] ").append(groupStack[i]).append('\n');
|
||||
}
|
||||
sb.append(" -> ").append(name).append(" (rejected)");
|
||||
throw new RuntimeException(sb.toString());
|
||||
}
|
||||
groupStack[depth] = name;
|
||||
KHRDebug.glPushDebugGroup(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, ID, name);
|
||||
depth++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popGroup() {
|
||||
depth--;
|
||||
if (depth < 0) {
|
||||
if (depth - 1 < 0) {
|
||||
throw new RuntimeException("Stack underflow");
|
||||
}
|
||||
KHRDebug.glPopDebugGroup();
|
||||
depth--;
|
||||
groupStack[depth] = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,111 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Shared GLSL shader transformation utilities.
|
||||
*/
|
||||
public class GlslTransformUtils {
|
||||
|
||||
private static final String RENAMED_PREFIX = "angelica_renamed_";
|
||||
|
||||
/** Texture function renames */
|
||||
public static final Map<String, String> TEXTURE_RENAMES = Map.ofEntries(
|
||||
Map.entry("texture2D", "texture"),
|
||||
Map.entry("texture3D", "texture"),
|
||||
Map.entry("texture2DLod", "textureLod"),
|
||||
Map.entry("texture3DLod", "textureLod"),
|
||||
Map.entry("texture2DProj", "textureProj"),
|
||||
Map.entry("texture3DProj", "textureProj"),
|
||||
Map.entry("texture2DGrad", "textureGrad"),
|
||||
Map.entry("texture2DGradARB", "textureGrad"),
|
||||
Map.entry("texture3DGrad", "textureGrad"),
|
||||
Map.entry("texelFetch2D", "texelFetch"),
|
||||
Map.entry("texelFetch3D", "texelFetch"),
|
||||
Map.entry("textureSize2D", "textureSize")
|
||||
);
|
||||
|
||||
private static final Pattern TEXTURE_PATTERN = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
|
||||
|
||||
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
|
||||
private record ReservedWordRename(Pattern pattern, String replacement) {}
|
||||
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
|
||||
400, List.of(new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"))
|
||||
);
|
||||
|
||||
public static String replaceTexture(String input) {
|
||||
final Matcher matcher = TEXTURE_PATTERN.matcher(input);
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
if (matcher.group(1) != null) {
|
||||
matcher.appendReplacement(builder, RENAMED_PREFIX + "texture");
|
||||
} else {
|
||||
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
|
||||
}
|
||||
}
|
||||
matcher.appendTail(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String renameReservedWords(String source, int targetVersion) {
|
||||
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
|
||||
if (targetVersion < entry.getKey()) {
|
||||
for (var rename : entry.getValue()) {
|
||||
source = rename.pattern().matcher(source).replaceAll(rename.replacement());
|
||||
}
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
public static String restoreReservedWords(String source) {
|
||||
source = source.replace(RENAMED_PREFIX + "texture", "texture");
|
||||
source = source.replace(RENAMED_PREFIX + "sample", "sample");
|
||||
return source;
|
||||
}
|
||||
|
||||
public static String getFormattedShader(ParseTree tree, String header) {
|
||||
final StringBuilder sb = new StringBuilder(header + "\n");
|
||||
final String[] tabHolder = {""};
|
||||
getFormattedShader(tree, sb, tabHolder);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
|
||||
if (tree instanceof TerminalNode) {
|
||||
final String text = tree.getText();
|
||||
if (text.equals("<EOF>")) {
|
||||
return;
|
||||
}
|
||||
if (text.equals("#")) {
|
||||
stringBuilder.append("\n#");
|
||||
return;
|
||||
}
|
||||
stringBuilder.append(text);
|
||||
if (text.equals("{")) {
|
||||
stringBuilder.append(" \n\t");
|
||||
tabHolder[0] = "\t";
|
||||
}
|
||||
|
||||
if (text.equals("}")) {
|
||||
if (stringBuilder.length() >= 2) {
|
||||
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
|
||||
}
|
||||
tabHolder[0] = "";
|
||||
stringBuilder.append(" \n");
|
||||
} else {
|
||||
stringBuilder.append(text.equals(";") ? " \n" + tabHolder[0] : " ");
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < tree.getChildCount(); ++i) {
|
||||
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
|
||||
|
||||
/**
|
||||
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
|
||||
*
|
||||
* <p>Maintains a single growable element buffer with pre-computed indices mapping
|
||||
* GL_QUADS vertex order to GL_TRIANGLES: {@code (0,1,3, 1,2,3)} per quad.
|
||||
* Uses {@code GL_LAST_VERTEX_CONVENTION} winding (Mesa {@code do_quad} LAST path)
|
||||
* so both emitted triangles share the quad's provoking vertex (v3).
|
||||
*/
|
||||
public final class QuadConverter {
|
||||
public static final int INDEX_TYPE = GL11.GL_UNSIGNED_INT;
|
||||
|
||||
private static int eboId;
|
||||
private static int maxQuads;
|
||||
|
||||
/** Scratch EBO for converted quad-element draws (drawQuadElementsAsTriangles). */
|
||||
private static int scratchEboId;
|
||||
private static int scratchEboCapacity; // in bytes
|
||||
|
||||
private QuadConverter() {}
|
||||
|
||||
private static void ensureCapacity(int quadCount) {
|
||||
if (quadCount <= maxQuads) return;
|
||||
|
||||
// Round up to power of 2 for amortized growth
|
||||
int newMaxQuads = Math.max(256, maxQuads);
|
||||
while (newMaxQuads < quadCount) {
|
||||
newMaxQuads *= 2;
|
||||
}
|
||||
|
||||
if (eboId == 0) {
|
||||
eboId = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
// Generate index data: 6 ints per quad
|
||||
final ByteBuffer indexData = memAlloc(newMaxQuads * 6 * 4);
|
||||
long ptr = memAddress0(indexData);
|
||||
for (int i = 0; i < newMaxQuads; i++) {
|
||||
final int base = i * 4;
|
||||
// GL_LAST_VERTEX_CONVENTION: both triangles share v3 as provoking vertex
|
||||
// Triangle 1: 0, 1, 3
|
||||
memPutInt(ptr, base);
|
||||
memPutInt(ptr + 4, base + 1);
|
||||
memPutInt(ptr + 8, base + 3);
|
||||
// Triangle 2: 1, 2, 3
|
||||
memPutInt(ptr + 12, base + 1);
|
||||
memPutInt(ptr + 16, base + 2);
|
||||
memPutInt(ptr + 20, base + 3);
|
||||
ptr += 24;
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
|
||||
|
||||
memFree(indexData);
|
||||
maxQuads = newMaxQuads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a GL_QUADS glDrawArrays call to indexed GL_TRIANGLES.
|
||||
* Binds the shared EBO to the current VAO and issues glDrawElements.
|
||||
*
|
||||
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
|
||||
* @param vertexCount number of vertices (must be multiple of 4)
|
||||
*/
|
||||
public static void drawQuadsAsTriangles(int first, int vertexCount) {
|
||||
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
|
||||
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
|
||||
final int quadCount = vertexCount / 4;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
|
||||
final long indexOffset = (long) (first / 4) * 6 * 4;
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload converted triangle indices to the scratch EBO and draw.
|
||||
* Saves/restores the caller's EBO binding so subsequent indexed draws
|
||||
* (or glGetBufferSubData reads) still see the original EBO.
|
||||
*
|
||||
* @param dst off-heap buffer with triangle index data (freed by this method)
|
||||
* @param triIndexCount number of triangle indices
|
||||
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
|
||||
* @param bytesPerIndex bytes per index element
|
||||
*/
|
||||
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
|
||||
final int needed = triIndexCount * bytesPerIndex;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
|
||||
if (scratchEboId == 0) {
|
||||
scratchEboId = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
|
||||
|
||||
if (needed > scratchEboCapacity) {
|
||||
// Power-of-2 growth — allocate full capacity, upload actual data
|
||||
int newCap = Math.max(4096, scratchEboCapacity);
|
||||
while (newCap < needed) newCap *= 2;
|
||||
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
|
||||
scratchEboCapacity = newCap;
|
||||
}
|
||||
GL15.glBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
|
||||
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
memFree(dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an indexed GL_QUADS draw to GL_TRIANGLES by reading the source EBO,
|
||||
* building a triangle index buffer, and issuing the converted draw.
|
||||
*
|
||||
* @param indexCount number of quad indices (must be multiple of 4)
|
||||
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
|
||||
* @param offset byte offset into the currently bound EBO
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
|
||||
|
||||
// Read source indices from caller's EBO
|
||||
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
|
||||
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
|
||||
final long srcAddr = memAddress0(src);
|
||||
|
||||
// Allocate output as GL_UNSIGNED_INT
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a, b, c, d;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
final long p = srcAddr + (long) i * 16;
|
||||
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
final long p = srcAddr + (long) i * 8;
|
||||
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
|
||||
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
|
||||
}
|
||||
default /* GL_UNSIGNED_BYTE */ -> {
|
||||
final long p = srcAddr + (long) i * 4;
|
||||
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
|
||||
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
|
||||
}
|
||||
}
|
||||
// GL_LAST_VERTEX_CONVENTION: (a,b,d), (b,c,d)
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a);
|
||||
memPutInt(dstAddr + o + 4, b);
|
||||
memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b);
|
||||
memPutInt(dstAddr + o + 16, c);
|
||||
memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
memFree(src);
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side IntBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
final int basePos = indices.position();
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = basePos + i * 4;
|
||||
final int a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ShortBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 2);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
final int basePos = indices.position();
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = basePos + i * 4;
|
||||
final short a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
|
||||
final long o = (long) i * 12;
|
||||
memPutShort(dstAddr + o, a); memPutShort(dstAddr + o + 2, b); memPutShort(dstAddr + o + 4, d);
|
||||
memPutShort(dstAddr + o + 6, b); memPutShort(dstAddr + o + 8, c); memPutShort(dstAddr + o + 10, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
|
||||
if (count == 0) return;
|
||||
assert count % 4 == 0;
|
||||
final int quadCount = count / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
final long srcAddr = memAddress0(indices) + indices.position();
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a, b, c, d;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
final long p = srcAddr + (long) i * 16;
|
||||
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
final long p = srcAddr + (long) i * 8;
|
||||
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
|
||||
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
|
||||
}
|
||||
case GL11.GL_UNSIGNED_BYTE -> {
|
||||
final long p = srcAddr + (long) i * 4;
|
||||
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
|
||||
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
|
||||
}
|
||||
default -> { memFree(dst); return; }
|
||||
}
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the shared EBO.
|
||||
*/
|
||||
public static void destroy() {
|
||||
if (eboId != 0) {
|
||||
GL15.glDeleteBuffers(eboId);
|
||||
eboId = 0;
|
||||
maxQuads = 0;
|
||||
}
|
||||
if (scratchEboId != 0) {
|
||||
GL15.glDeleteBuffers(scratchEboId);
|
||||
scratchEboId = 0;
|
||||
scratchEboCapacity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import net.coderbot.iris.gl.shader.StandardMacros;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3i;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.ARBBufferStorage;
|
||||
import org.lwjgl.opengl.ARBClearBufferObject;
|
||||
import org.lwjgl.opengl.ARBClearTexture;
|
||||
import org.lwjgl.opengl.ARBShaderImageLoadStore;
|
||||
import org.lwjgl.opengl.ARBShaderStorageBufferObject;
|
||||
import org.lwjgl.opengl.EXTShaderImageLoadStore;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
import org.lwjgl.opengl.NVXGpuMemoryInfo;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
@@ -31,13 +45,26 @@ public class RenderSystem {
|
||||
private static final Logger LOGGER = LogManager.getLogger("RenderSystem");
|
||||
private static DSAAccess dsaState;
|
||||
private static boolean supportsCompute;
|
||||
private static boolean supportsImageLoadStore;
|
||||
private static boolean supportsSSBO;
|
||||
private static boolean supportsBufferStorage;
|
||||
private static boolean supportsClearTexture;
|
||||
private static boolean supportsTesselation;
|
||||
private static boolean supportsSamplerObjects;
|
||||
private static int maxImageUnits;
|
||||
private static int maxSSBOBindings;
|
||||
private static int maxGlslVersion;
|
||||
private static boolean supportsGpuShader4;
|
||||
|
||||
// Sampler object state tracking (null if unsupported)
|
||||
private static int[] samplers;
|
||||
|
||||
private RenderSystem() {}
|
||||
|
||||
public static void initRenderer() {
|
||||
try {
|
||||
if (GLStateManager.capabilities.OpenGL45) {
|
||||
dsaState = new DSACore();
|
||||
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
|
||||
LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
|
||||
}
|
||||
|
||||
@@ -54,6 +81,59 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
supportsCompute = supportsCompute();
|
||||
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
|
||||
|
||||
supportsImageLoadStore = GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_shader_image_load_store || GLStateManager.capabilities.GL_EXT_shader_image_load_store;
|
||||
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
|
||||
supportsBufferStorage = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage;
|
||||
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
|
||||
|
||||
// Cache maximum image units
|
||||
if (supportsImageLoadStore) {
|
||||
if (GLStateManager.capabilities.OpenGL42) {
|
||||
maxImageUnits = GL11.glGetInteger(GL42.GL_MAX_IMAGE_UNITS);
|
||||
} else if (GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
|
||||
maxImageUnits = GL11.glGetInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS);
|
||||
} else {
|
||||
maxImageUnits = GL11.glGetInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
|
||||
}
|
||||
} else {
|
||||
maxImageUnits = 0;
|
||||
}
|
||||
|
||||
// Cache maximum SSBO bindings
|
||||
if (supportsSSBO) {
|
||||
if (GLStateManager.capabilities.OpenGL43) {
|
||||
maxSSBOBindings = GL11.glGetInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
|
||||
} else {
|
||||
maxSSBOBindings = GL11.glGetInteger(ARBShaderStorageBufferObject.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
|
||||
}
|
||||
} else {
|
||||
maxSSBOBindings = 0;
|
||||
}
|
||||
|
||||
// Check for sampler objects support (GL 3.3+ or ARB extension)
|
||||
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33
|
||||
|| GLStateManager.capabilities.GL_ARB_sampler_objects;
|
||||
if (supportsSamplerObjects) {
|
||||
samplers = new int[GL11.glGetInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
|
||||
}
|
||||
|
||||
maxGlslVersion = Integer.parseInt(StandardMacros.getGlVersion(GL20.GL_SHADING_LANGUAGE_VERSION));
|
||||
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
|
||||
|
||||
LOGGER.info("Max GLSL version: {}, GPU Shader4: {}", maxGlslVersion, supportsGpuShader4);
|
||||
LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
|
||||
LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
|
||||
LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
|
||||
|
||||
if (GLStateManager.capabilities.OpenGL32) {
|
||||
final int profileMask = GL11.glGetInteger(GL32.GL_CONTEXT_PROFILE_MASK);
|
||||
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
|
||||
LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
|
||||
ShaderManager.getInstance().enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateMipmaps(int texture, int mipmapTarget) {
|
||||
@@ -65,8 +145,11 @@ 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) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
|
||||
@@ -74,7 +157,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
|
||||
GL11.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
|
||||
GLStateManager.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
|
||||
}
|
||||
|
||||
public static void uniform1f(int location, float v0) {
|
||||
@@ -131,6 +214,33 @@ public class RenderSystem {
|
||||
dsaState.texParameterf(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
|
||||
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexImage1D(target, level, internalformat, width, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL12.glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static String getProgramInfoLog(int program) {
|
||||
return GL20.glGetProgramInfoLog(program, GL20.glGetProgrami(program, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
@@ -179,6 +289,10 @@ public class RenderSystem {
|
||||
return dsaState.getTexParameteri(texture, target, pname);
|
||||
}
|
||||
|
||||
public static int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
return dsaState.getTexLevelParameteri(texture, level, pname);
|
||||
}
|
||||
|
||||
public static void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
|
||||
if (GLStateManager.capabilities.OpenGL42) {
|
||||
GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format);
|
||||
@@ -188,13 +302,27 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int getMaxImageUnits() {
|
||||
if (GLStateManager.capabilities.OpenGL42) {
|
||||
return GL11.glGetInteger(GL42.GL_MAX_IMAGE_UNITS);
|
||||
} else if (GLStateManager.capabilities.GL_EXT_shader_image_load_store) {
|
||||
return GL11.glGetInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return maxImageUnits;
|
||||
}
|
||||
|
||||
public static boolean supportsImageLoadStore() {
|
||||
return supportsImageLoadStore;
|
||||
}
|
||||
|
||||
public static boolean supportsSSBO() {
|
||||
return supportsSSBO;
|
||||
}
|
||||
|
||||
public static boolean supportsBufferStorage() {
|
||||
return supportsBufferStorage;
|
||||
}
|
||||
|
||||
public static boolean supportsClearTexture() {
|
||||
return supportsClearTexture;
|
||||
}
|
||||
|
||||
public static int getMaxSSBOBindings() {
|
||||
return maxSSBOBindings;
|
||||
}
|
||||
|
||||
public static void getProgramiv(int program, int value, IntBuffer storage) {
|
||||
@@ -209,6 +337,14 @@ public class RenderSystem {
|
||||
GL43.glDispatchCompute(workGroups.x, workGroups.y, workGroups.z);
|
||||
}
|
||||
|
||||
public static void dispatchComputeIndirect(long offset) {
|
||||
GL43.glDispatchComputeIndirect(offset);
|
||||
}
|
||||
|
||||
public static void bindBuffer(int target, int buffer) {
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
public static void memoryBarrier(int barriers) {
|
||||
if (supportsCompute) {
|
||||
GL42.glMemoryBarrier(barriers);
|
||||
@@ -221,10 +357,12 @@ public class RenderSystem {
|
||||
|
||||
public static void disableBufferBlend(int buffer) {
|
||||
GL30.glDisablei(GL11.GL_BLEND, buffer);
|
||||
GLStateManager.getBlendMode().setUnknownState();
|
||||
}
|
||||
|
||||
public static void enableBufferBlend(int buffer) {
|
||||
GL30.glEnablei(GL11.GL_BLEND, buffer);
|
||||
GLStateManager.getBlendMode().setUnknownState();
|
||||
}
|
||||
|
||||
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
@@ -235,19 +373,23 @@ public class RenderSystem {
|
||||
dsaState.bindTextureToUnit(unit, texture);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int target, int unit, int texture) {
|
||||
dsaState.bindTextureToUnit(target, unit, texture);
|
||||
}
|
||||
|
||||
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
|
||||
public static void setupProjectionMatrix(Matrix4f matrix) {
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glPushMatrix();
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPushMatrix();
|
||||
matrix.get(0, PROJECTION_MATRIX_BUFFER);
|
||||
GL11.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
public static void restoreProjectionMatrix() {
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glPopMatrix();
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter) {
|
||||
@@ -270,4 +412,122 @@ public class RenderSystem {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int createBuffers() {
|
||||
return GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
public static void bindBufferBase(int target, int index, int buffer) {
|
||||
GL30.glBindBufferBase(target, index, buffer);
|
||||
}
|
||||
|
||||
public static void bufferStorage(int target, long size, int flags) {
|
||||
if (GLStateManager.capabilities.OpenGL44) {
|
||||
GL44.glBufferStorage(target, size, flags);
|
||||
} else if (GLStateManager.capabilities.GL_ARB_buffer_storage) {
|
||||
ARBBufferStorage.glBufferStorage(target, size, flags);
|
||||
} else {
|
||||
// Fallback: use mutable storage
|
||||
GL15.glBufferData(target, size, GL15.GL_DYNAMIC_DRAW);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
|
||||
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
|
||||
buf.asIntBuffer().put(data);
|
||||
if (GLStateManager.capabilities.OpenGL43) {
|
||||
GL43.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
|
||||
} else if (GLStateManager.capabilities.GL_ARB_clear_buffer_object) {
|
||||
ARBClearBufferObject.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteBuffers(int buffer) {
|
||||
GL15.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
public static long getVRAM() {
|
||||
if (GLStateManager.capabilities.GL_NVX_gpu_memory_info) {
|
||||
return GL11.glGetInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1024L;
|
||||
} else {
|
||||
// Default to 4GB if we can't query VRAM
|
||||
return 4294967296L;
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearTexImage(int texture, int target, int level, int format, int type) {
|
||||
if (GLStateManager.capabilities.OpenGL44) {
|
||||
GL44.glClearTexImage(texture, level, format, type, (ByteBuffer) null);
|
||||
} else {
|
||||
ARBClearTexture.glClearTexImage(texture, level, format, type, (ByteBuffer) null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
dsaState.textureStorage1D(texture, target, levels, internalFormat, width);
|
||||
}
|
||||
|
||||
public static void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
dsaState.textureStorage2D(texture, target, levels, internalFormat, width, height);
|
||||
}
|
||||
|
||||
public static void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
dsaState.textureStorage3D(texture, target, levels, internalFormat, width, height, depth);
|
||||
}
|
||||
|
||||
public static int getMaxGlslVersion() {
|
||||
return maxGlslVersion;
|
||||
}
|
||||
|
||||
public static boolean supportsGpuShader4() {
|
||||
return supportsGpuShader4;
|
||||
}
|
||||
|
||||
public static boolean supportsSnormFormats() {
|
||||
return GLStateManager.capabilities.OpenGL31;
|
||||
}
|
||||
|
||||
public static boolean supportsPackedFloatRenderable() {
|
||||
return GLStateManager.capabilities.OpenGL30 && maxGlslVersion >= 130;
|
||||
}
|
||||
|
||||
public static boolean supportsTesselation() {
|
||||
return supportsTesselation;
|
||||
}
|
||||
|
||||
public static boolean supportsSamplerObjects() {
|
||||
return supportsSamplerObjects;
|
||||
}
|
||||
|
||||
public static int genSampler() {
|
||||
if (!supportsSamplerObjects) return 0;
|
||||
return GL33.glGenSamplers();
|
||||
}
|
||||
|
||||
public static void destroySampler(int sampler) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
GL33.glDeleteSamplers(sampler);
|
||||
}
|
||||
|
||||
public static void samplerParameteri(int sampler, int pname, int param) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
GL33.glSamplerParameteri(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void bindSamplerToUnit(int unit, int sampler) {
|
||||
if (!supportsSamplerObjects) return;
|
||||
if (samplers[unit] == sampler) return;
|
||||
GL33.glBindSampler(unit, sampler);
|
||||
samplers[unit] = sampler;
|
||||
}
|
||||
|
||||
public static void unbindAllSamplers() {
|
||||
if (!supportsSamplerObjects) return;
|
||||
for (int i = 0; i < samplers.length; i++) {
|
||||
if (samplers[i] != 0) {
|
||||
GL33.glBindSampler(i, 0);
|
||||
samplers[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.MultMatrixCmd;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class for tracking and collapsing matrix transforms during optimization.
|
||||
* Implements the transform accumulation and emission strategy:
|
||||
* <ul>
|
||||
* <li>accumulated: total relative transform (what GL matrix should be)</li>
|
||||
* <li>lastEmitted: what we've emitted (what GL matrix is)</li>
|
||||
* <li>stack: saved accumulated values at Push points</li>
|
||||
* </ul>
|
||||
*/
|
||||
class TransformOptimizer {
|
||||
private static final Logger LOGGER = LogManager.getLogger("TransformOptimizer");
|
||||
|
||||
private final Matrix4f accumulated = new Matrix4f(); // Current accumulated transform
|
||||
private final Matrix4f lastEmitted = new Matrix4f(); // Last emitted transform
|
||||
private final Deque<Matrix4f> stack = new ArrayDeque<>(); // For Push/Pop
|
||||
private final int listId; // For logging
|
||||
private boolean absoluteMatrixFlag = false; // True after LoadMatrix, cleared by LoadIdentity emit
|
||||
|
||||
TransformOptimizer(int listId) {
|
||||
this.listId = listId;
|
||||
accumulated.identity();
|
||||
lastEmitted.identity();
|
||||
}
|
||||
|
||||
Matrix4f getAccumulated() {
|
||||
return accumulated;
|
||||
}
|
||||
|
||||
void loadIdentity() {
|
||||
accumulated.identity();
|
||||
}
|
||||
|
||||
void pushTransform() {
|
||||
stack.push(new Matrix4f(accumulated));
|
||||
}
|
||||
|
||||
void popTransform() {
|
||||
if (!stack.isEmpty()) {
|
||||
final Matrix4f popped = stack.pop();
|
||||
accumulated.set(popped);
|
||||
// After Pop, GL state is restored to the pushed value so lastEmitted should also reflect this
|
||||
lastEmitted.set(accumulated);
|
||||
} else {
|
||||
LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
|
||||
accumulated.identity();
|
||||
lastEmitted.identity();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isIdentity() {
|
||||
return DisplayListManager.isIdentity(accumulated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's a pending transform that hasn't been emitted yet.
|
||||
*/
|
||||
boolean hasPendingTransform() {
|
||||
return !accumulated.equals(lastEmitted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a MultMatrix command if accumulated differs from lastEmitted.
|
||||
* Updates lastEmitted to match accumulated after emission.
|
||||
*/
|
||||
void emitPendingTransform(List<DisplayListCommand> output) {
|
||||
if (!accumulated.equals(lastEmitted)) {
|
||||
emitTransformTo(output, accumulated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a MultMatrix command to make GL state match a target transform.
|
||||
* Used when a draw expects a specific transform that may differ from accumulated.
|
||||
* Updates lastEmitted to match target after emission.
|
||||
*
|
||||
* @param output The command list to emit to
|
||||
* @param target The target transform to reach
|
||||
*/
|
||||
void emitTransformTo(List<DisplayListCommand> output, Matrix4f target) {
|
||||
if (target.equals(lastEmitted)) {
|
||||
// Already at target - nothing to emit
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate delta: what we need to multiply to go from lastEmitted to target
|
||||
// delta = inverse(lastEmitted) * target
|
||||
// But for simplicity, if lastEmitted is identity, just emit target
|
||||
if (DisplayListManager.isIdentity(lastEmitted)) {
|
||||
// Simple case: just emit target
|
||||
output.add(MultMatrixCmd.create(target));
|
||||
} else {
|
||||
// Need to emit delta: inv(lastEmitted) * target
|
||||
final Matrix4f delta = new Matrix4f(lastEmitted).invert().mul(target);
|
||||
output.add(MultMatrixCmd.create(delta));
|
||||
}
|
||||
lastEmitted.set(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a draw's expected transform differs from what we've emitted.
|
||||
*/
|
||||
boolean needsTransformForDraw(Matrix4f drawTransform) {
|
||||
return !drawTransform.equals(lastEmitted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark that the GL matrix is at an absolute value (after LoadMatrix).
|
||||
* Subsequent LoadIdentity commands must be emitted to actually reset GL to identity.
|
||||
*/
|
||||
void markAbsoluteMatrix() {
|
||||
absoluteMatrixFlag = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the GL matrix is at an absolute value and clear the flag.
|
||||
* Used to determine if LoadIdentity needs to emit.
|
||||
* @return true if LoadMatrix was called and not yet reset by LoadIdentity
|
||||
*/
|
||||
boolean checkAndClearAbsoluteMatrix() {
|
||||
boolean was = absoluteMatrixFlag;
|
||||
absoluteMatrixFlag = false;
|
||||
return was;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat;
|
||||
|
||||
/**
|
||||
* Simple holder for fog color values captured from vanilla rendering.
|
||||
* Used by terrain renderers to access fog state.
|
||||
*/
|
||||
public class FogHelper {
|
||||
public static float red;
|
||||
public static float green;
|
||||
public static float blue;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.util.glu.Cylinder;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_LINES;
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
|
||||
import static org.lwjgl.opengl.GL11.GL_POINTS;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FILL;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_LINE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_POINT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
|
||||
|
||||
/**
|
||||
* Core-profile compatible replacement for {@link Cylinder}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaCylinder extends Cylinder {
|
||||
|
||||
private static final float PI = (float) Math.PI;
|
||||
|
||||
private void emitNormal(float x, float y, float z) {
|
||||
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
|
||||
if (mag > 0.00001F) {
|
||||
x /= mag;
|
||||
y /= mag;
|
||||
z /= mag;
|
||||
}
|
||||
GLStateManager.glNormal3f(x, y, z);
|
||||
}
|
||||
|
||||
private void emitTexCoord(float x, float y) {
|
||||
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(float baseRadius, float topRadius, float height, int slices, int stacks) {
|
||||
final float da, dr, dz, nz, nsign;
|
||||
float r, x, y, z;
|
||||
|
||||
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
|
||||
|
||||
da = 2.0f * PI / slices;
|
||||
dr = (topRadius - baseRadius) / stacks;
|
||||
dz = height / stacks;
|
||||
nz = (baseRadius - topRadius) / height;
|
||||
|
||||
if (super.drawStyle == GLU_POINT) {
|
||||
GLStateManager.glBegin(GL_POINTS);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
|
||||
z = 0.0f;
|
||||
r = baseRadius;
|
||||
for (int j = 0; j <= stacks; j++) {
|
||||
GLStateManager.glVertex3f(x * r, y * r, z);
|
||||
z += dz;
|
||||
r += dr;
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
|
||||
// Draw rings
|
||||
if (super.drawStyle == GLU_LINE) {
|
||||
z = 0.0f;
|
||||
r = baseRadius;
|
||||
for (int j = 0; j <= stacks; j++) {
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
GLStateManager.glVertex3f(x * r, y * r, z);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
z += dz;
|
||||
r += dr;
|
||||
}
|
||||
} else {
|
||||
// draw one ring at each end
|
||||
if (baseRadius != 0.0) {
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
// draw length lines
|
||||
GLStateManager.glBegin(GL_LINES);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
|
||||
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
} else if (super.drawStyle == GLU_FILL) {
|
||||
final float ds = 1.0f / slices;
|
||||
final float dt = 1.0f / stacks;
|
||||
float t = 0.0f;
|
||||
z = 0.0f;
|
||||
r = baseRadius;
|
||||
for (int j = 0; j < stacks; j++) {
|
||||
float s = 0.0f;
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
final float angle = (i == slices) ? 0.0f : i * da;
|
||||
x = sin(angle);
|
||||
y = cos(angle);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
emitTexCoord(s, t);
|
||||
GLStateManager.glVertex3f(x * r, y * r, z);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
emitTexCoord(s, t + dt);
|
||||
GLStateManager.glVertex3f(x * (r + dr), y * (r + dr), z + dz);
|
||||
s += ds;
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
r += dr;
|
||||
t += dt;
|
||||
z += dz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.util.glu.Disk;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
|
||||
import static org.lwjgl.opengl.GL11.GL_POINTS;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FILL;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_LINE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_NONE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_POINT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
|
||||
|
||||
/**
|
||||
* Core-profile compatible replacement for {@link Disk}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaDisk extends Disk {
|
||||
|
||||
private static final float PI = (float) Math.PI;
|
||||
|
||||
private void emitTexCoord(float x, float y) {
|
||||
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(float innerRadius, float outerRadius, int slices, int loops) {
|
||||
final float da, dr;
|
||||
|
||||
if (super.normals != GLU_NONE) {
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
|
||||
}
|
||||
|
||||
da = 2.0f * PI / slices;
|
||||
dr = (outerRadius - innerRadius) / loops;
|
||||
|
||||
switch (super.drawStyle) {
|
||||
case GLU_FILL -> {
|
||||
final float dtc = 2.0f * outerRadius;
|
||||
float sa, ca;
|
||||
float r1 = innerRadius;
|
||||
for (int l = 0; l < loops; l++) {
|
||||
final float r2 = r1 + dr;
|
||||
if (super.orientation == GLU_OUTSIDE) {
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
for (int s = 0; s <= slices; s++) {
|
||||
final float a = (s == slices) ? 0.0f : s * da;
|
||||
sa = sin(a);
|
||||
ca = cos(a);
|
||||
emitTexCoord(0.5f + sa * r2 / dtc, 0.5f + ca * r2 / dtc);
|
||||
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
|
||||
emitTexCoord(0.5f + sa * r1 / dtc, 0.5f + ca * r1 / dtc);
|
||||
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
} else {
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
for (int s = slices; s >= 0; s--) {
|
||||
final float a = (s == slices) ? 0.0f : s * da;
|
||||
sa = sin(a);
|
||||
ca = cos(a);
|
||||
emitTexCoord(0.5f - sa * r2 / dtc, 0.5f + ca * r2 / dtc);
|
||||
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
|
||||
emitTexCoord(0.5f - sa * r1 / dtc, 0.5f + ca * r1 / dtc);
|
||||
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
r1 = r2;
|
||||
}
|
||||
}
|
||||
case GLU_LINE -> {
|
||||
/* draw loops */
|
||||
for (int l = 0; l <= loops; l++) {
|
||||
final float r = innerRadius + l * dr;
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int s = 0; s < slices; s++) {
|
||||
final float a = s * da;
|
||||
GLStateManager.glVertex2f(r * sin(a), r * cos(a));
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
/* draw spokes */
|
||||
for (int s = 0; s < slices; s++) {
|
||||
final float a = s * da;
|
||||
final float x = sin(a);
|
||||
final float y = cos(a);
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int l = 0; l <= loops; l++) {
|
||||
final float r = innerRadius + l * dr;
|
||||
GLStateManager.glVertex2f(r * x, r * y);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
case GLU_POINT -> {
|
||||
GLStateManager.glBegin(GL_POINTS);
|
||||
for (int s = 0; s < slices; s++) {
|
||||
final float a = s * da;
|
||||
final float x = sin(a);
|
||||
final float y = cos(a);
|
||||
for (int l = 0; l <= loops; l++) {
|
||||
final float r = innerRadius * l * dr;
|
||||
GLStateManager.glVertex2f(r * x, r * y);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
case GLU_SILHOUETTE -> {
|
||||
if (innerRadius != 0.0) {
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (float a = 0.0f; a < 2.0 * PI; a += da) {
|
||||
final float x = innerRadius * sin(a);
|
||||
final float y = innerRadius * cos(a);
|
||||
GLStateManager.glVertex2f(x, y);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (float a = 0; a < 2.0f * PI; a += da) {
|
||||
final float x = outerRadius * sin(a);
|
||||
final float y = outerRadius * cos(a);
|
||||
GLStateManager.glVertex2f(x, y);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.util.glu.PartialDisk;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
|
||||
import static org.lwjgl.opengl.GL11.GL_POINTS;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FILL;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FLAT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_LINE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_POINT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SMOOTH;
|
||||
|
||||
/**
|
||||
* Core-profile compatible replacement for {@link PartialDisk}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaPartialDisk extends PartialDisk {
|
||||
|
||||
private static final float PI = (float) Math.PI;
|
||||
private static final int CACHE_SIZE = 240;
|
||||
private final float[] sinCache = new float[CACHE_SIZE];
|
||||
private final float[] cosCache = new float[CACHE_SIZE];
|
||||
|
||||
@Override
|
||||
public void draw(float innerRadius, float outerRadius, int slices, int loops, float startAngle, float sweepAngle) {
|
||||
final float deltaRadius, angleOffset;
|
||||
float angle, sintemp, costemp, radiusLow, radiusHigh, texLow = 0, texHigh = 0;
|
||||
final int slices2, finish;
|
||||
|
||||
if (slices >= CACHE_SIZE) slices = CACHE_SIZE - 1;
|
||||
if (slices < 2 || loops < 1 || outerRadius <= 0.0f || innerRadius < 0.0f || innerRadius > outerRadius) {
|
||||
LOGGER.warn("PartialDisk: GLU_INVALID_VALUE (slices={}, loops={}, innerRadius={}, outerRadius={})", slices, loops, innerRadius, outerRadius);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sweepAngle < -360.0f) sweepAngle = 360.0f;
|
||||
if (sweepAngle > 360.0f) sweepAngle = 360.0f;
|
||||
if (sweepAngle < 0) {
|
||||
startAngle += sweepAngle;
|
||||
sweepAngle = -sweepAngle;
|
||||
}
|
||||
|
||||
slices2 = (sweepAngle == 360.0f) ? slices : slices + 1;
|
||||
|
||||
/* Compute length (needed for normal calculations) */
|
||||
deltaRadius = outerRadius - innerRadius;
|
||||
|
||||
angleOffset = startAngle / 180.0f * PI;
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
angle = angleOffset + ((PI * sweepAngle) / 180.0f) * i / slices;
|
||||
sinCache[i] = sin(angle);
|
||||
cosCache[i] = cos(angle);
|
||||
}
|
||||
|
||||
if (sweepAngle == 360.0f) {
|
||||
sinCache[slices] = sinCache[0];
|
||||
cosCache[slices] = cosCache[0];
|
||||
}
|
||||
|
||||
switch (super.normals) {
|
||||
case GLU_FLAT, GLU_SMOOTH -> GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
|
||||
switch (super.drawStyle) {
|
||||
case GLU_FILL -> {
|
||||
if (innerRadius == .0f) {
|
||||
finish = loops - 1;
|
||||
/* Triangle strip for inner polygons */
|
||||
GLStateManager.glBegin(GL_TRIANGLE_FAN);
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(0.5f, 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
|
||||
radiusLow = outerRadius - deltaRadius * ((float) (loops - 1) / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
}
|
||||
|
||||
if (super.orientation == GLU_OUTSIDE) {
|
||||
for (int i = slices; i >= 0; i--) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
} else {
|
||||
finish = loops;
|
||||
}
|
||||
for (int j = 0; j < finish; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
radiusHigh = outerRadius - deltaRadius * ((float) (j + 1) / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
texHigh = radiusHigh / outerRadius / 2;
|
||||
}
|
||||
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.orientation == GLU_OUTSIDE) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
|
||||
} else {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
|
||||
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
case GLU_POINT -> {
|
||||
GLStateManager.glBegin(GL_POINTS);
|
||||
for (int i = 0; i < slices2; i++) {
|
||||
sintemp = sinCache[i];
|
||||
costemp = cosCache[i];
|
||||
for (int j = 0; j <= loops; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
case GLU_LINE -> {
|
||||
if (innerRadius == outerRadius) {
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(sinCache[i] / 2 + 0.5f, cosCache[i] / 2 + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(innerRadius * sinCache[i], innerRadius * cosCache[i], 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j <= loops; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
}
|
||||
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
for (int i = 0; i < slices2; i++) {
|
||||
sintemp = sinCache[i];
|
||||
costemp = cosCache[i];
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int j = 0; j <= loops; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
}
|
||||
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
case GLU_SILHOUETTE -> {
|
||||
if (sweepAngle < 360.0f) {
|
||||
for (int i = 0; i <= slices; i += slices) {
|
||||
sintemp = sinCache[i];
|
||||
costemp = cosCache[i];
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int j = 0; j <= loops; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
for (int j = 0; j <= loops; j += loops) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
}
|
||||
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
if (innerRadius == outerRadius) break;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.util.glu.Sphere;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
|
||||
import static org.lwjgl.opengl.GL11.GL_POINTS;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FILL;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_LINE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_NONE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_POINT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
|
||||
|
||||
/**
|
||||
* Core-profile compatible replacement for {@link Sphere}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaSphere extends Sphere {
|
||||
|
||||
private static final float PI = (float) Math.PI;
|
||||
|
||||
private void emitNormal(float x, float y, float z) {
|
||||
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
|
||||
if (mag > 0.00001F) {
|
||||
x /= mag;
|
||||
y /= mag;
|
||||
z /= mag;
|
||||
}
|
||||
GLStateManager.glNormal3f(x, y, z);
|
||||
}
|
||||
|
||||
private void emitTexCoord(float x, float y) {
|
||||
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(float radius, int slices, int stacks) {
|
||||
final float drho, dtheta, ds, dt, nsign;
|
||||
float rho, theta, x, y, z, s, t;
|
||||
|
||||
final int imin, imax;
|
||||
final boolean normals;
|
||||
|
||||
normals = super.normals != GLU_NONE;
|
||||
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
|
||||
|
||||
drho = PI / stacks;
|
||||
dtheta = 2.0f * PI / slices;
|
||||
|
||||
if (super.drawStyle == GLU_FILL) {
|
||||
if (!super.textureFlag) {
|
||||
// draw +Z end as a triangle fan
|
||||
GLStateManager.glBegin(GL_TRIANGLE_FAN);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, nsign * radius);
|
||||
for (int j = 0; j <= slices; j++) {
|
||||
theta = (j == slices) ? 0.0f : j * dtheta;
|
||||
x = -sin(theta) * sin(drho);
|
||||
y = cos(theta) * sin(drho);
|
||||
z = nsign * cos(drho);
|
||||
if (normals) {
|
||||
emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
}
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
ds = 1.0f / slices;
|
||||
dt = 1.0f / stacks;
|
||||
t = 1.0f;
|
||||
if (super.textureFlag) {
|
||||
imin = 0;
|
||||
imax = stacks;
|
||||
} else {
|
||||
imin = 1;
|
||||
imax = stacks - 1;
|
||||
}
|
||||
|
||||
// draw intermediate stacks as triangle strips (GL_QUAD_STRIP removed in core profile)
|
||||
for (int i = imin; i < imax; i++) {
|
||||
rho = i * drho;
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
s = 0.0f;
|
||||
for (int j = 0; j <= slices; j++) {
|
||||
theta = (j == slices) ? 0.0f : j * dtheta;
|
||||
x = -sin(theta) * sin(rho);
|
||||
y = cos(theta) * sin(rho);
|
||||
z = nsign * cos(rho);
|
||||
if (normals) {
|
||||
emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
}
|
||||
emitTexCoord(s, t);
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
x = -sin(theta) * sin(rho + drho);
|
||||
y = cos(theta) * sin(rho + drho);
|
||||
z = nsign * cos(rho + drho);
|
||||
if (normals) {
|
||||
emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
}
|
||||
emitTexCoord(s, t - dt);
|
||||
s += ds;
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
t -= dt;
|
||||
}
|
||||
|
||||
if (!super.textureFlag) {
|
||||
// draw -Z end as a triangle fan
|
||||
GLStateManager.glBegin(GL_TRIANGLE_FAN);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, -1.0f);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, -radius * nsign);
|
||||
rho = PI - drho;
|
||||
s = 1.0f;
|
||||
for (int j = slices; j >= 0; j--) {
|
||||
theta = (j == slices) ? 0.0f : j * dtheta;
|
||||
x = -sin(theta) * sin(rho);
|
||||
y = cos(theta) * sin(rho);
|
||||
z = nsign * cos(rho);
|
||||
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
s -= ds;
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
|
||||
// draw stack lines
|
||||
for (int i = 1; i < stacks; i++) {
|
||||
rho = i * drho;
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int j = 0; j < slices; j++) {
|
||||
theta = j * dtheta;
|
||||
x = cos(theta) * sin(rho);
|
||||
y = sin(theta) * sin(rho);
|
||||
z = cos(rho);
|
||||
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
// draw slice lines
|
||||
for (int j = 0; j < slices; j++) {
|
||||
theta = j * dtheta;
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int i = 0; i <= stacks; i++) {
|
||||
rho = i * drho;
|
||||
x = cos(theta) * sin(rho);
|
||||
y = sin(theta) * sin(rho);
|
||||
z = cos(rho);
|
||||
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
} else if (super.drawStyle == GLU_POINT) {
|
||||
// top and bottom-most points
|
||||
GLStateManager.glBegin(GL_POINTS);
|
||||
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, nsign);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, radius);
|
||||
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, -nsign);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, -radius);
|
||||
|
||||
// loop over stacks
|
||||
for (int i = 1; i < stacks - 1; i++) {
|
||||
rho = i * drho;
|
||||
for (int j = 0; j < slices; j++) {
|
||||
theta = j * dtheta;
|
||||
x = cos(theta) * sin(rho);
|
||||
y = sin(theta) * sin(rho);
|
||||
z = cos(rho);
|
||||
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.glsm.debug;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.ITextureObject;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
// Adapted from https://github.com/makamys/CoreTweaks/blob/master/src/main/java/makamys/coretweaks/util/OpenGLDebugging.java;
|
||||
// originally from https://github.com/TheGreyGhost/MinecraftByExample/blob/1-8final/src/main/java/minecraftbyexample/usefultools/OpenGLdebugging.java
|
||||
@@ -25,12 +37,13 @@ public class OpenGLDebugging {
|
||||
public static final Logger LOGGER = LogManager.getLogger("gldumper");
|
||||
|
||||
private static final Consumer<String> LINE_LOGGER = LOGGER::debug;
|
||||
private static final Set<String> seenErrors = new HashSet<>();
|
||||
public static final String GL_IS_ENABLED = "glIsEnabled()";
|
||||
public static final String GL_GET_BOOLEANV = "glGetBooleanv()";
|
||||
public static final String GL_GET_INTEGERV = "glGetIntegerv()";
|
||||
public static final String GL_GET_FLOATV = "glGetFloatv()";
|
||||
|
||||
public class GLproperty {
|
||||
public static class GLproperty {
|
||||
|
||||
public GLproperty(int glConstant, String name, String description, String category, String fetchCommand) {
|
||||
this.gLConstant = glConstant;
|
||||
@@ -445,30 +458,250 @@ public class OpenGLDebugging {
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkGLSM() {
|
||||
public static String getGLSMErrorLog() {
|
||||
return getGLSMErrorLog(null, false, false);
|
||||
}
|
||||
|
||||
// Re-entrancy guard - getGLSMErrorLog calls glActiveTexture which could trigger checkGLSMOnce
|
||||
private static boolean inCheck = false;
|
||||
|
||||
/**
|
||||
* Enhanced GLSM error logging with context, stack trace, and first-occurrence filtering.
|
||||
*
|
||||
* @param context Optional context string to include in error messages (e.g., "During rendering")
|
||||
* @param logStacktrace If true, includes the current stack trace in the error output
|
||||
* @param onlyFirstOccurrence If true, only logs each unique error once
|
||||
* @return Error log string if mismatches found, null otherwise
|
||||
*/
|
||||
public static String getGLSMErrorLog(String context, boolean logStacktrace, boolean onlyFirstOccurrence) {
|
||||
if (inCheck) return null;
|
||||
if (!GLStateManager.isCachingEnabled() || GLStateManager.isRecordingDisplayList()) {
|
||||
return null;
|
||||
}
|
||||
inCheck = true;
|
||||
try {
|
||||
String group = "checkGLSM" + (context == null ? "" : " " + context);
|
||||
GLDebug.pushGroup(group);
|
||||
final StringBuilder error = new StringBuilder();
|
||||
boolean mismatch = false;
|
||||
|
||||
if (context != null && !context.isEmpty()) {
|
||||
error.append("[").append(context).append("] ");
|
||||
}
|
||||
|
||||
for (int i = 0; i < instance.propertyList.length; ++i) {
|
||||
final GLproperty gLProperty = instance.propertyList[i];
|
||||
if(gLProperty.fetchCommand.equals(GL_GET_FLOATV) && gLProperty.name.endsWith("_MATRIX")) {
|
||||
if (gLProperty.fetchCommand.equals(GL_GET_FLOATV) && gLProperty.name.endsWith("_MATRIX")) {
|
||||
final Matrix4f cached = gLProperty.getAsMatrix4f(true);
|
||||
final Matrix4f uncached = gLProperty.getAsMatrix4f(false);
|
||||
// Some precision issues due to RAD vs degrees, and translated (vs f)
|
||||
if (!cached.equals(uncached, 0.001f)) {
|
||||
LOGGER.error("GLSM mismatch: " + gLProperty.name + "\ncached:\n" + cached + "uncached:\n" + uncached);
|
||||
mismatch = true;
|
||||
|
||||
// Skip comparison if either matrix is degenerate (contains NaN/Infinity) these represent invalid GL state from bad parameters, not cache bugs
|
||||
if (isMatrixDegenerate(cached) || isMatrixDegenerate(uncached)) continue;
|
||||
|
||||
if (!matricesEquivalent(cached, uncached, 0.001f)) {
|
||||
final String errorKey = "MATRIX:" + gLProperty.name;
|
||||
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
|
||||
final String errorMsg = "GLSM mismatch: " + gLProperty.name + "\ncached:\n" + cached + "uncached:\n" + uncached;
|
||||
LOGGER.error(errorMsg);
|
||||
error.append(errorMsg).append('\n');
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final String cached = gLProperty.getAsString(true);
|
||||
final String uncached = gLProperty.getAsString(false);
|
||||
if (!cached.equals(uncached)) {
|
||||
LOGGER.error("GLSM mismatch: " + gLProperty.name + " cached: " + cached + " uncached: " + uncached);
|
||||
final String errorKey = "PROPERTY:" + gLProperty.name + ":" + cached + ":" + uncached;
|
||||
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
|
||||
error.append("GLSM mismatch: ").append(gLProperty.name).append(" cached: ").append(cached).append(" uncached: ").append(uncached);
|
||||
error.append('\n');
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final TextureUnitArray textureUnits = GLStateManager.getTextures();
|
||||
final int currentActiveTexture = GLStateManager.getActiveTextureUnit();
|
||||
|
||||
// Check GL_ACTIVE_TEXTURE
|
||||
final int glActiveUnit = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE) - GL13.GL_TEXTURE0;
|
||||
if (glActiveUnit != currentActiveTexture) {
|
||||
final String errorKey = "ACTIVE_TEXTURE:" + currentActiveTexture + ":" + glActiveUnit;
|
||||
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
|
||||
error.append("GLSM mismatch: GL_ACTIVE_TEXTURE cached: ").append(currentActiveTexture)
|
||||
.append(" uncached: ").append(glActiveUnit).append('\n');
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check per-unit texture state
|
||||
for (int i = 0; i < textureUnits.textureMatricies.length; i++) {
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + i);
|
||||
|
||||
// Check GL_TEXTURE_2D enabled
|
||||
final boolean cachedEnabled = textureUnits.getTextureUnitStates(i).isEnabled();
|
||||
final boolean uncachedEnabled = GL11.glIsEnabled(GL11.GL_TEXTURE_2D);
|
||||
if (cachedEnabled != uncachedEnabled) {
|
||||
final String errorKey = "TEXTURE_2D:" + i + ":" + cachedEnabled + ":" + uncachedEnabled;
|
||||
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
|
||||
error.append("GLSM mismatch: GL_TEXTURE_2D cached: ").append(cachedEnabled)
|
||||
.append(" uncached: ").append(uncachedEnabled)
|
||||
.append(", texture unit: GL_TEXTURE").append(i).append('\n');
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check GL_TEXTURE_BINDING_2D
|
||||
final int cachedBinding = textureUnits.getTextureUnitBindings(i).getBinding();
|
||||
final int uncachedBinding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
if (cachedBinding != uncachedBinding) {
|
||||
final String errorKey = "TEXTURE_BINDING_2D:" + i + ":" + cachedBinding + ":" + uncachedBinding;
|
||||
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
|
||||
error.append("GLSM mismatch: GL_TEXTURE_BINDING_2D cached: ").append(cachedBinding)
|
||||
.append(" uncached: ").append(uncachedBinding)
|
||||
.append(", texture unit: GL_TEXTURE").append(i).append('\n');
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!mismatch) {
|
||||
LOGGER.info("GLSM state matches!");
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + currentActiveTexture);
|
||||
|
||||
// Add stack trace if requested
|
||||
if (mismatch && logStacktrace) {
|
||||
error.append("\nStack trace:\n");
|
||||
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (int i = 2; i < stackTrace.length; i++) {
|
||||
error.append(" at ").append(stackTrace[i].toString()).append('\n');
|
||||
}
|
||||
}
|
||||
GLDebug.popGroup();
|
||||
|
||||
return mismatch ? error.toString() : null;
|
||||
} finally {
|
||||
inCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkGLSMOnce(String context) {
|
||||
final String output = getGLSMErrorLog(context, true, true);
|
||||
|
||||
if (output != null) {
|
||||
// Only log on actual mismatch
|
||||
LOGGER.error("[{}] MISMATCH thread={}\n{}", context, Thread.currentThread().getName(), output);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a matrix contains any non-finite values (NaN or Infinity).
|
||||
* Such matrices represent invalid/degenerate GL state (e.g., zero near/far in projection).
|
||||
*/
|
||||
private static boolean isMatrixDegenerate(Matrix4f matrix) {
|
||||
for (int col = 0; col < 4; col++) {
|
||||
for (int row = 0; row < 4; row++) {
|
||||
if (!Float.isFinite(matrix.get(col, row))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two matrices with tolerance for NaN/Infinity.
|
||||
* Treats NaN == NaN and Infinity == Infinity as equal (both are degenerate states).
|
||||
* For finite values, uses epsilon comparison.
|
||||
*/
|
||||
private static boolean matricesEquivalent(Matrix4f a, Matrix4f b, float epsilon) {
|
||||
// Compare all 16 elements
|
||||
for (int col = 0; col < 4; col++) {
|
||||
for (int row = 0; row < 4; row++) {
|
||||
final float aVal = a.get(col, row);
|
||||
final float bVal = b.get(col, row);
|
||||
|
||||
// If both are non-finite (NaN or Infinity), consider them equal
|
||||
if (!Float.isFinite(aVal) && !Float.isFinite(bVal)) {
|
||||
// Both non-finite: check if both NaN or both same Infinity
|
||||
if (Float.isNaN(aVal) && Float.isNaN(bVal)) continue;
|
||||
if (Float.isInfinite(aVal) && Float.isInfinite(bVal)) {
|
||||
// Check same sign of infinity
|
||||
if ((aVal > 0) == (bVal > 0)) continue;
|
||||
}
|
||||
return false; // Different non-finite values
|
||||
}
|
||||
|
||||
// If one is finite and other is not, they differ
|
||||
if (Float.isFinite(aVal) != Float.isFinite(bVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Both finite: use epsilon comparison
|
||||
if (Math.abs(aVal - bVal) > epsilon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void checkGLSM() {
|
||||
final String output = getGLSMErrorLog();
|
||||
|
||||
if (output == null) {
|
||||
LOGGER.info("GLSM state matches!");
|
||||
} else {
|
||||
LOGGER.error(output);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyTexture(ResourceLocation resource, File output) {
|
||||
final Object object = Minecraft.getMinecraft().renderEngine.mapTextureObjects.get(resource);
|
||||
|
||||
if (object != null) {
|
||||
copyTexture(((ITextureObject) object).getGlTextureId(), output);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyTexture(int textureID, File output) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
|
||||
|
||||
int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
|
||||
int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
|
||||
|
||||
BufferedImage bufferedimage = new BufferedImage(width, height, 1);
|
||||
final int[] pixelValues = ((DataBufferInt) bufferedimage.getRaster().getDataBuffer()).getData();
|
||||
IntBuffer pixelBuffer = BufferUtils.createIntBuffer(pixelValues.length);
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
|
||||
pixelBuffer.get(pixelValues);
|
||||
|
||||
// Flip texture
|
||||
final int halfHeight = height / 2;
|
||||
final int[] rowBuffer = new int[width];
|
||||
for (int y = 0; y < halfHeight; y++) {
|
||||
final int top = y * width;
|
||||
final int bottom = (height - 1 - y) * width;
|
||||
|
||||
System.arraycopy(pixelValues, top, rowBuffer, 0, width);
|
||||
System.arraycopy(pixelValues, bottom, pixelValues, top, width);
|
||||
System.arraycopy(rowBuffer, 0, pixelValues, bottom, width);
|
||||
}
|
||||
|
||||
File dir = output.getParentFile();
|
||||
dir.mkdirs();
|
||||
copyBufferedImageToFile(bufferedimage, output);
|
||||
bufferedimage.flush();
|
||||
}
|
||||
|
||||
|
||||
private static void copyBufferedImageToFile(BufferedImage image, File file) {
|
||||
try {
|
||||
ImageIO.write(image, "png", file);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.ARBDirectStateAccess;
|
||||
import org.lwjgl.opengl.GL45;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
@@ -14,8 +15,32 @@ public class DSAARB extends DSAUnsupported {
|
||||
ARBDirectStateAccess.glGenerateTextureMipmap(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
// Note: DSA glTextureImage2D doesn't exist in ARB_direct_state_access
|
||||
// We need to use glTextureStorage2D + glTextureSubImage2D, or fall back to bind-based approach
|
||||
// For simplicity, use the fallback which binds, uploads, and restores
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameteri(int texture, int target, int pname, int param) {
|
||||
param = GLStateManager.remapTexClamp(pname, param);
|
||||
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
|
||||
|
||||
ARBDirectStateAccess.glTextureParameteri(texture, pname, param);
|
||||
@@ -23,6 +48,7 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public void texParameterf(int texture, int target, int pname, float param) {
|
||||
param = GLStateManager.remapTexClamp(pname, param);
|
||||
if(!GLStateManager.updateTexParameterfCache(target, texture, pname, param)) return;
|
||||
|
||||
ARBDirectStateAccess.glTextureParameterf(texture, pname, param);
|
||||
@@ -30,6 +56,7 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
GLStateManager.remapTexClampBuffer(pname, params);
|
||||
ARBDirectStateAccess.glTextureParameter(texture, pname, params);
|
||||
}
|
||||
|
||||
@@ -48,6 +75,11 @@ public class DSAARB extends DSAUnsupported {
|
||||
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> ARBDirectStateAccess.glGetTextureParameteri(texture, pname));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
return ARBDirectStateAccess.glGetTextureLevelParameteri(texture, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
ARBDirectStateAccess.glCopyTextureSubImage2D(destTexture, i, i1, i2, i3, i4, width, height);
|
||||
@@ -55,7 +87,7 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public void bindTextureToUnit(int unit, int texture) {
|
||||
if(GLStateManager.getBoundTexture(unit) == texture) return;
|
||||
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
|
||||
|
||||
if (texture == 0) {
|
||||
super.bindTextureToUnit(unit, texture);
|
||||
@@ -65,6 +97,11 @@ public class DSAARB extends DSAUnsupported {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTextureToUnit(int target, int unit, int texture) {
|
||||
bindTextureToUnit(unit, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
final int buffer = GL45.glCreateBuffers();
|
||||
@@ -92,4 +129,19 @@ public class DSAARB extends DSAUnsupported {
|
||||
public int createTexture(int target) {
|
||||
return ARBDirectStateAccess.glCreateTextures(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
ARBDirectStateAccess.glTextureStorage1D(texture, levels, internalFormat, width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
ARBDirectStateAccess.glTextureStorage2D(texture, levels, internalFormat, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
ARBDirectStateAccess.glTextureStorage3D(texture, levels, internalFormat, width, height, depth);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public interface DSAAccess {
|
||||
void generateMipmaps(int texture, int target);
|
||||
|
||||
// Texture image upload - DSA allows specifying texture directly without binding
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
|
||||
|
||||
void texParameteri(int texture, int target, int pname, int param);
|
||||
void texParameterf(int texture, int target, int pname, float param);
|
||||
void texParameteriv(int texture, int target, int pname, IntBuffer params);
|
||||
@@ -16,9 +23,12 @@ public interface DSAAccess {
|
||||
|
||||
int getTexParameteri(int texture, int target, int pname);
|
||||
|
||||
int getTexLevelParameteri(int texture, int level, int pname);
|
||||
|
||||
void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height);
|
||||
|
||||
void bindTextureToUnit(int unit, int texture);
|
||||
void bindTextureToUnit(int target, int unit, int texture);
|
||||
|
||||
int bufferStorage(int target, FloatBuffer data, int usage);
|
||||
|
||||
@@ -28,4 +38,9 @@ public interface DSAAccess {
|
||||
|
||||
int createFramebuffer();
|
||||
int createTexture(int target);
|
||||
|
||||
// Texture storage methods for custom images
|
||||
void textureStorage1D(int texture, int target, int levels, int internalFormat, int width);
|
||||
void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height);
|
||||
void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import org.lwjgl.opengl.EXTDirectStateAccess;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class DSAEXT extends DSACore {
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
EXTDirectStateAccess.glTextureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
EXTDirectStateAccess.glTextureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,16 @@ package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
@@ -20,6 +23,39 @@ public class DSAUnsupported implements DSAAccess {
|
||||
GL30.glGenerateMipmap(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding); // Restore to cache value
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameteri(int texture, int target, int pname, int param) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
@@ -40,13 +76,13 @@ public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public void readBuffer(int framebuffer, int buffer) {
|
||||
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
GL11.glReadBuffer(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawBuffers(int framebuffer, IntBuffer buffers) {
|
||||
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
GL20.glDrawBuffers(buffers);
|
||||
}
|
||||
|
||||
@@ -56,9 +92,18 @@ public class DSAUnsupported implements DSAAccess {
|
||||
return GLStateManager.glGetTexParameteri(target, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
final int previous = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
final int result = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, pname);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previous);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
final int previous = GLStateManager.getBoundTexture();
|
||||
final int previous = GLStateManager.getBoundTextureForServerState();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, destTexture);
|
||||
GL11.glCopyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
|
||||
@@ -70,12 +115,18 @@ public class DSAUnsupported implements DSAAccess {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTextureToUnit(int target, int unit, int texture) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
final int buffer = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(target, buffer);
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
RenderSystem.bufferData(target, data, usage);
|
||||
GL15.glBindBuffer(target, 0);
|
||||
GLStateManager.glBindBuffer(target, 0);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
@@ -83,21 +134,21 @@ public class DSAUnsupported implements DSAAccess {
|
||||
@Override
|
||||
public void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2,
|
||||
int bufferChoice, int filter) {
|
||||
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_READ_FRAMEBUFFER, source);
|
||||
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_DRAW_FRAMEBUFFER, dest);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, source);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, dest);
|
||||
GL30.glBlitFramebuffer(offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(fbtarget, fb);
|
||||
GLStateManager.glBindFramebuffer(fbtarget, fb);
|
||||
GL30.glFramebufferTexture2D(fbtarget, attachment, target, texture, levels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createFramebuffer() {
|
||||
final int framebuffer = OpenGlHelper.func_153165_e/*glGenFramebuffers*/();
|
||||
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
final int framebuffer = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
@@ -107,4 +158,28 @@ public class DSAUnsupported implements DSAAccess {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
final int previous = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_1D);
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL42.glTexStorage1D(target, levels, internalFormat, width);
|
||||
GL11.glBindTexture(target, previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
final int previous = (target == GL11.GL_TEXTURE_2D) ? GLStateManager.getBoundTextureForServerState() : GL11.glGetInteger(GL31.GL_TEXTURE_BINDING_RECTANGLE);
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL42.glTexStorage2D(target, levels, internalFormat, width, height);
|
||||
GL11.glBindTexture(target, previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
final int previous = GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D);
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL42.glTexStorage3D(target, levels, internalFormat, width, height, depth);
|
||||
GL11.glBindTexture(target, previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
|
||||
|
||||
/**
|
||||
* DirectTessellator subclass for deferred particle batching. Accumulates vertex data from
|
||||
* multiple interceptDraw() calls, recording per-draw metadata as {@link DrawRange} entries.
|
||||
* Computes a delta modelview matrix at each intercept to pre-transform vertices.
|
||||
*/
|
||||
class DeferredBatchTessellator extends DirectTessellator {
|
||||
|
||||
@Desugar
|
||||
record DrawRange(long stateKey, int byteOffset, int byteLength, int vertexCount, int drawMode, int flags) {}
|
||||
|
||||
private final List<DrawRange> ranges = new ArrayList<>();
|
||||
private final Matrix4f defaultModelview = new Matrix4f();
|
||||
private final Matrix4f defaultModelviewInverse = new Matrix4f();
|
||||
private final Matrix4f deltaMatrix = new Matrix4f(); // reusable scratch
|
||||
private final Vector3f scratch = new Vector3f();
|
||||
private Matrix4fc currentTransform;
|
||||
|
||||
DeferredBatchTessellator(ByteBuffer buffer) {
|
||||
super(buffer, false); // deleteAfter=false: keep buffer across resets for reuse
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long writeVertexData(VertexFormat format, int[] rawBuffer, int rawBufferIndex) {
|
||||
if (currentTransform != null) {
|
||||
return format.writeToBuffer0(writePtr, rawBuffer, rawBufferIndex, currentTransform, scratch);
|
||||
}
|
||||
return super.writeVertexData(format, rawBuffer, rawBufferIndex);
|
||||
}
|
||||
|
||||
/** Snapshot the current modelview as the "default" for this bracket. */
|
||||
void snapshotDefaultModelview() {
|
||||
defaultModelview.set(GLStateManager.getModelViewMatrix());
|
||||
defaultModelview.invert(defaultModelviewInverse);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int interceptDraw(Tessellator tess) {
|
||||
// Compute delta: if current MV differs from default, pre-transform vertices
|
||||
final Matrix4f currentMV = GLStateManager.getModelViewMatrix();
|
||||
if (!currentMV.equals(defaultModelview)) {
|
||||
defaultModelviewInverse.mul(currentMV, deltaMatrix);
|
||||
this.currentTransform = deltaMatrix;
|
||||
} else {
|
||||
this.currentTransform = null;
|
||||
}
|
||||
|
||||
final int byteOffsetBefore = bufferLimit();
|
||||
final int vertCount = tess.vertexCount;
|
||||
final int drawMode = tess.drawMode;
|
||||
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
|
||||
final long stateKey = DeferredDrawBatcher.captureStateKey();
|
||||
|
||||
final int result = super.interceptDraw(tess); // repacks int[] → ByteBuffer + transforms if vertexTransform set
|
||||
|
||||
final int byteOffsetAfter = bufferLimit();
|
||||
ranges.add(new DrawRange(stateKey, byteOffsetBefore, byteOffsetAfter - byteOffsetBefore, vertCount, drawMode, flags));
|
||||
this.currentTransform = null; // clear for next draw
|
||||
return result;
|
||||
}
|
||||
|
||||
List<DrawRange> getRanges() { return ranges; }
|
||||
|
||||
void clearRanges() { ranges.clear(); }
|
||||
|
||||
void copyRange(int byteOffset, int byteLength, long destAddress) {
|
||||
memCopy(startPtr + byteOffset, destAddress, byteLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemovedFromStack() {
|
||||
clearRanges();
|
||||
super.onRemovedFromStack(); // calls reset(), keeps baseBuffer (deleteAfter=false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
|
||||
/**
|
||||
* Deferred draw batching coordinator. During rendering regions (e.g. particles),
|
||||
* intercepts tessellator.draw() calls via DirectTessellator, captures pre-packed vertex data
|
||||
* + GL state, then flushes all captured batches sorted by state key.
|
||||
* <p>
|
||||
* Vertices are pre-transformed at capture time.
|
||||
*/
|
||||
public class DeferredDrawBatcher {
|
||||
|
||||
private static final int INITIAL_BUFFER_SIZE = 64 * 1024;
|
||||
|
||||
@Getter private static boolean active = false;
|
||||
private static DeferredBatchTessellator batchTessellator;
|
||||
|
||||
private DeferredDrawBatcher() {}
|
||||
|
||||
/**
|
||||
* Begin deferred capture mode. Called after startDrawingQuads().
|
||||
* Pushes a DeferredBatchTessellator onto the DirectTessellator stack so that
|
||||
* subsequent tessellator.draw() calls are intercepted and accumulated.
|
||||
*/
|
||||
public static void enter() {
|
||||
active = true;
|
||||
if (batchTessellator == null) {
|
||||
batchTessellator = new DeferredBatchTessellator(memAlloc(INITIAL_BUFFER_SIZE));
|
||||
}
|
||||
batchTessellator.clearRanges();
|
||||
batchTessellator.snapshotDefaultModelview();
|
||||
TessellatorManager.startCapturingDirect(batchTessellator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit deferred mode and flush all captured batches. Groups entries by state key
|
||||
* and vertex format, issues one draw per unique group. Restores the GL state that
|
||||
* was active for the last group so vanilla sees consistent state.
|
||||
*/
|
||||
public static void exitAndFlush() {
|
||||
active = false;
|
||||
final List<DeferredBatchTessellator.DrawRange> ranges = batchTessellator.getRanges();
|
||||
|
||||
if (!ranges.isEmpty()) {
|
||||
ranges.sort(Comparator.comparingLong(DeferredBatchTessellator.DrawRange::stateKey));
|
||||
flushSorted(ranges);
|
||||
}
|
||||
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
}
|
||||
|
||||
private static void flushSorted(List<DeferredBatchTessellator.DrawRange> ranges) {
|
||||
long currentKey = ranges.get(0).stateKey();
|
||||
int groupStart = 0;
|
||||
|
||||
for (int i = 0; i <= ranges.size(); i++) {
|
||||
final boolean endOfList = (i == ranges.size());
|
||||
final boolean keyChanged = !endOfList && ranges.get(i).stateKey() != currentKey;
|
||||
|
||||
if (endOfList || keyChanged) {
|
||||
applyStateKey(currentKey);
|
||||
flushGroup(ranges, groupStart, i);
|
||||
|
||||
if (!endOfList) {
|
||||
currentKey = ranges.get(i).stateKey();
|
||||
groupStart = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush a group of ranges sharing the same state key.
|
||||
* Subgroups by (drawMode, format flags) since those must match for merged draws.
|
||||
*/
|
||||
private static void flushGroup(List<DeferredBatchTessellator.DrawRange> ranges, int from, int to) {
|
||||
int i = from;
|
||||
while (i < to) {
|
||||
final DeferredBatchTessellator.DrawRange first = ranges.get(i);
|
||||
final int drawMode = first.drawMode();
|
||||
final int flags = first.flags();
|
||||
|
||||
int totalBytes = 0;
|
||||
int totalVertices = 0;
|
||||
int subEnd = i;
|
||||
|
||||
while (subEnd < to) {
|
||||
final DeferredBatchTessellator.DrawRange e = ranges.get(subEnd);
|
||||
if (e.drawMode() != drawMode || e.flags() != flags) break;
|
||||
totalBytes += e.byteLength();
|
||||
totalVertices += e.vertexCount();
|
||||
subEnd++;
|
||||
}
|
||||
|
||||
TessellatorStreamingDrawer.drawPackedBatch(
|
||||
batchTessellator, ranges, i, subEnd, totalBytes, totalVertices, drawMode, flags);
|
||||
|
||||
i = subEnd;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack current GLSM state into a long key for grouping.
|
||||
* Layout: [tex1En:1][tex0En:1][textureId:20][srcRgb:12][dstRgb:12][blendEnabled:1][depthMask:1] = 48 bits
|
||||
* GL blend constants (e.g. GL_SRC_ALPHA=0x0302) need 12 bits.
|
||||
*
|
||||
* NOT captured (uniform within a particle layer bracket — set once before startDrawingQuads,
|
||||
* not changed by individual particles):
|
||||
* - depth test enable (always GL_LEQUAL for particles)
|
||||
* - alpha test func/ref (set per-layer, not per-particle)
|
||||
* - separate blend alpha factors (srcAlpha/dstAlpha) — particles use glBlendFunc not glBlendFuncSeparate
|
||||
* - fog state (mode, color, start/end — constant for the frame)
|
||||
*/
|
||||
static long captureStateKey() {
|
||||
final int textureId = GLStateManager.getTextures().getTextureUnitBindings(0).getBinding();
|
||||
final int srcRgb = GLStateManager.getBlendState().getSrcRgb();
|
||||
final int dstRgb = GLStateManager.getBlendState().getDstRgb();
|
||||
final boolean blendEnabled = GLStateManager.getBlendMode().isEnabled();
|
||||
final boolean depthMask = GLStateManager.getDepthState().isEnabled();
|
||||
final boolean tex0Enabled = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled();
|
||||
final boolean tex1Enabled = GLStateManager.getTextures().getTextureUnitStates(1).isEnabled();
|
||||
|
||||
return packStateKey(textureId, srcRgb, dstRgb, blendEnabled, depthMask, tex0Enabled, tex1Enabled);
|
||||
}
|
||||
|
||||
static long packStateKey(int textureId, int srcRgb, int dstRgb, boolean blendEnabled, boolean depthMask, boolean tex0Enabled, boolean tex1Enabled) {
|
||||
return ((long) (textureId & 0xFFFFF) << 26)
|
||||
| ((long) (srcRgb & 0xFFF) << 14)
|
||||
| ((long) (dstRgb & 0xFFF) << 2)
|
||||
| (blendEnabled ? 2L : 0L)
|
||||
| (depthMask ? 1L : 0L)
|
||||
| (tex0Enabled ? (1L << 46) : 0L)
|
||||
| (tex1Enabled ? (1L << 47) : 0L);
|
||||
}
|
||||
|
||||
static int unpackTextureId(long key) { return (int) ((key >> 26) & 0xFFFFF); }
|
||||
static int unpackSrcRgb(long key) { return (int) ((key >> 14) & 0xFFF); }
|
||||
static int unpackDstRgb(long key) { return (int) ((key >> 2) & 0xFFF); }
|
||||
static boolean unpackBlendEnabled(long key) { return (key & 2L) != 0; }
|
||||
static boolean unpackDepthMask(long key) { return (key & 1L) != 0; }
|
||||
static boolean unpackTex0Enabled(long key) { return (key & (1L << 46)) != 0; }
|
||||
static boolean unpackTex1Enabled(long key) { return (key & (1L << 47)) != 0; }
|
||||
|
||||
static void applyStateKey(long key) {
|
||||
final int textureId = unpackTextureId(key);
|
||||
final int srcRgb = unpackSrcRgb(key);
|
||||
final int dstRgb = unpackDstRgb(key);
|
||||
final boolean blendEnabled = unpackBlendEnabled(key);
|
||||
final boolean depthMask = unpackDepthMask(key);
|
||||
final boolean tex0Enabled = unpackTex0Enabled(key);
|
||||
final boolean tex1Enabled = unpackTex1Enabled(key);
|
||||
|
||||
GLStateManager.getTextures().getTextureUnitStates(0).setEnabled(tex0Enabled);
|
||||
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(tex1Enabled);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
|
||||
GLStateManager.glBlendFunc(srcRgb, dstRgb);
|
||||
if (blendEnabled) {
|
||||
GLStateManager.enableBlend();
|
||||
} else {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
GLStateManager.glDepthMask(depthMask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
/**
|
||||
* Packed-long fragment shader permutation key for FFP emulation.
|
||||
*
|
||||
* Layout:
|
||||
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
|
||||
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
|
||||
* Only max(1, nrEnabledUnits) longs are significant.
|
||||
*
|
||||
* Per-unit packing (47 bits):
|
||||
* 0: enabled
|
||||
* 1-3: mode (MODULATE=0..COMBINE=5)
|
||||
* 4: needsEnvColor
|
||||
* 5-8: combineRgb (encoded 0-7)
|
||||
* 9-12: combineAlpha (encoded 0-7)
|
||||
* 13-14: scaleShiftRgb
|
||||
* 15-16: scaleShiftAlpha
|
||||
* 17-21: arg0 RGB (source:3 + operand:2)
|
||||
* 22-26: arg1 RGB
|
||||
* 27-31: arg2 RGB
|
||||
* 32-36: arg0 Alpha
|
||||
* 37-41: arg1 Alpha
|
||||
* 42-46: arg2 Alpha
|
||||
*/
|
||||
public final class FragmentKey {
|
||||
private static final Logger LOGGER = LogManager.getLogger("FragmentKey");
|
||||
|
||||
public static final int TEX_ENV_MODULATE = 0;
|
||||
public static final int TEX_ENV_REPLACE = 1;
|
||||
public static final int TEX_ENV_ADD = 2;
|
||||
public static final int TEX_ENV_DECAL = 3;
|
||||
public static final int TEX_ENV_BLEND = 4;
|
||||
public static final int TEX_ENV_COMBINE = 5;
|
||||
|
||||
public static final int COMBINE_REPLACE = 0;
|
||||
public static final int COMBINE_MODULATE = 1;
|
||||
public static final int COMBINE_ADD = 2;
|
||||
public static final int COMBINE_ADD_SIGNED = 3;
|
||||
public static final int COMBINE_INTERPOLATE = 4;
|
||||
public static final int COMBINE_SUBTRACT = 5;
|
||||
public static final int COMBINE_DOT3_RGB = 6;
|
||||
public static final int COMBINE_DOT3_RGBA = 7;
|
||||
|
||||
public static final int SRC_TEXTURE = 0;
|
||||
public static final int SRC_CONSTANT = 1;
|
||||
public static final int SRC_PRIMARY_COLOR = 2;
|
||||
public static final int SRC_PREVIOUS = 3;
|
||||
|
||||
public static final int OP_SRC_COLOR = 0;
|
||||
public static final int OP_ONE_MINUS_SRC_COLOR = 1;
|
||||
public static final int OP_SRC_ALPHA = 2;
|
||||
public static final int OP_ONE_MINUS_SRC_ALPHA = 3;
|
||||
|
||||
public static final int FOG_NONE = 0;
|
||||
public static final int FOG_LINEAR = 1;
|
||||
public static final int FOG_EXP = 2;
|
||||
public static final int FOG_EXP2 = 3;
|
||||
|
||||
private static final int GLOBAL_BITS = 10;
|
||||
private static final int BIT_FOG_MODE = 0; // 2 bits
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
|
||||
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
|
||||
|
||||
private static final int U_ENABLED = 0;
|
||||
private static final int U_MODE = 1; // 3 bits
|
||||
private static final int U_NEEDS_ENV_COLOR = 4; // 1 bit
|
||||
private static final int U_COMBINE_RGB = 5; // 4 bits
|
||||
private static final int U_COMBINE_ALPHA = 9; // 4 bits
|
||||
private static final int U_SCALE_RGB = 13; // 2 bits
|
||||
private static final int U_SCALE_ALPHA = 15; // 2 bits
|
||||
private static final int U_ARG0_RGB = 17; // 5 bits (source:3 + operand:2)
|
||||
private static final int U_ARG1_RGB = 22; // 5 bits
|
||||
private static final int U_ARG2_RGB = 27; // 5 bits
|
||||
private static final int U_ARG0_ALPHA = 32; // 5 bits
|
||||
private static final int U_ARG1_ALPHA = 37; // 5 bits
|
||||
private static final int U_ARG2_ALPHA = 42; // 5 bits
|
||||
|
||||
static final int MAX_UNITS = 4;
|
||||
|
||||
private final long[] packed;
|
||||
private final int hash;
|
||||
|
||||
private FragmentKey(long[] packed, int hash) {
|
||||
this.packed = packed;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack current GLSM fragment state into pre-allocated output array.
|
||||
* @param out must be at least MAX_UNITS (4) longs
|
||||
* @return number of significant longs (1..4)
|
||||
*/
|
||||
public static int packFromState(long[] out) {
|
||||
long global = 0;
|
||||
|
||||
// Fog
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
|
||||
case GL11.GL_LINEAR -> FOG_LINEAR;
|
||||
case GL11.GL_EXP -> FOG_EXP;
|
||||
case GL11.GL_EXP2 -> FOG_EXP2;
|
||||
default -> FOG_NONE;
|
||||
};
|
||||
global |= (long) fogMode;
|
||||
}
|
||||
|
||||
// Alpha test
|
||||
if (GLStateManager.getAlphaTest().isEnabled()) {
|
||||
global |= (1L << BIT_ALPHA_TEST);
|
||||
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
|
||||
}
|
||||
|
||||
// Separate specular
|
||||
if (GLStateManager.getLightingState().isEnabled()
|
||||
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
global |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
}
|
||||
|
||||
// Per-unit state
|
||||
int highestEnabled = -1;
|
||||
long unit0Bits = 0;
|
||||
for (int i = 0; i < MAX_UNITS; i++) {
|
||||
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled();
|
||||
if (texEnabled) highestEnabled = i;
|
||||
|
||||
final long unitBits = packUnit(i, texEnabled);
|
||||
if (i == 0) {
|
||||
unit0Bits = unitBits;
|
||||
} else {
|
||||
out[i] = unitBits;
|
||||
}
|
||||
}
|
||||
|
||||
final int nrEnabled = highestEnabled + 1;
|
||||
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
|
||||
global |= (unit0Bits << GLOBAL_BITS);
|
||||
out[0] = global;
|
||||
|
||||
return Math.max(1, nrEnabled);
|
||||
}
|
||||
|
||||
private static long packUnit(int unitIndex, boolean texEnabled) {
|
||||
if (!texEnabled) return 0;
|
||||
|
||||
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
|
||||
final int mode = encodeTexEnvMode(envState.mode);
|
||||
|
||||
long bits = 1L; // enabled
|
||||
bits |= ((long) (mode & 0x7)) << U_MODE;
|
||||
|
||||
boolean needsEnvColor = (mode == TEX_ENV_BLEND);
|
||||
|
||||
if (mode == TEX_ENV_COMBINE) {
|
||||
final int combRgb = encodeCombineFunc(envState.combineRgb);
|
||||
final int combAlpha = encodeCombineFunc(envState.combineAlpha);
|
||||
bits |= ((long) (combRgb & 0xF)) << U_COMBINE_RGB;
|
||||
bits |= ((long) (combAlpha & 0xF)) << U_COMBINE_ALPHA;
|
||||
bits |= ((long) (encodeScale(envState.scaleRgb) & 0x3)) << U_SCALE_RGB;
|
||||
bits |= ((long) (encodeScale(envState.scaleAlpha) & 0x3)) << U_SCALE_ALPHA;
|
||||
|
||||
// Only pack args actually used by each channel's combine function.
|
||||
// Unused arg slots stay 0, preventing TexEnvState defaults from inflating the cache.
|
||||
final int numArgsRgb = combineNumArgs(combRgb);
|
||||
final int numArgsAlpha = combineNumArgs(combAlpha);
|
||||
final int maxArgs = Math.max(numArgsRgb, numArgsAlpha);
|
||||
|
||||
for (int a = 0; a < maxArgs; a++) {
|
||||
if (a < numArgsRgb) {
|
||||
final int srcR = encodeSource(envState.sourceRgb[a]);
|
||||
final int opR = encodeOperand(envState.operandRgb[a]);
|
||||
bits |= ((long) ((srcR & 0x7) | ((opR & 0x3) << 3))) << (U_ARG0_RGB + a * 5);
|
||||
if (srcR == SRC_CONSTANT) needsEnvColor = true;
|
||||
}
|
||||
if (a < numArgsAlpha) {
|
||||
final int srcA = encodeSource(envState.sourceAlpha[a]);
|
||||
final int opA = encodeOperand(envState.operandAlpha[a]);
|
||||
bits |= ((long) ((srcA & 0x7) | ((opA & 0x3) << 3))) << (U_ARG0_ALPHA + a * 5);
|
||||
if (srcA == SRC_CONSTANT) needsEnvColor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsEnvColor) {
|
||||
bits |= (1L << U_NEEDS_ENV_COLOR);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/** Create FragmentKey from packed scratch data. Only called on cache miss. */
|
||||
public static FragmentKey fromPacked(long[] scratch, int len) {
|
||||
final long[] copy = Arrays.copyOf(scratch, len);
|
||||
return new FragmentKey(copy, Arrays.hashCode(copy));
|
||||
}
|
||||
|
||||
/** Convenience: pack + create. Allocates. For infrequent paths. */
|
||||
public static FragmentKey fromState() {
|
||||
final long[] scratch = new long[MAX_UNITS];
|
||||
final int len = packFromState(scratch);
|
||||
return fromPacked(scratch, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof FragmentKey other)) return false;
|
||||
if (hash != other.hash) return false;
|
||||
return Arrays.equals(packed, other.packed);
|
||||
}
|
||||
|
||||
public int fogMode() { return (int) (packed[0] & 0x3); }
|
||||
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
|
||||
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
|
||||
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
|
||||
|
||||
private long unitBits(int i) {
|
||||
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
|
||||
}
|
||||
|
||||
public boolean unitEnabled(int i) { return (unitBits(i) & 1) != 0; }
|
||||
public int unitMode(int i) { return (int) ((unitBits(i) >> U_MODE) & 0x7); }
|
||||
public boolean unitNeedsEnvColor(int i) { return ((unitBits(i) >> U_NEEDS_ENV_COLOR) & 1) != 0; }
|
||||
public int unitCombineRgb(int i) { return (int) ((unitBits(i) >> U_COMBINE_RGB) & 0xF); }
|
||||
public int unitCombineAlpha(int i) { return (int) ((unitBits(i) >> U_COMBINE_ALPHA) & 0xF); }
|
||||
public int unitScaleShiftRgb(int i) { return (int) ((unitBits(i) >> U_SCALE_RGB) & 0x3); }
|
||||
public int unitScaleShiftAlpha(int i) { return (int) ((unitBits(i) >> U_SCALE_ALPHA) & 0x3); }
|
||||
|
||||
public int unitSourceRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5)) & 0x7); }
|
||||
public int unitOperandRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5 + 3)) & 0x3); }
|
||||
public int unitSourceAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5)) & 0x7); }
|
||||
public int unitOperandAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5 + 3)) & 0x3); }
|
||||
|
||||
public boolean textureEnabled() {
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (unitEnabled(i)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean lightmapEnabled() {
|
||||
return nrEnabledUnits() > 1 && unitEnabled(1);
|
||||
}
|
||||
|
||||
private static int encodeAlphaFunc(int glFunc) {
|
||||
return glFunc & 0x7;
|
||||
}
|
||||
|
||||
static int encodeTexEnvMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_MODULATE -> TEX_ENV_MODULATE;
|
||||
case GL11.GL_REPLACE -> TEX_ENV_REPLACE;
|
||||
case GL11.GL_ADD -> TEX_ENV_ADD;
|
||||
case GL11.GL_DECAL -> TEX_ENV_DECAL;
|
||||
case GL11.GL_BLEND -> TEX_ENV_BLEND;
|
||||
case GL13.GL_COMBINE -> TEX_ENV_COMBINE;
|
||||
default -> TEX_ENV_MODULATE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeCombineFunc(int glFunc) {
|
||||
return switch (glFunc) {
|
||||
case GL11.GL_REPLACE -> COMBINE_REPLACE;
|
||||
case GL11.GL_MODULATE -> COMBINE_MODULATE;
|
||||
case GL11.GL_ADD -> COMBINE_ADD;
|
||||
case GL13.GL_ADD_SIGNED -> COMBINE_ADD_SIGNED;
|
||||
case GL13.GL_INTERPOLATE -> COMBINE_INTERPOLATE;
|
||||
case GL13.GL_SUBTRACT -> COMBINE_SUBTRACT;
|
||||
case GL13.GL_DOT3_RGB -> COMBINE_DOT3_RGB;
|
||||
case GL13.GL_DOT3_RGBA -> COMBINE_DOT3_RGBA;
|
||||
default -> COMBINE_REPLACE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeSource(int glSource) {
|
||||
return switch (glSource) {
|
||||
case GL11.GL_TEXTURE -> SRC_TEXTURE;
|
||||
case GL13.GL_CONSTANT -> SRC_CONSTANT;
|
||||
case GL13.GL_PRIMARY_COLOR -> SRC_PRIMARY_COLOR;
|
||||
case GL13.GL_PREVIOUS -> SRC_PREVIOUS;
|
||||
default -> {
|
||||
LOGGER.debug("Unknown TexEnv source 0x{}, falling back to SRC_PREVIOUS", Integer.toHexString(glSource));
|
||||
yield SRC_PREVIOUS;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeOperand(int glOperand) {
|
||||
return switch (glOperand) {
|
||||
case GL11.GL_SRC_COLOR -> OP_SRC_COLOR;
|
||||
case GL11.GL_ONE_MINUS_SRC_COLOR -> OP_ONE_MINUS_SRC_COLOR;
|
||||
case GL11.GL_SRC_ALPHA -> OP_SRC_ALPHA;
|
||||
case GL11.GL_ONE_MINUS_SRC_ALPHA -> OP_ONE_MINUS_SRC_ALPHA;
|
||||
default -> OP_SRC_COLOR;
|
||||
};
|
||||
}
|
||||
|
||||
/** Number of source/operand args used by the given (encoded) combine function. */
|
||||
public static int combineNumArgs(int func) {
|
||||
return switch (func) {
|
||||
case COMBINE_REPLACE -> 1;
|
||||
case COMBINE_INTERPOLATE -> 3;
|
||||
default -> 2; // MODULATE, ADD, ADD_SIGNED, SUBTRACT, DOT3_*
|
||||
};
|
||||
}
|
||||
|
||||
private static int encodeScale(float scale) {
|
||||
if (scale >= 4.0f) return 2;
|
||||
if (scale >= 2.0f) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int decodeAlphaFunc(int encoded) {
|
||||
return 0x0200 | encoded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String fogName = switch (fogMode()) {
|
||||
case FOG_NONE -> "NONE";
|
||||
case FOG_LINEAR -> "LINEAR";
|
||||
case FOG_EXP -> "EXP";
|
||||
case FOG_EXP2 -> "EXP2";
|
||||
default -> "?";
|
||||
};
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
|
||||
fogName, alphaTestEnabled(),
|
||||
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
|
||||
separateSpecular(), nrEnabledUnits()));
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (!unitEnabled(i)) {
|
||||
sb.append(String.format(" u%d=OFF", i));
|
||||
} else {
|
||||
final String modeName = switch (unitMode(i)) {
|
||||
case TEX_ENV_MODULATE -> "MOD";
|
||||
case TEX_ENV_REPLACE -> "REPL";
|
||||
case TEX_ENV_ADD -> "ADD";
|
||||
case TEX_ENV_DECAL -> "DEC";
|
||||
case TEX_ENV_BLEND -> "BL";
|
||||
case TEX_ENV_COMBINE -> "COMB";
|
||||
default -> "?";
|
||||
};
|
||||
sb.append(String.format(" u%d=%s", i, modeName));
|
||||
}
|
||||
}
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core fragment shaders for FFP emulation.
|
||||
* Supports per-unit texenv chain including GL_COMBINE mode.
|
||||
* Follows Mesa's ff_fragment_shader.c and st_nir_lower_fog.c.
|
||||
*/
|
||||
public final class FragmentShaderGenerator {
|
||||
|
||||
private FragmentShaderGenerator() {}
|
||||
|
||||
public static String generate(FragmentKey key) {
|
||||
final StringBuilder sb = new StringBuilder(2048);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
emitInputs(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
sb.append("out vec4 fragColor;\n\n");
|
||||
|
||||
sb.append("void main() {\n");
|
||||
emitTextureSampling(sb, key);
|
||||
emitTexEnvChain(sb, key);
|
||||
emitSpecularAdd(sb, key);
|
||||
emitAlphaTest(sb, key);
|
||||
emitFog(sb, key);
|
||||
sb.append(" fragColor = color;\n");
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void emitInputs(StringBuilder sb, FragmentKey key) {
|
||||
sb.append("// Inputs from vertex shader\n");
|
||||
sb.append("in vec4 v_Color;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("in vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("in float v_FogCoord;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
|
||||
// Samplers for each enabled unit
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (key.unitEnabled(i)) {
|
||||
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
|
||||
}
|
||||
}
|
||||
if (key.alphaTestEnabled()) {
|
||||
sb.append("uniform float u_AlphaRef;\n");
|
||||
}
|
||||
// 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.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("uniform vec4 u_FogParams;\n");
|
||||
sb.append("uniform vec4 u_FogColor;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
// Unit 1 is the lightmap — uses v_TexCoord1.
|
||||
// Units 2-3 intentionally share unit 0's texture coordinates since
|
||||
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
|
||||
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
|
||||
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitTexEnvChain(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.textureEnabled()) {
|
||||
sb.append(" // No texture — vertex color only\n");
|
||||
sb.append(" vec4 color = v_Color;\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process units in order; "previous" starts as vertex color
|
||||
boolean firstUnit = true;
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
|
||||
final String texVar = "tex" + i + "Color";
|
||||
final String envColorVar = "u_TexEnvColor" + i;
|
||||
final String prevVar = firstUnit ? "v_Color" : "color";
|
||||
final String assign = firstUnit ? " vec4 color = " : " color = ";
|
||||
|
||||
if (key.unitMode(i) == FragmentKey.TEX_ENV_COMBINE) {
|
||||
emitCombineUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
|
||||
} else {
|
||||
emitSimpleUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
|
||||
}
|
||||
firstUnit = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
|
||||
switch (key.unitMode(unit)) {
|
||||
case FragmentKey.TEX_ENV_REPLACE -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (REPLACE)\n");
|
||||
sb.append(assign).append(texVar).append(";\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_ADD -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (ADD)\n");
|
||||
sb.append(assign).append("vec4(clamp(").append(prevVar).append(".rgb + ").append(texVar).append(".rgb, 0.0, 1.0), ")
|
||||
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_DECAL -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (DECAL)\n");
|
||||
sb.append(assign).append("vec4(mix(").append(prevVar).append(".rgb, ").append(texVar).append(".rgb, ").append(texVar).append(".a), ")
|
||||
.append(prevVar).append(".a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_BLEND -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (BLEND)\n");
|
||||
sb.append(assign).append("vec4(").append(prevVar).append(".rgb * (vec3(1.0) - ").append(texVar).append(".rgb) + ")
|
||||
.append(envColorVar).append(".rgb * ").append(texVar).append(".rgb, ")
|
||||
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
|
||||
}
|
||||
default -> { // MODULATE
|
||||
sb.append(" // Unit ").append(unit).append(" (MODULATE)\n");
|
||||
sb.append(assign).append(prevVar).append(" * ").append(texVar).append(";\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Emit a GL_COMBINE texenv unit */
|
||||
private static void emitCombineUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
|
||||
sb.append(" // Unit ").append(unit).append(" (COMBINE)\n");
|
||||
|
||||
final String rgbResult = "combRgb" + unit;
|
||||
final String alphaResult = "combAlpha" + unit;
|
||||
|
||||
if (key.unitCombineRgb(unit) == FragmentKey.COMBINE_DOT3_RGBA) {
|
||||
sb.append(" vec3 ").append(rgbResult).append(";\n");
|
||||
sb.append(" {\n");
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
|
||||
sb.append(" }\n");
|
||||
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(rgbResult).append(".x);\n");
|
||||
} else {
|
||||
sb.append(" vec3 ").append(rgbResult).append(";\n");
|
||||
sb.append(" float ").append(alphaResult).append(";\n");
|
||||
sb.append(" {\n");
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, false);
|
||||
sb.append(" }\n");
|
||||
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(alphaResult).append(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit one channel (RGB or Alpha) of a GL_COMBINE unit.
|
||||
*/
|
||||
private static void emitCombineChannel(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, boolean isRgb) {
|
||||
final String suffix = isRgb ? "Rgb" : "Alpha";
|
||||
final String type = isRgb ? "vec3" : "float";
|
||||
final int combineFunc = isRgb ? key.unitCombineRgb(unit) : key.unitCombineAlpha(unit);
|
||||
final int scaleShift = isRgb ? key.unitScaleShiftRgb(unit) : key.unitScaleShiftAlpha(unit);
|
||||
|
||||
// Guard: DOT3 is invalid for alpha channel — fallback to REPLACE
|
||||
final int effectiveFunc;
|
||||
if (!isRgb && (combineFunc == FragmentKey.COMBINE_DOT3_RGB || combineFunc == FragmentKey.COMBINE_DOT3_RGBA)) {
|
||||
effectiveFunc = FragmentKey.COMBINE_REPLACE;
|
||||
} else {
|
||||
effectiveFunc = combineFunc;
|
||||
}
|
||||
|
||||
// Determine how many args we need
|
||||
final int numArgs = combineNumArgs(effectiveFunc);
|
||||
|
||||
// Emit argument resolution
|
||||
for (int a = 0; a < numArgs; a++) {
|
||||
final int source = isRgb ? key.unitSourceRgb(unit, a) : key.unitSourceAlpha(unit, a);
|
||||
final int operand = isRgb ? key.unitOperandRgb(unit, a) : key.unitOperandAlpha(unit, a);
|
||||
final String argName = "arg" + suffix + a + "_" + unit;
|
||||
final String sourceExpr = resolveSource(source, texVar, envColorVar, prevVar);
|
||||
final String operandExpr = applyOperand(sourceExpr, operand, isRgb);
|
||||
sb.append(" ").append(type).append(" ").append(argName).append(" = ").append(operandExpr).append(";\n");
|
||||
}
|
||||
|
||||
// Emit combine operation
|
||||
final String resultName = "comb" + suffix + unit;
|
||||
final String a0 = "arg" + suffix + "0_" + unit;
|
||||
final String a1 = "arg" + suffix + "1_" + unit;
|
||||
final String a2 = "arg" + suffix + "2_" + unit;
|
||||
|
||||
sb.append(" ").append(resultName).append(" = ");
|
||||
switch (effectiveFunc) {
|
||||
case FragmentKey.COMBINE_REPLACE -> sb.append(a0);
|
||||
case FragmentKey.COMBINE_MODULATE -> sb.append(a0).append(" * ").append(a1);
|
||||
case FragmentKey.COMBINE_ADD -> sb.append(a0).append(" + ").append(a1);
|
||||
case FragmentKey.COMBINE_ADD_SIGNED ->
|
||||
sb.append(a0).append(" + ").append(a1).append(" - ").append(isRgb ? "vec3(0.5)" : "0.5");
|
||||
case FragmentKey.COMBINE_SUBTRACT -> sb.append(a0).append(" - ").append(a1);
|
||||
case FragmentKey.COMBINE_INTERPOLATE ->
|
||||
sb.append("mix(").append(a1).append(", ").append(a0).append(", ").append(a2).append(")");
|
||||
case FragmentKey.COMBINE_DOT3_RGB, FragmentKey.COMBINE_DOT3_RGBA ->
|
||||
sb.append("vec3(4.0 * dot(").append(a0).append(" - vec3(0.5), ").append(a1).append(" - vec3(0.5)))");
|
||||
default -> sb.append(a0); // fallback to REPLACE
|
||||
}
|
||||
sb.append(";\n");
|
||||
|
||||
// Apply scale
|
||||
if (scaleShift > 0) {
|
||||
final String scaleFactor = scaleShift == 1 ? "2.0" : "4.0";
|
||||
sb.append(" ").append(resultName).append(" *= ").append(scaleFactor).append(";\n");
|
||||
}
|
||||
|
||||
// Clamp
|
||||
sb.append(" ").append(resultName).append(" = clamp(").append(resultName).append(", ")
|
||||
.append(isRgb ? "vec3(0.0), vec3(1.0)" : "0.0, 1.0").append(");\n");
|
||||
}
|
||||
|
||||
private static String resolveSource(int source, String texVar, String envColorVar, String prevVar) {
|
||||
return switch (source) {
|
||||
case FragmentKey.SRC_TEXTURE -> texVar;
|
||||
case FragmentKey.SRC_CONSTANT -> envColorVar;
|
||||
case FragmentKey.SRC_PRIMARY_COLOR -> "v_Color";
|
||||
case FragmentKey.SRC_PREVIOUS -> prevVar;
|
||||
default -> prevVar;
|
||||
};
|
||||
}
|
||||
|
||||
private static String applyOperand(String sourceExpr, int operand, boolean isRgb) {
|
||||
if (isRgb) {
|
||||
return switch (operand) {
|
||||
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".rgb";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(vec3(1.0) - " + sourceExpr + ".rgb)";
|
||||
case FragmentKey.OP_SRC_ALPHA -> "vec3(" + sourceExpr + ".a)";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "vec3(1.0 - " + sourceExpr + ".a)";
|
||||
default -> sourceExpr + ".rgb";
|
||||
};
|
||||
} else {
|
||||
return switch (operand) {
|
||||
case FragmentKey.OP_SRC_ALPHA -> sourceExpr + ".a";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "(1.0 - " + sourceExpr + ".a)";
|
||||
// Per GL spec, alpha channel operands are SRC_ALPHA or ONE_MINUS_SRC_ALPHA only, but handle color operands gracefully
|
||||
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".a";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(1.0 - " + sourceExpr + ".a)";
|
||||
default -> sourceExpr + ".a";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static int combineNumArgs(int func) {
|
||||
return FragmentKey.combineNumArgs(func);
|
||||
}
|
||||
|
||||
private static void emitSpecularAdd(StringBuilder sb, FragmentKey key) {
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" // Add separate specular\n");
|
||||
sb.append(" color.rgb += v_SpecularColor;\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitAlphaTest(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.alphaTestEnabled()) return;
|
||||
|
||||
sb.append(" // Alpha test\n");
|
||||
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
|
||||
|
||||
switch (func) {
|
||||
case GL11.GL_NEVER ->
|
||||
sb.append(" discard;\n");
|
||||
case GL11.GL_LESS ->
|
||||
sb.append(" if (color.a >= u_AlphaRef) discard;\n");
|
||||
case GL11.GL_EQUAL ->
|
||||
sb.append(" if (color.a != u_AlphaRef) discard;\n");
|
||||
case GL11.GL_LEQUAL ->
|
||||
sb.append(" if (color.a > u_AlphaRef) discard;\n");
|
||||
case GL11.GL_GREATER ->
|
||||
sb.append(" if (color.a <= u_AlphaRef) discard;\n");
|
||||
case GL11.GL_NOTEQUAL ->
|
||||
sb.append(" if (color.a == u_AlphaRef) discard;\n");
|
||||
case GL11.GL_GEQUAL ->
|
||||
sb.append(" if (color.a < u_AlphaRef) discard;\n");
|
||||
case GL11.GL_ALWAYS -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitFog(StringBuilder sb, FragmentKey key) {
|
||||
final int fogMode = key.fogMode();
|
||||
if (fogMode == FragmentKey.FOG_NONE) return;
|
||||
|
||||
sb.append(" // Fog\n");
|
||||
sb.append(" float f;\n");
|
||||
|
||||
switch (fogMode) {
|
||||
case FragmentKey.FOG_LINEAR ->
|
||||
sb.append(" f = v_FogCoord * u_FogParams.x + u_FogParams.y;\n");
|
||||
case FragmentKey.FOG_EXP ->
|
||||
sb.append(" f = exp2(-(v_FogCoord * u_FogParams.z));\n");
|
||||
case FragmentKey.FOG_EXP2 -> {
|
||||
sb.append(" float fogTmp = v_FogCoord * u_FogParams.w;\n");
|
||||
sb.append(" f = exp2(-(fogTmp * fogTmp));\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" f = clamp(f, 0.0, 1.0);\n");
|
||||
sb.append(" color.rgb = mix(u_FogColor.rgb, color.rgb, f);\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core geometry shaders for wide line emulation.
|
||||
*/
|
||||
public final class GeometryShaderGenerator {
|
||||
|
||||
private GeometryShaderGenerator() {}
|
||||
|
||||
public static String generate(VertexKey key) {
|
||||
final StringBuilder sb = new StringBuilder(1024);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
sb.append("layout(lines) in;\n");
|
||||
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
|
||||
|
||||
sb.append("uniform vec2 u_ViewportSize;\n");
|
||||
sb.append("uniform float u_LineWidth;\n\n");
|
||||
|
||||
sb.append("// Pass-through varyings\n");
|
||||
sb.append("in vec4 v_Color_gs[];\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("in vec3 v_SpecularColor_gs[];\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord0_gs[];\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord1_gs[];\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("in float v_FogCoord_gs[];\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
|
||||
sb.append("out vec4 v_Color;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("out float v_FogCoord;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
|
||||
sb.append("void main() {\n");
|
||||
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
|
||||
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
|
||||
|
||||
sb.append(" // NDC positions\n");
|
||||
sb.append(" vec2 n0 = c0.xy / c0.w;\n");
|
||||
sb.append(" vec2 n1 = c1.xy / c1.w;\n\n");
|
||||
|
||||
sb.append(" // Screen-space direction and perpendicular\n");
|
||||
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
|
||||
sb.append(" vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;\n\n");
|
||||
|
||||
emitVertex(sb, key, 0, "+offset");
|
||||
emitVertex(sb, key, 0, "-offset");
|
||||
emitVertex(sb, key, 1, "+offset");
|
||||
emitVertex(sb, key, 1, "-offset");
|
||||
|
||||
sb.append(" EndPrimitive();\n");
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
|
||||
final String ci = "c" + endpointIdx;
|
||||
final String ni = "n" + endpointIdx;
|
||||
final String idx = "[" + endpointIdx + "]";
|
||||
|
||||
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
|
||||
|
||||
sb.append(" v_Color = v_Color_gs").append(idx).append(";\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
|
||||
}
|
||||
|
||||
if (key.clipPlanesEnabled()) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = gl_in").append(idx).append(".gl_ClipDistance[").append(i).append("];\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(ci).append(".w, ").append(ci).append(".z, ").append(ci).append(".w);\n");
|
||||
sb.append(" EmitVertex();\n\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import lombok.Getter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.KHRDebug;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* A compiled and linked FFP emulation program (vertex + fragment + optional geometry shaders). Owns the GL program handle and caches uniform locations.
|
||||
*/
|
||||
public class Program {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("FFPProgram");
|
||||
private static final AtomicInteger PROGRAM_COUNTER = new AtomicInteger();
|
||||
|
||||
@Getter private final int programId;
|
||||
@Getter private final VertexKey vertexKey;
|
||||
@Getter private final FragmentKey fragmentKey;
|
||||
|
||||
// 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 int locTextureMatrix0 = -1;
|
||||
|
||||
// Current state defaults
|
||||
public int locCurrentNormal = -1;
|
||||
public int locCurrentColor = -1;
|
||||
public int locCurrentTexCoord = -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;
|
||||
|
||||
// Fragment uniforms
|
||||
public final int[] locSampler = { -1, -1, -1, -1 };
|
||||
public int locAlphaRef = -1;
|
||||
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
|
||||
public int locFogParams = -1;
|
||||
public int locFogColor = -1;
|
||||
|
||||
Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
|
||||
this.programId = programId;
|
||||
this.vertexKey = vertexKey;
|
||||
this.fragmentKey = fragmentKey;
|
||||
resolveLocations();
|
||||
}
|
||||
|
||||
private void resolveLocations() {
|
||||
locModelViewMatrix = loc("u_ModelViewMatrix");
|
||||
locProjectionMatrix = loc("u_ProjectionMatrix");
|
||||
locMVPMatrix = loc("u_MVPMatrix");
|
||||
locNormalMatrix = loc("u_NormalMatrix");
|
||||
locTextureMatrix0 = loc("u_TextureMatrix0");
|
||||
|
||||
locCurrentNormal = loc("u_CurrentNormal");
|
||||
locCurrentColor = loc("u_CurrentColor");
|
||||
locCurrentTexCoord = loc("u_CurrentTexCoord");
|
||||
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");
|
||||
|
||||
// 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);
|
||||
}
|
||||
locFogParams = loc("u_FogParams");
|
||||
locFogColor = loc("u_FogColor");
|
||||
}
|
||||
|
||||
private int loc(String name) {
|
||||
return GL20.glGetUniformLocation(programId, name);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
GL20.glDeleteProgram(programId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile vertex + fragment + optional geometry shaders, link into a program, and return the FFPProgram.
|
||||
*/
|
||||
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc, String geomSrc) {
|
||||
final int id = PROGRAM_COUNTER.getAndIncrement();
|
||||
final String vkHex = Long.toHexString(vk.pack());
|
||||
final boolean hasGeom = geomSrc != null;
|
||||
|
||||
int shaderCount = 0;
|
||||
final int[] shaders = new int[5]; // vertex, fragment, geometry, tess control, tess evaluation
|
||||
int program = 0;
|
||||
try {
|
||||
shaders[shaderCount++] = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + vkHex);
|
||||
shaders[shaderCount++] = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
|
||||
if (geomSrc != null) {
|
||||
shaders[shaderCount++] = compileShader(GL32.GL_GEOMETRY_SHADER, geomSrc, "ffp_g_" + id);
|
||||
}
|
||||
|
||||
program = GL20.glCreateProgram();
|
||||
for (int i = 0; i < shaderCount; i++) GL20.glAttachShader(program, shaders[i]);
|
||||
GL20.glLinkProgram(program);
|
||||
|
||||
final String log = RenderSystem.getProgramInfoLog(program);
|
||||
if (!log.isEmpty()) {
|
||||
LOGGER.warn("FFP program link log (vk=0x{}, fk={}): {}", vkHex, fk, log);
|
||||
}
|
||||
|
||||
if (GL20.glGetProgrami(program, GL20.GL_LINK_STATUS) != GL11.GL_TRUE) {
|
||||
throw new RuntimeException("FFP shader link failed (vk=0x" + vkHex + ", fk=" + fk + "): " + log);
|
||||
}
|
||||
|
||||
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
|
||||
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
|
||||
|
||||
// Detach and delete individual shaders — they're linked into the program
|
||||
for (int i = 0; i < shaderCount; i++) {
|
||||
GL20.glDetachShader(program, shaders[i]);
|
||||
GL20.glDeleteShader(shaders[i]);
|
||||
}
|
||||
shaderCount = 0;
|
||||
|
||||
final Program ffpProgram = new Program(program, vk, fk);
|
||||
|
||||
final int previousProgram = GLStateManager.getActiveProgram();
|
||||
GL20.glUseProgram(program);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (ffpProgram.locSampler[i] != -1) GL20.glUniform1i(ffpProgram.locSampler[i], i);
|
||||
}
|
||||
GL20.glUseProgram(previousProgram);
|
||||
|
||||
return ffpProgram;
|
||||
} catch (RuntimeException e) {
|
||||
if (program != 0) GL20.glDeleteProgram(program);
|
||||
for (int i = 0; i < shaderCount; i++) GL20.glDeleteShader(shaders[i]);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static int compileShader(int type, String src, String name) {
|
||||
final int shader = GL20.glCreateShader(type);
|
||||
GL20.glShaderSource(shader, src);
|
||||
GL20.glCompileShader(shader);
|
||||
|
||||
final String typeName = switch (type) {
|
||||
case GL20.GL_VERTEX_SHADER -> "vertex";
|
||||
case GL20.GL_FRAGMENT_SHADER -> "fragment";
|
||||
case GL32.GL_GEOMETRY_SHADER -> "geometry";
|
||||
case GL40.GL_TESS_CONTROL_SHADER -> "tess_control";
|
||||
case GL40.GL_TESS_EVALUATION_SHADER -> "tess_evaluation";
|
||||
case GL43.GL_COMPUTE_SHADER -> "compute";
|
||||
default -> "unknown(0x" + Integer.toHexString(type) + ")";
|
||||
};
|
||||
GLDebug.nameObject(KHRDebug.GL_SHADER, shader, name + "(" + typeName + ")");
|
||||
|
||||
final String log = RenderSystem.getShaderInfoLog(shader);
|
||||
if (!log.isEmpty()) {
|
||||
LOGGER.warn("FFP {} shader compilation log for {}: {}", typeName, name, log);
|
||||
}
|
||||
|
||||
final int result = GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS);
|
||||
if (result != GL11.GL_TRUE) {
|
||||
LOGGER.error("FFP {} shader source:\n{}", typeName, src);
|
||||
GL20.glDeleteShader(shader);
|
||||
throw new RuntimeException("FFP " + typeName + " shader compilation failed for " + name + ": " + log);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import lombok.Setter;
|
||||
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.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
|
||||
|
||||
/**
|
||||
* Open-addressed cache for FFP shader program variants.
|
||||
* Keyed on (vertexKey, fragmentKey) pairs stored inline as raw longs — zero allocation on cache hits.
|
||||
* Converges on Mesa's prog_cache.c: hash+memcmp over packed bytes, open addressing.
|
||||
*
|
||||
* <p>Layout per slot in {@code keys[]}: {@link #SLOT_STRIDE}=6 longs
|
||||
* <pre>
|
||||
* [0]: vertexKeyPacked
|
||||
* [1]: (fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFF) — never 0 for valid entries (fkLen >= 1)
|
||||
* [2..5]: fk0..fk3
|
||||
* </pre>
|
||||
*/
|
||||
public class ShaderCache {
|
||||
private static final Logger LOGGER = LogManager.getLogger("FFPShaderCache");
|
||||
private static final int SLOT_STRIDE = 6;
|
||||
private static final int INITIAL_CAPACITY = 64; // power of 2
|
||||
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
|
||||
|
||||
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
// Open-addressed table — parallel arrays
|
||||
private long[] keys;
|
||||
private Program[] programs;
|
||||
private String[] fragSources; // fragment source per slot; reused across VK variants via findFragSource
|
||||
private int capacity;
|
||||
private int count;
|
||||
private int uniqueFragCount;
|
||||
|
||||
// Fast-path: last-used key (raw longs, no objects)
|
||||
private long lastVK = Long.MIN_VALUE;
|
||||
private int lastFKLen;
|
||||
private long lastFK0, lastFK1, lastFK2, lastFK3;
|
||||
private Program lastProgram;
|
||||
|
||||
private int fragmentDumpCounter;
|
||||
|
||||
@Setter private Path dumpDir = null;
|
||||
|
||||
public ShaderCache() {
|
||||
capacity = INITIAL_CAPACITY;
|
||||
keys = new long[capacity * SLOT_STRIDE];
|
||||
programs = new Program[capacity];
|
||||
fragSources = new String[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create an FFP program for the given state keys.
|
||||
*
|
||||
* @param vkPacked packed vertex key
|
||||
* @param fkPacked pre-allocated scratch array with packed fragment key longs
|
||||
* @param fkLen number of significant longs in fkPacked (1..4)
|
||||
*/
|
||||
public Program getOrCreate(long vkPacked, long[] fkPacked, int fkLen) {
|
||||
// Fast-path: compare raw longs inline
|
||||
if (vkPacked == lastVK && fkLen == lastFKLen && lastProgram != null
|
||||
&& fkPacked[0] == lastFK0
|
||||
&& (fkLen < 2 || fkPacked[1] == lastFK1)
|
||||
&& (fkLen < 3 || fkPacked[2] == lastFK2)
|
||||
&& (fkLen < 4 || fkPacked[3] == lastFK3)) {
|
||||
return lastProgram;
|
||||
}
|
||||
|
||||
final long hash = hashKey(vkPacked, fkPacked, fkLen);
|
||||
final long storedWord = ((long) fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFFL);
|
||||
final int mask = capacity - 1;
|
||||
int idx = (int) (hash & mask);
|
||||
while (true) {
|
||||
final int base = idx * SLOT_STRIDE;
|
||||
final long slot1 = keys[base + 1];
|
||||
if (slot1 == EMPTY_MARKER) {
|
||||
break;
|
||||
}
|
||||
if (slot1 == storedWord && keys[base] == vkPacked && keysMatch(base + 2, fkPacked, fkLen)) {
|
||||
final Program p = programs[idx];
|
||||
setLastUsed(vkPacked, fkPacked, fkLen, p);
|
||||
return p;
|
||||
}
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
|
||||
if (count >= (capacity * 3) / 4) {
|
||||
grow();
|
||||
return getOrCreate(vkPacked, fkPacked, fkLen);
|
||||
}
|
||||
|
||||
final VertexKey vk = VertexKey.fromPacked(vkPacked);
|
||||
final String vertSrc = getOrGenerateVertex(vk);
|
||||
|
||||
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
|
||||
String fragSrc = findFragSource(fkPacked, fkLen);
|
||||
if (fragSrc == null) {
|
||||
fragSrc = FragmentShaderGenerator.generate(fk);
|
||||
uniqueFragCount++;
|
||||
}
|
||||
|
||||
final String geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
|
||||
final Program program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
|
||||
|
||||
final int insertIdx = findEmptySlot(hash);
|
||||
final int base = insertIdx * SLOT_STRIDE;
|
||||
keys[base] = vkPacked;
|
||||
keys[base + 1] = storedWord;
|
||||
for (int i = 0; i < fkLen; i++) keys[base + 2 + i] = fkPacked[i];
|
||||
programs[insertIdx] = program;
|
||||
fragSources[insertIdx] = fragSrc;
|
||||
count++;
|
||||
|
||||
setLastUsed(vkPacked, fkPacked, fkLen, program);
|
||||
|
||||
LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, count);
|
||||
|
||||
if (dumpDir != null) {
|
||||
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
|
||||
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
|
||||
if (geomSrc != null) {
|
||||
dumpShader(Long.toHexString(vkPacked), ".geom.glsl", geomSrc);
|
||||
}
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
private String findFragSource(long[] fkPacked, int fkLen) {
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
final int base = i * SLOT_STRIDE;
|
||||
if (keys[base + 1] == EMPTY_MARKER) continue;
|
||||
final int storedLen = (int) (keys[base + 1] >>> 48);
|
||||
if (storedLen != fkLen) continue;
|
||||
if (keysMatch(base + 2, fkPacked, fkLen)) {
|
||||
return fragSources[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean keysMatch(int keysOffset, long[] fkPacked, int fkLen) {
|
||||
for (int i = 0; i < fkLen; i++) {
|
||||
if (keys[keysOffset + i] != fkPacked[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setLastUsed(long vkPacked, long[] fkPacked, int fkLen, Program program) {
|
||||
lastVK = vkPacked;
|
||||
lastFKLen = fkLen;
|
||||
lastFK0 = fkPacked[0];
|
||||
lastFK1 = fkLen >= 2 ? fkPacked[1] : 0;
|
||||
lastFK2 = fkLen >= 3 ? fkPacked[2] : 0;
|
||||
lastFK3 = fkLen >= 4 ? fkPacked[3] : 0;
|
||||
lastProgram = program;
|
||||
}
|
||||
|
||||
private int findEmptySlot(long hash) {
|
||||
final int mask = capacity - 1;
|
||||
int idx = (int) (hash & mask);
|
||||
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash over (vk + fk longs). Adapted from Mesa's hash_key — mix-rotate-multiply.
|
||||
*/
|
||||
private static long hashKey(long vkPacked, long[] fkPacked, int fkLen) {
|
||||
long h = vkPacked * 0x9E3779B97F4A7C15L;
|
||||
for (int i = 0; i < fkLen; i++) {
|
||||
h ^= fkPacked[i] * 0x517CC1B727220A95L;
|
||||
h = Long.rotateLeft(h, 31) * 0x9E3779B97F4A7C15L;
|
||||
}
|
||||
return h | 1L; // ensure nonzero lower bits
|
||||
}
|
||||
|
||||
private void grow() {
|
||||
final int oldCap = capacity;
|
||||
final long[] oldKeys = keys;
|
||||
final Program[] oldPrograms = programs;
|
||||
final String[] oldFragSources = fragSources;
|
||||
|
||||
capacity = oldCap * 2;
|
||||
keys = new long[capacity * SLOT_STRIDE];
|
||||
programs = new Program[capacity];
|
||||
fragSources = new String[capacity];
|
||||
count = 0;
|
||||
|
||||
final int mask = capacity - 1;
|
||||
for (int i = 0; i < oldCap; i++) {
|
||||
final int oldBase = i * SLOT_STRIDE;
|
||||
if (oldKeys[oldBase + 1] == EMPTY_MARKER) continue;
|
||||
|
||||
final long storedHash = oldKeys[oldBase + 1] & 0x0000_FFFF_FFFF_FFFFL;
|
||||
int idx = (int) (storedHash & mask);
|
||||
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
final int newBase = idx * SLOT_STRIDE;
|
||||
System.arraycopy(oldKeys, oldBase, keys, newBase, SLOT_STRIDE);
|
||||
programs[idx] = oldPrograms[i];
|
||||
fragSources[idx] = oldFragSources[i];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
private String getOrGenerateVertex(VertexKey vk) {
|
||||
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
|
||||
}
|
||||
|
||||
private String getOrGenerateGeometry(VertexKey vk) {
|
||||
return geometrySourceCache.computeIfAbsent(vk.pack(), k -> GeometryShaderGenerator.generate(vk));
|
||||
}
|
||||
|
||||
private void dumpShader(String name, String suffix, String source) {
|
||||
if (dumpDir == null) return;
|
||||
try {
|
||||
Files.createDirectories(dumpDir);
|
||||
final Path file = dumpDir.resolve("ffp_" + name + suffix);
|
||||
if (!Files.exists(file)) {
|
||||
Files.writeString(file, source, StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to dump FFP shader to {}: {}", dumpDir, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Dump all cached shaders to disk. */
|
||||
public void dumpAll(Path outputDir) {
|
||||
try {
|
||||
Files.createDirectories(outputDir);
|
||||
for (var entry : vertexSourceCache.entrySet()) {
|
||||
dumpShader(Long.toHexString(entry.getKey()), ".vert.glsl", entry.getValue());
|
||||
}
|
||||
final IdentityHashMap<String, Integer> seen = new IdentityHashMap<>();
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
if (fragSources[i] != null && !seen.containsKey(fragSources[i])) {
|
||||
final int idx = seen.size();
|
||||
seen.put(fragSources[i], idx);
|
||||
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
|
||||
}
|
||||
}
|
||||
for (var entry : geometrySourceCache.entrySet()) {
|
||||
dumpShader(Long.toHexString(entry.getKey()), ".geom.glsl", entry.getValue());
|
||||
}
|
||||
LOGGER.info("Dumped {} vertex, {} geometry, and {} fragment FFP shader sources to {}",
|
||||
vertexSourceCache.size(), geometrySourceCache.size(), seen.size(), outputDir);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Destroy all cached programs and clear caches. */
|
||||
public void destroy() {
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
if (programs[i] != null) {
|
||||
programs[i].destroy();
|
||||
}
|
||||
}
|
||||
Arrays.fill(keys, 0);
|
||||
Arrays.fill(programs, null);
|
||||
Arrays.fill(fragSources, null);
|
||||
count = 0;
|
||||
uniqueFragCount = 0;
|
||||
vertexSourceCache.clear();
|
||||
geometrySourceCache.clear();
|
||||
lastProgram = null;
|
||||
lastVK = Long.MIN_VALUE;
|
||||
LOGGER.debug("FFP shader cache cleared");
|
||||
}
|
||||
|
||||
public int getProgramCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getVertexVariantCount() {
|
||||
return vertexSourceCache.size();
|
||||
}
|
||||
|
||||
public int getFragmentVariantCount() {
|
||||
return uniqueFragCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.gl.blending.BlendModeStorage;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* FFP shader manager
|
||||
*/
|
||||
public class ShaderManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger("FFPShaderManager");
|
||||
|
||||
private static final class Holder { static final ShaderManager INSTANCE = new ShaderManager(); }
|
||||
|
||||
private final ShaderCache cache = new ShaderCache();
|
||||
private final Uniforms uniforms = new Uniforms();
|
||||
private final Int2IntOpenHashMap vaoVertexFlags = new Int2IntOpenHashMap();
|
||||
|
||||
@Getter
|
||||
private boolean active = false;
|
||||
private Program currentProgram = null;
|
||||
private long currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
|
||||
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
|
||||
private int currentFKLen = 0;
|
||||
|
||||
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
|
||||
@Getter private static final Vector4f currentTexCoord = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
|
||||
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoord);
|
||||
@Getter private static int normalGeneration;
|
||||
@Getter private static int texCoordGeneration;
|
||||
@Getter private boolean enabled = false;
|
||||
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
|
||||
|
||||
private ShaderManager() {
|
||||
vaoVertexFlags.defaultReturnValue(-1);
|
||||
boolean isDev = false;
|
||||
try {
|
||||
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
|
||||
isDev = Boolean.TRUE.equals(deobfEnv);
|
||||
} catch (Exception ignored) {}
|
||||
if (isDev || Boolean.parseBoolean(System.getProperty("angelica.ffp.dumpShaders", "false"))) {
|
||||
cache.setDumpDir(Paths.get("ffp_shaders"));
|
||||
}
|
||||
}
|
||||
|
||||
public static ShaderManager getInstance() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
|
||||
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
|
||||
currentVertexFlags = format.getVertexFlags();
|
||||
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
|
||||
return false;
|
||||
});
|
||||
|
||||
LOGGER.info("FFP shader emulation enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
active = true;
|
||||
updateVariant(true, true, true, true);
|
||||
uploadUniforms();
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
currentFKLen = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
GLStateManager.flushDeferredVertexAttribs();
|
||||
BlendModeStorage.flushDeferredBlend();
|
||||
|
||||
if (!active) {
|
||||
if (enabled) {
|
||||
final int currentProgramId = GLStateManager.getActiveProgram();
|
||||
if (currentProgramId != 0) {
|
||||
CompatUniformManager.onUseProgram(currentProgramId);
|
||||
return;
|
||||
}
|
||||
active = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
|
||||
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
|
||||
commitVariant(vkPacked, fkLen);
|
||||
}
|
||||
|
||||
uploadUniforms();
|
||||
}
|
||||
|
||||
public void preDraw(int vertexFlags) {
|
||||
currentVertexFlags = vertexFlags;
|
||||
preDraw(
|
||||
(vertexFlags & VertexFlags.COLOR_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.NORMAL_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.TEXTURE_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0
|
||||
);
|
||||
}
|
||||
|
||||
public void preDraw() {
|
||||
preDraw(currentVertexFlags);
|
||||
}
|
||||
|
||||
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
commitVariant(vkPacked, fkLen);
|
||||
}
|
||||
|
||||
private void commitVariant(long vkPacked, int fkLen) {
|
||||
currentVertexKeyPacked = vkPacked;
|
||||
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
|
||||
currentFKLen = fkLen;
|
||||
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
|
||||
GL20.glUseProgram(currentProgram.getProgramId());
|
||||
}
|
||||
|
||||
private void uploadUniforms() {
|
||||
if (currentProgram != null) {
|
||||
uniforms.upload(currentProgram);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setCurrentNormal(float x, float y, float z) {
|
||||
currentNormal.set(x, y, z);
|
||||
normalGeneration++;
|
||||
}
|
||||
|
||||
public static void setCurrentTexCoord(float s, float t, float r, float q) {
|
||||
currentTexCoord.set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
public static void bumpNormalGeneration() { normalGeneration++; }
|
||||
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
|
||||
|
||||
public void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
|
||||
public void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
|
||||
public int getCurrentVertexFlags() { return currentVertexFlags; }
|
||||
public void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
|
||||
|
||||
public void onBindVertexArray(int vaoId) {
|
||||
final int flags = vaoVertexFlags.get(vaoId);
|
||||
if (flags != -1) {
|
||||
currentVertexFlags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeleteVertexArray(int vaoId) {
|
||||
vaoVertexFlags.remove(vaoId);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
cache.destroy();
|
||||
uniforms.destroy();
|
||||
TessellatorStreamingDrawer.destroy();
|
||||
com.gtnewhorizons.angelica.glsm.QuadConverter.destroy();
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
}
|
||||
|
||||
public String getDebugInfo() {
|
||||
return String.format("FFP: %d programs (%d vert, %d frag variants)",
|
||||
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.QuadConverter;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer.UploadStrategy;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
|
||||
/**
|
||||
* Replaces the vanilla Tessellator's FFP client-array draw path with a streaming VBO+VAO approach for GL 3.3 core profile compatibility.
|
||||
* <p>
|
||||
* Uses a persistent-mapped ring buffer on GL4.4+ hardware (zero-copy uploads with fence sync),
|
||||
* falling back to the classic orphan pattern on older hardware or on overflow.
|
||||
* Maintains two VAO sets (persistent + orphan) per vertex format (up to 16 combinations).
|
||||
*/
|
||||
public class TessellatorStreamingDrawer {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("TessellatorStreamingDrawer");
|
||||
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE; // 16
|
||||
|
||||
private static PersistentStreamingBuffer persistentBuffer;
|
||||
private static final OrphanStreamingBuffer[] orphanBuffers = new OrphanStreamingBuffer[FORMAT_COUNT];
|
||||
|
||||
private static final int[] persistentVAOs = new int[FORMAT_COUNT];
|
||||
private static final int[] orphanVAOs = new int[FORMAT_COUNT];
|
||||
|
||||
private static ByteBuffer repackBuffer;
|
||||
private static long repackAddress;
|
||||
private static int repackCapacity;
|
||||
|
||||
private static boolean initialized = false;
|
||||
private static UploadStrategy orphanStrategy = UploadStrategy.BUFFER_SUB_DATA;
|
||||
|
||||
static {
|
||||
// Initial repack buffer: 64KB
|
||||
repackCapacity = 0x10000;
|
||||
repackBuffer = memAlloc(repackCapacity);
|
||||
repackAddress = memAddress0(repackBuffer);
|
||||
}
|
||||
|
||||
private static void init() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
// Select orphan upload strategy from config
|
||||
if (AngelicaMod.options() != null && AngelicaMod.options().advanced.useMapBufferRange) {
|
||||
orphanStrategy = UploadStrategy.MAP_BUFFER_RANGE;
|
||||
}
|
||||
LOGGER.info("Orphan upload strategy: {}", orphanStrategy);
|
||||
|
||||
if (RenderSystem.supportsBufferStorage() && !Boolean.getBoolean("angelica.forceOrphanStreaming")) {
|
||||
try {
|
||||
persistentBuffer = new PersistentStreamingBuffer();
|
||||
LOGGER.info("Persistent streaming buffer created ({}MB)", PersistentStreamingBuffer.DEFAULT_CAPACITY / (1024 * 1024));
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to create persistent streaming buffer, using orphan fallback", e);
|
||||
persistentBuffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the vanilla Tessellator's data using streaming VBO+VAO instead of FFP client arrays.
|
||||
*/
|
||||
public static int draw(Tessellator tess) {
|
||||
if (!tess.isDrawing) {
|
||||
throw new IllegalStateException("Not tesselating!");
|
||||
}
|
||||
|
||||
tess.isDrawing = false;
|
||||
|
||||
final int vertexCount = tess.vertexCount;
|
||||
if (vertexCount == 0) {
|
||||
final int result = tess.rawBufferIndex * 4;
|
||||
tess.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Determine the optimal vertex format from the tessellator's flags
|
||||
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
|
||||
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
|
||||
final int vertexSize = format.getVertexSize();
|
||||
|
||||
final int requiredBytes = vertexCount * vertexSize;
|
||||
ensureRepackCapacity(requiredBytes);
|
||||
|
||||
final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex);
|
||||
repackBuffer.position(0);
|
||||
repackBuffer.limit((int)(writePtr - repackAddress));
|
||||
|
||||
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount);
|
||||
|
||||
// Shrink rawBuffer if oversized
|
||||
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) {
|
||||
tess.rawBufferSize = 0x10000;
|
||||
tess.rawBuffer = new int[tess.rawBufferSize];
|
||||
}
|
||||
|
||||
final int result = tess.rawBufferIndex * 4;
|
||||
tess.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw DirectTessellator data via streaming VBO+VAO. Used for live immediate mode emulation.
|
||||
*/
|
||||
public static void drawDirect(DirectTessellator dt) {
|
||||
final VertexFormat format = dt.getVertexFormat();
|
||||
if (format == null) return;
|
||||
|
||||
final int vertexCount = dt.getVertexCount();
|
||||
if (vertexCount == 0) return;
|
||||
|
||||
final int drawMode = dt.getDrawMode();
|
||||
final int flags = format.getVertexFlags();
|
||||
final ByteBuffer buffer = dt.getWriteBuffer();
|
||||
final int vertexSize = format.getVertexSize();
|
||||
|
||||
uploadAndDraw(buffer, flags, format, vertexSize, drawMode, vertexCount);
|
||||
}
|
||||
|
||||
private static String cachedDebugInfo = "Stream: not initialized";
|
||||
private static long lastDebugUpdateNanos;
|
||||
private static final long DEBUG_UPDATE_INTERVAL_NS = 500_000_000L; // 500ms
|
||||
|
||||
public static String getDebugInfo() {
|
||||
if (!initialized) return "Stream: not initialized";
|
||||
|
||||
final long now = System.nanoTime();
|
||||
if (now - lastDebugUpdateNanos < DEBUG_UPDATE_INTERVAL_NS) {
|
||||
return cachedDebugInfo;
|
||||
}
|
||||
lastDebugUpdateNanos = now;
|
||||
|
||||
int orphanCount = 0;
|
||||
int orphanBytes = 0;
|
||||
for (int i = 0; i < FORMAT_COUNT; i++) {
|
||||
if (orphanBuffers[i] != null) {
|
||||
orphanCount++;
|
||||
orphanBytes += orphanBuffers[i].getCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
cachedDebugInfo = String.format("Stream: Persistent %s (%s free) + %d orphan (%s)",
|
||||
formatBytes(persistentBuffer.getCapacity()), formatBytes(persistentBuffer.getRemaining()),
|
||||
orphanCount, formatBytes(orphanBytes));
|
||||
} else {
|
||||
cachedDebugInfo = String.format("Stream: Orphan (%d bufs, %s)", orphanCount, formatBytes(orphanBytes));
|
||||
}
|
||||
return cachedDebugInfo;
|
||||
}
|
||||
|
||||
private static String formatBytes(int bytes) {
|
||||
if (bytes >= 1024 * 1024) return String.format("%5.1fMB", bytes / (1024.0 * 1024.0));
|
||||
if (bytes >= 1024) return String.format("%5.1fKB", bytes / 1024.0);
|
||||
return String.format("%5dB", bytes);
|
||||
}
|
||||
|
||||
public static void endFrame() {
|
||||
if (persistentBuffer != null) {
|
||||
persistentBuffer.postDraw();
|
||||
}
|
||||
for (int i = 0; i < FORMAT_COUNT; i++) {
|
||||
if (orphanBuffers[i] != null) {
|
||||
orphanBuffers[i].postDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a merged batch of pre-packed vertex data from a DeferredBatchTessellator.
|
||||
* Copies byte ranges into repackBuffer, then uploads and draws.
|
||||
*/
|
||||
static void drawPackedBatch(DeferredBatchTessellator source,
|
||||
java.util.List<DeferredBatchTessellator.DrawRange> ranges,
|
||||
int from, int to,
|
||||
int totalBytes, int totalVertices,
|
||||
int drawMode, int flags) {
|
||||
if (totalVertices == 0) return;
|
||||
|
||||
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
|
||||
final int vertexSize = format.getVertexSize();
|
||||
|
||||
ensureRepackCapacity(totalBytes);
|
||||
|
||||
long writePos = repackAddress;
|
||||
for (int j = from; j < to; j++) {
|
||||
final DeferredBatchTessellator.DrawRange r = ranges.get(j);
|
||||
source.copyRange(r.byteOffset(), r.byteLength(), writePos);
|
||||
writePos += r.byteLength();
|
||||
}
|
||||
repackBuffer.position(0);
|
||||
repackBuffer.limit(totalBytes);
|
||||
|
||||
uploadAndDraw(repackBuffer, flags, format, vertexSize, drawMode, totalVertices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload packed vertex data to a streaming buffer and issue the draw call.
|
||||
* 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) {
|
||||
ensureVAO(flags, format);
|
||||
|
||||
int firstVertex = -1;
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
firstVertex = persistentBuffer.upload(packed, vertexSize);
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
firstVertex = orphanBuffers[flags].upload(packed, vertexSize);
|
||||
}
|
||||
|
||||
GLStateManager.prepareWideLineEmulation(drawMode);
|
||||
ShaderManager.getInstance().preDraw(flags);
|
||||
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
|
||||
if (drawMode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
|
||||
} else {
|
||||
GL11.glDrawArrays(drawMode, firstVertex, vertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureRepackCapacity(int requiredBytes) {
|
||||
if (requiredBytes <= repackCapacity) return;
|
||||
|
||||
int newCapacity = repackCapacity;
|
||||
while (newCapacity < requiredBytes) {
|
||||
newCapacity *= 2;
|
||||
}
|
||||
|
||||
memFree(repackBuffer);
|
||||
repackBuffer = memAlloc(newCapacity);
|
||||
repackAddress = memAddress0(repackBuffer);
|
||||
repackCapacity = newCapacity;
|
||||
}
|
||||
|
||||
private static void ensureVAO(int flags, VertexFormat format) {
|
||||
init();
|
||||
|
||||
if (orphanVAOs[flags] == 0) {
|
||||
orphanBuffers[flags] = new OrphanStreamingBuffer(orphanStrategy);
|
||||
|
||||
orphanVAOs[flags] = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
|
||||
format.setupBufferState(0L);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
if (persistentBuffer != null && persistentVAOs[flags] == 0) {
|
||||
persistentVAOs[flags] = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
|
||||
format.setupBufferState(0L);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all VAOs, streaming buffers, and the repack buffer.
|
||||
*/
|
||||
public static void destroy() {
|
||||
for (int i = 0; i < FORMAT_COUNT; i++) {
|
||||
if (persistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(persistentVAOs[i]); persistentVAOs[i] = 0; }
|
||||
if (orphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(orphanVAOs[i]); orphanVAOs[i] = 0; }
|
||||
if (orphanBuffers[i] != null) { orphanBuffers[i].destroy(); orphanBuffers[i] = null; }
|
||||
}
|
||||
if (persistentBuffer != null) {
|
||||
persistentBuffer.destroy();
|
||||
persistentBuffer = null;
|
||||
}
|
||||
if (repackBuffer != null) {
|
||||
memFree(repackBuffer);
|
||||
repackBuffer = null;
|
||||
repackAddress = 0;
|
||||
repackCapacity = 0;
|
||||
}
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
|
||||
/**
|
||||
* Uploads GLSM cached state to the active FFP program's uniforms.
|
||||
*
|
||||
* Follows Mesa's prog_statevars.c patterns:
|
||||
* - Pre-computed derived state (light products, scene color) when material is static
|
||||
* - Raw light/material values when color material is active
|
||||
* - Optimized fog params (pre-divided for single MAD in shader)
|
||||
*/
|
||||
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 final Matrix4f mvpMatrix = new Matrix4f();
|
||||
private final Matrix3f normalMatrix = new Matrix3f();
|
||||
|
||||
// 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 = Math.log(2.0);
|
||||
private static final double SQRT_LN2 = Math.sqrt(LN2);
|
||||
|
||||
// Dirty tracking: last-uploaded generation per category + program ID.
|
||||
// Program change forces full re-upload since uniform locations differ.
|
||||
private int lastProgramId = -1;
|
||||
private int lastMvGen = -1;
|
||||
private int lastProjGen = -1;
|
||||
private int lastTexMatGen = -1;
|
||||
private int lastLightingGen = -1;
|
||||
private int lastFragmentGen = -1;
|
||||
private int lastColorGen = -1;
|
||||
private int lastNormalGen = -1;
|
||||
private int lastTexCoordGen = -1;
|
||||
private float lastLightmapX = Float.NaN;
|
||||
private float lastLightmapY = Float.NaN;
|
||||
private int lastTexGenGen = -1;
|
||||
private int lastClipPlaneGen = -1;
|
||||
private float lastLineWidth = Float.NaN;
|
||||
private int lastViewportWidth = -1;
|
||||
private int lastViewportHeight = -1;
|
||||
|
||||
/**
|
||||
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
|
||||
* Uses generation counters to skip categories that haven't changed since last upload.
|
||||
*/
|
||||
public void upload(Program program) {
|
||||
final boolean programChanged = program.getProgramId() != lastProgramId;
|
||||
lastProgramId = program.getProgramId();
|
||||
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
if (program.getVertexKey().lightingEnabled()) {
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
uploadLighting(program);
|
||||
lastLightingGen = litGen;
|
||||
}
|
||||
}
|
||||
|
||||
// Current color/normal/texcoord — skip if generation unchanged
|
||||
if (!program.getVertexKey().hasVertexColor()) {
|
||||
final int colorGen = GLStateManager.colorGeneration;
|
||||
if (programChanged || colorGen != lastColorGen) {
|
||||
uploadCurrentColor(program);
|
||||
lastColorGen = colorGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
|
||||
final int normalGen = ShaderManager.getNormalGeneration();
|
||||
if (programChanged || normalGen != lastNormalGen) {
|
||||
uploadCurrentNormal(program);
|
||||
lastNormalGen = normalGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
|
||||
final int texGen = ShaderManager.getTexCoordGeneration();
|
||||
if (programChanged || texGen != lastTexCoordGen) {
|
||||
uploadCurrentTexCoord(program);
|
||||
lastTexCoordGen = texGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
|
||||
if (programChanged || OpenGlHelper.lastBrightnessX != lastLightmapX || OpenGlHelper.lastBrightnessY != lastLightmapY) {
|
||||
uploadCurrentLightmapCoord(program);
|
||||
lastLightmapX = OpenGlHelper.lastBrightnessX;
|
||||
lastLightmapY = OpenGlHelper.lastBrightnessY;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().texGenEnabled()) {
|
||||
final int tgGen = GLStateManager.texGenGeneration;
|
||||
if (programChanged || tgGen != lastTexGenGen) {
|
||||
uploadTexGen(program);
|
||||
lastTexGenGen = tgGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().clipPlanesEnabled()) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
uploadClipPlanes(program);
|
||||
lastClipPlaneGen = cpGen;
|
||||
}
|
||||
}
|
||||
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
uploadFragmentUniforms(program);
|
||||
lastFragmentGen = fragGen;
|
||||
}
|
||||
|
||||
// Wide line emulation uniforms
|
||||
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
|
||||
uploadWideLineUniforms(program, programChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
// ModelView + derived (normal matrix, normal scale)
|
||||
if (mvChanged) {
|
||||
if (program.locModelViewMatrix != -1) {
|
||||
mv.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
|
||||
}
|
||||
|
||||
// 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);
|
||||
GL20.glUniformMatrix3(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();
|
||||
GL20.glUniform1f(program.locNormalScale, scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Projection
|
||||
if (projChanged) {
|
||||
if (program.locProjectionMatrix != -1) {
|
||||
proj.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(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);
|
||||
GL20.glUniformMatrix4(program.locMVPMatrix, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Texture matrices
|
||||
if (texMatChanged) {
|
||||
// Texture matrix unit 0
|
||||
if (program.locTextureMatrix0 != -1) {
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
texMat.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(program.locTextureMatrix0, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Texture matrix unit 1 (lightmap)
|
||||
if (program.locLightmapTextureMatrix != -1) {
|
||||
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
|
||||
lmTexMat.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(program.locLightmapTextureMatrix, false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadLighting(Program program) {
|
||||
final VertexKey vk = program.getVertexKey();
|
||||
|
||||
if (vk.colorMaterialEnabled()) {
|
||||
uploadLightingColorMaterial(program, vk);
|
||||
} else {
|
||||
uploadLightingPreComputed(program, vk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Color material active: upload raw light and material values.
|
||||
* The shader multiplies them with vertex color at runtime.
|
||||
*/
|
||||
private void uploadLightingColorMaterial(Program program, VertexKey vk) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
|
||||
uploadVec4(program.locLightModelAmbient, GLStateManager.getLightModel().ambient);
|
||||
uploadVec4(program.locMaterialEmission, mat.emission);
|
||||
uploadVec4(program.locMaterialAmbient, mat.ambient);
|
||||
uploadVec4(program.locMaterialDiffuse, mat.diffuse);
|
||||
uploadVec4(program.locMaterialSpecular, mat.specular);
|
||||
if (program.locMaterialShininess != -1) {
|
||||
GL20.glUniform1f(program.locMaterialShininess, mat.shininess);
|
||||
}
|
||||
|
||||
if (vk.light0Enabled()) {
|
||||
final LightState light0 = GLStateManager.getLightDataStates()[0];
|
||||
uploadVec4(program.locLight0Ambient, light0.ambient);
|
||||
uploadVec4(program.locLight0Diffuse, light0.diffuse);
|
||||
uploadVec4(program.locLight0Specular, light0.specular);
|
||||
uploadVec4(program.locLight0Position, light0.position);
|
||||
}
|
||||
if (vk.light1Enabled()) {
|
||||
final LightState light1 = GLStateManager.getLightDataStates()[1];
|
||||
uploadVec4(program.locLight1Ambient, light1.ambient);
|
||||
uploadVec4(program.locLight1Diffuse, light1.diffuse);
|
||||
uploadVec4(program.locLight1Specular, light1.specular);
|
||||
uploadVec4(program.locLight1Position, light1.position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No color material: upload pre-computed light products and scene color.
|
||||
* Fewer shader multiplies since material is static.
|
||||
*/
|
||||
private void uploadLightingPreComputed(Program program, VertexKey vk) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
|
||||
// Scene color = emission + ambient * lightModel.ambient
|
||||
// Alpha = material.diffuse.a
|
||||
if (program.locSceneColor != -1) {
|
||||
tempVec4.set(
|
||||
mat.emission.x + mat.ambient.x * GLStateManager.getLightModel().ambient.x,
|
||||
mat.emission.y + mat.ambient.y * GLStateManager.getLightModel().ambient.y,
|
||||
mat.emission.z + mat.ambient.z * GLStateManager.getLightModel().ambient.z,
|
||||
mat.diffuse.w // alpha from diffuse
|
||||
);
|
||||
uploadVec4(program.locSceneColor, tempVec4);
|
||||
}
|
||||
|
||||
if (program.locMaterialShininess != -1) {
|
||||
GL20.glUniform1f(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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
GL20.glUniform3(loc, vec3Buf);
|
||||
}
|
||||
|
||||
private void uploadCurrentColor(Program program) {
|
||||
if (program.locCurrentColor == -1) return;
|
||||
// Upload the current color from GLSM
|
||||
final var color = GLStateManager.getColor();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(color.getRed());
|
||||
vec4Buf.put(color.getGreen());
|
||||
vec4Buf.put(color.getBlue());
|
||||
vec4Buf.put(color.getAlpha());
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(program.locCurrentColor, vec4Buf);
|
||||
}
|
||||
|
||||
private void uploadCurrentTexCoord(Program program) {
|
||||
if (program.locCurrentTexCoord == -1) return;
|
||||
final var tc = ShaderManager.getCurrentTexCoord();
|
||||
uploadVec4(program.locCurrentTexCoord, tc);
|
||||
}
|
||||
|
||||
private void uploadCurrentLightmapCoord(Program program) {
|
||||
if (program.locCurrentLightmapCoord == -1) return;
|
||||
GL20.glUniform2f(program.locCurrentLightmapCoord,
|
||||
OpenGlHelper.lastBrightnessX, OpenGlHelper.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();
|
||||
vec3Buf.put(normal.x);
|
||||
vec3Buf.put(normal.y);
|
||||
vec3Buf.put(normal.z);
|
||||
vec3Buf.flip();
|
||||
GL20.glUniform3(program.locCurrentNormal, vec3Buf);
|
||||
}
|
||||
|
||||
private void uploadTexGen(Program program) {
|
||||
final VertexKey vk = program.getVertexKey();
|
||||
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
|
||||
|
||||
if (vk.texGenModeS() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneS, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_S));
|
||||
} else if (vk.texGenModeS() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneS, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_S));
|
||||
}
|
||||
|
||||
if (vk.texGenModeT() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneT, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_T));
|
||||
} else if (vk.texGenModeT() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneT, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_T));
|
||||
}
|
||||
|
||||
if (vk.texGenModeR() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneR, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_R));
|
||||
} else if (vk.texGenModeR() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneR, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_R));
|
||||
}
|
||||
|
||||
if (vk.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneQ, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_Q));
|
||||
} else if (vk.texGenModeQ() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneQ, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_Q));
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadPlane(int loc, float[] plane) {
|
||||
if (loc == -1) return;
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(plane[0]);
|
||||
vec4Buf.put(plane[1]);
|
||||
vec4Buf.put(plane[2]);
|
||||
vec4Buf.put(plane[3]);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(loc, vec4Buf);
|
||||
}
|
||||
|
||||
private void uploadClipPlanes(Program program) {
|
||||
if (program.locClipPlanes == -1) return;
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
clipPlaneBuf.flip();
|
||||
GL20.glUniform4(program.locClipPlanes, clipPlaneBuf);
|
||||
}
|
||||
|
||||
private void uploadFragmentUniforms(Program program) {
|
||||
final FragmentKey fk = program.getFragmentKey();
|
||||
|
||||
// Alpha test reference
|
||||
if (fk.alphaTestEnabled() && program.locAlphaRef != -1) {
|
||||
GL20.glUniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
|
||||
}
|
||||
|
||||
// Per-unit tex env color
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (program.locTexEnvColor[i] != -1) {
|
||||
final var envState = GLStateManager.getTextures().getTexEnvState(i);
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(envState.envColorR);
|
||||
vec4Buf.put(envState.envColorG);
|
||||
vec4Buf.put(envState.envColorB);
|
||||
vec4Buf.put(envState.envColorA);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(program.locTexEnvColor[i], vec4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Fog
|
||||
if (fk.fogMode() != FragmentKey.FOG_NONE) {
|
||||
uploadFog(program);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload optimized fog params (following Mesa's STATE_FOG_PARAMS_OPTIMIZED):
|
||||
*/
|
||||
private void uploadFog(Program program) {
|
||||
final FogState fog = GLStateManager.getFogState();
|
||||
|
||||
if (program.locFogParams != -1) {
|
||||
final float start = fog.getStart();
|
||||
final float end = fog.getEnd();
|
||||
final float density = fog.getDensity();
|
||||
final float range = end - start;
|
||||
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(range != 0.0f ? -1.0f / range : 0.0f); // [0]: -1/(end-start)
|
||||
vec4Buf.put(range != 0.0f ? end / range : 1.0f); // [1]: end/(end-start)
|
||||
vec4Buf.put((float)(density / LN2)); // [2]: density/ln(2)
|
||||
vec4Buf.put((float)(density / SQRT_LN2)); // [3]: density/sqrt(ln(2))
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(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();
|
||||
GL20.glUniform4(program.locFogColor, vec4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadWideLineUniforms(Program program, boolean programChanged) {
|
||||
final float lineWidth = GLStateManager.getLineState().getWidth();
|
||||
if (programChanged || lineWidth != lastLineWidth) {
|
||||
GL20.glUniform1f(program.locLineWidth, lineWidth);
|
||||
lastLineWidth = lineWidth;
|
||||
}
|
||||
final int vw = GLStateManager.getViewportState().width;
|
||||
final int vh = GLStateManager.getViewportState().height;
|
||||
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
|
||||
GL20.glUniform2f(program.locViewportSize, vw, vh);
|
||||
lastViewportWidth = vw;
|
||||
lastViewportHeight = vh;
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadVec4(int loc, Vector4f v) {
|
||||
if (loc == -1) return;
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(v.x);
|
||||
vec4Buf.put(v.y);
|
||||
vec4Buf.put(v.z);
|
||||
vec4Buf.put(v.w);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(loc, vec4Buf);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
memFree(mat4Buf);
|
||||
memFree(mat3Buf);
|
||||
memFree(vec4Buf);
|
||||
memFree(vec3Buf);
|
||||
memFree(clipPlaneBuf);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
/**
|
||||
* Compact key capturing the vertex shader permutation axes for FFP emulation.
|
||||
* Packed into a single long for fast hash/compare.
|
||||
*/
|
||||
public final class VertexKey {
|
||||
|
||||
private final long packed;
|
||||
|
||||
private static final int BIT_LIGHTING = 0;
|
||||
private static final int BIT_LIGHT0 = 1;
|
||||
private static final int BIT_LIGHT1 = 2;
|
||||
private static final int BIT_LIGHT0_DIR = 3;
|
||||
private static final int BIT_LIGHT1_DIR = 4;
|
||||
private static final int BIT_COLOR_MATERIAL = 5;
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6;
|
||||
private static final int BIT_FOG = 7;
|
||||
private static final int BIT_FOG_DIST_MODE = 8;
|
||||
private static final int BIT_NORMALIZE = 10;
|
||||
private static final int BIT_RESCALE_NORMAL = 11;
|
||||
private static final int BIT_TEXTURE = 12;
|
||||
private static final int BIT_LIGHTMAP = 13;
|
||||
private static final int BIT_TEX_MATRIX = 14;
|
||||
private static final int BIT_HAS_VERTEX_COLOR = 15;
|
||||
private static final int BIT_HAS_VERTEX_NORMAL = 16;
|
||||
private static final int BIT_HAS_VERTEX_TEX = 17;
|
||||
private static final int BIT_HAS_VERTEX_LIGHTMAP = 18;
|
||||
private static final int BIT_COLOR_MAT_MODE = 19;
|
||||
// bits 19-21: colorMaterialMode (3 bits)
|
||||
// TexGen modes: 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR)
|
||||
private static final int BIT_TEXGEN_S = 22;
|
||||
private static final int BIT_TEXGEN_T = 25;
|
||||
private static final int BIT_TEXGEN_R = 28;
|
||||
private static final int BIT_TEXGEN_Q = 31;
|
||||
// bits 31-33: texGenModeQ (3 bits)
|
||||
private static final int BIT_CLIP_PLANES = 34;
|
||||
private static final int BIT_WIDE_LINE = 35;
|
||||
|
||||
public static final int TG_NONE = 0;
|
||||
public static final int TG_OBJ_LINEAR = 1;
|
||||
public static final int TG_EYE_LINEAR = 2;
|
||||
|
||||
public static final int CM_AMBIENT = 0;
|
||||
public static final int CM_DIFFUSE = 1;
|
||||
public static final int CM_SPECULAR = 2;
|
||||
public static final int CM_EMISSION = 3;
|
||||
public static final int CM_AMBIENT_AND_DIFFUSE = 4;
|
||||
|
||||
private VertexKey(long packed) {
|
||||
this.packed = packed;
|
||||
}
|
||||
|
||||
public long pack() { return packed; }
|
||||
|
||||
public boolean lightingEnabled() { return bit(BIT_LIGHTING); }
|
||||
public boolean light0Enabled() { return bit(BIT_LIGHT0); }
|
||||
public boolean light1Enabled() { return bit(BIT_LIGHT1); }
|
||||
public boolean light0Directional() { return bit(BIT_LIGHT0_DIR); }
|
||||
public boolean light1Directional() { return bit(BIT_LIGHT1_DIR); }
|
||||
public boolean colorMaterialEnabled() { return bit(BIT_COLOR_MATERIAL); }
|
||||
public boolean separateSpecular() { return bit(BIT_SEPARATE_SPECULAR); }
|
||||
public boolean fogEnabled() { return bit(BIT_FOG); }
|
||||
public int fogDistanceMode() { return (int)((packed >> BIT_FOG_DIST_MODE) & 0x3); }
|
||||
public boolean normalizeEnabled() { return bit(BIT_NORMALIZE); }
|
||||
public boolean rescaleNormalsEnabled() { return bit(BIT_RESCALE_NORMAL); }
|
||||
public boolean textureEnabled() { return bit(BIT_TEXTURE); }
|
||||
public boolean lightmapEnabled() { return bit(BIT_LIGHTMAP); }
|
||||
public boolean textureMatrixEnabled() { return bit(BIT_TEX_MATRIX); }
|
||||
public boolean hasVertexColor() { return bit(BIT_HAS_VERTEX_COLOR); }
|
||||
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
|
||||
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
|
||||
public boolean hasVertexLightmap() { return bit(BIT_HAS_VERTEX_LIGHTMAP); }
|
||||
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
|
||||
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
|
||||
|
||||
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). */
|
||||
public int texGenModeS() { return (int)((packed >> BIT_TEXGEN_S) & 0x7); }
|
||||
/** TexGen mode for T coordinate. */
|
||||
public int texGenModeT() { return (int)((packed >> BIT_TEXGEN_T) & 0x7); }
|
||||
/** TexGen mode for R coordinate. */
|
||||
public int texGenModeR() { return (int)((packed >> BIT_TEXGEN_R) & 0x7); }
|
||||
/** TexGen mode for Q coordinate. */
|
||||
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
|
||||
/** Whether any texgen coordinate is enabled. */
|
||||
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
|
||||
/** Whether any clip plane is enabled. */
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
/** Whether wide line GS emulation is active for this variant. */
|
||||
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
|
||||
|
||||
/** Whether vertex color replaces material ambient in this variant. */
|
||||
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
/** Whether vertex color replaces material diffuse in this variant. */
|
||||
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
/** Whether vertex color replaces material specular in this variant. */
|
||||
public boolean cmReplacesSpecular() { return colorMaterialMode() == CM_SPECULAR; }
|
||||
/** Whether vertex color replaces material emission in this variant. */
|
||||
public boolean cmReplacesEmission() { return colorMaterialMode() == CM_EMISSION; }
|
||||
|
||||
private boolean bit(int pos) { return ((packed >> pos) & 1) != 0; }
|
||||
|
||||
static int encodeTexGenMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_OBJECT_LINEAR -> TG_OBJ_LINEAR;
|
||||
case GL11.GL_EYE_LINEAR -> TG_EYE_LINEAR;
|
||||
default -> TG_NONE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeColorMaterialMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_AMBIENT -> CM_AMBIENT;
|
||||
case GL11.GL_DIFFUSE -> CM_DIFFUSE;
|
||||
case GL11.GL_SPECULAR -> CM_SPECULAR;
|
||||
case GL11.GL_EMISSION -> CM_EMISSION;
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> CM_AMBIENT_AND_DIFFUSE;
|
||||
default -> CM_AMBIENT_AND_DIFFUSE;
|
||||
};
|
||||
}
|
||||
|
||||
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
long bits = 0;
|
||||
|
||||
final boolean lighting = GLStateManager.getLightingState().isEnabled();
|
||||
if (lighting) {
|
||||
bits |= (1L << BIT_LIGHTING);
|
||||
|
||||
if (GLStateManager.getLightStates()[0].isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHT0);
|
||||
// position.w == 0 means directional
|
||||
if (GLStateManager.getLightDataStates()[0].position.w == 0.0f) {
|
||||
bits |= (1L << BIT_LIGHT0_DIR);
|
||||
}
|
||||
}
|
||||
if (GLStateManager.getLightStates()[1].isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHT1);
|
||||
if (GLStateManager.getLightDataStates()[1].position.w == 0.0f) {
|
||||
bits |= (1L << BIT_LIGHT1_DIR);
|
||||
}
|
||||
}
|
||||
|
||||
if (GLStateManager.getColorMaterial().isEnabled()) {
|
||||
bits |= (1L << BIT_COLOR_MATERIAL);
|
||||
final int cmMode = encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue());
|
||||
bits |= ((long) cmMode & 0x7) << BIT_COLOR_MAT_MODE;
|
||||
}
|
||||
|
||||
if (GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
bits |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
}
|
||||
}
|
||||
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
bits |= (1L << BIT_FOG);
|
||||
final int fogDistMode = GLStateManager.getFogState().getFogDistanceMode();
|
||||
bits |= ((long) fogDistMode & 0x3) << BIT_FOG_DIST_MODE;
|
||||
}
|
||||
|
||||
if (GLStateManager.getNormalizeState().isEnabled()) {
|
||||
bits |= (1L << BIT_NORMALIZE);
|
||||
}
|
||||
if (GLStateManager.getRescaleNormalState().isEnabled()) {
|
||||
bits |= (1L << BIT_RESCALE_NORMAL);
|
||||
}
|
||||
|
||||
// Texture unit 0 (base texture)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
|
||||
bits |= (1L << BIT_TEXTURE);
|
||||
}
|
||||
// Texture unit 1 (lightmap)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHTMAP);
|
||||
}
|
||||
|
||||
// Check if texture matrix unit 0 is non-identity
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
if (!DisplayListManager.isIdentity(texMat)) {
|
||||
bits |= (1L << BIT_TEX_MATRIX);
|
||||
}
|
||||
|
||||
// TexGen (unit 0 only) — encode per-coordinate mode if enabled
|
||||
final var texUnit = GLStateManager.getTextures();
|
||||
final TexGenState tg = texUnit.getTexGenState(0);
|
||||
boolean anyTexGen = false;
|
||||
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_S));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_S;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenTStates(0).isEnabled() && tg.getMode(GL11.GL_T) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_T));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_T;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenRStates(0).isEnabled() && tg.getMode(GL11.GL_R) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_R));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_R;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenQStates(0).isEnabled() && tg.getMode(GL11.GL_Q) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_Q));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_Q;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
// Force texture matrix on when texgen is active (end portal pattern always uses it)
|
||||
if (anyTexGen) {
|
||||
bits |= (1L << BIT_TEX_MATRIX);
|
||||
}
|
||||
|
||||
// Vertex format flags
|
||||
if (hasColor) bits |= (1L << BIT_HAS_VERTEX_COLOR);
|
||||
if (hasNormal) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
|
||||
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
|
||||
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
|
||||
|
||||
// Clip planes
|
||||
if (GLStateManager.anyClipPlaneEnabled()) {
|
||||
bits |= (1L << BIT_CLIP_PLANES);
|
||||
}
|
||||
|
||||
// Wide line GS emulation
|
||||
if (GLStateManager.wideLineEmulationActive) {
|
||||
bits |= (1L << BIT_WIDE_LINE);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap));
|
||||
}
|
||||
|
||||
static VertexKey fromPacked(long packed) {
|
||||
return new VertexKey(packed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(packed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof VertexKey other)) return false;
|
||||
return packed == other.packed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
|
||||
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
|
||||
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
|
||||
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
|
||||
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core vertex shaders for FFP emulation.
|
||||
* Follows Mesa's ffvertex_prog.c build order.
|
||||
*
|
||||
* Attribute locations are sourced from {@link VertexFormatElement.Usage} to stay in sync with GTNHLib's vertex format setup.
|
||||
*/
|
||||
public final class VertexShaderGenerator {
|
||||
|
||||
private VertexShaderGenerator() {}
|
||||
|
||||
public static String generate(VertexKey key) {
|
||||
final StringBuilder sb = new StringBuilder(2048);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
emitAttributes(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
emitOutputs(sb, key);
|
||||
|
||||
sb.append("void main() {\n");
|
||||
emitPositionTransform(sb, key);
|
||||
if (key.lightingEnabled()) {
|
||||
emitNormalTransform(sb, key);
|
||||
emitLighting(sb, key);
|
||||
} else {
|
||||
emitColorPassthrough(sb, key);
|
||||
}
|
||||
emitTexCoordPassthrough(sb, key);
|
||||
if (key.fogEnabled()) {
|
||||
emitFogDistance(sb, key);
|
||||
}
|
||||
if (key.clipPlanesEnabled()) {
|
||||
emitClipDistances(sb);
|
||||
}
|
||||
sb.append("}\n");
|
||||
|
||||
String source = sb.toString();
|
||||
if (key.wideLineEmulation()) {
|
||||
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
|
||||
.replace("v_TexCoord0", "v_TexCoord0_gs")
|
||||
.replace("v_TexCoord1", "v_TexCoord1_gs")
|
||||
.replace("v_FogCoord", "v_FogCoord_gs")
|
||||
.replace("v_Color", "v_Color_gs");
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private static void emitAttributes(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Vertex attributes\n");
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
|
||||
if (key.hasVertexColor()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
|
||||
}
|
||||
if (key.hasVertexTexCoord()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
|
||||
}
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
|
||||
}
|
||||
if (key.hasVertexNormal()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitUniforms(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Matrices\n");
|
||||
sb.append("uniform mat4 u_ModelViewMatrix;\n");
|
||||
sb.append("uniform mat4 u_ProjectionMatrix;\n");
|
||||
sb.append("uniform mat4 u_MVPMatrix;\n");
|
||||
|
||||
if (key.lightingEnabled()) {
|
||||
sb.append("uniform mat3 u_NormalMatrix;\n");
|
||||
}
|
||||
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append("uniform mat4 u_TextureMatrix0;\n");
|
||||
}
|
||||
|
||||
// TexGen plane uniforms
|
||||
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.hasVertexTexCoord() && key.textureEnabled()) {
|
||||
sb.append("uniform vec4 u_CurrentTexCoord;\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');
|
||||
}
|
||||
|
||||
private static void emitLightingUniforms(StringBuilder sb, VertexKey key) {
|
||||
sb.append("\n// Lighting\n");
|
||||
|
||||
if (key.colorMaterialEnabled()) {
|
||||
// When color material is active, we can't pre-compute light products so we upload raw light and material values
|
||||
sb.append("uniform vec4 u_LightModelAmbient;\n");
|
||||
sb.append("uniform vec4 u_MaterialEmission;\n");
|
||||
sb.append("uniform vec4 u_MaterialAmbient;\n");
|
||||
sb.append("uniform vec4 u_MaterialDiffuse;\n");
|
||||
sb.append("uniform vec4 u_MaterialSpecular;\n");
|
||||
sb.append("uniform float u_MaterialShininess;\n");
|
||||
|
||||
if (key.light0Enabled()) {
|
||||
sb.append("uniform vec4 u_Light0Ambient;\n");
|
||||
sb.append("uniform vec4 u_Light0Diffuse;\n");
|
||||
sb.append("uniform vec4 u_Light0Specular;\n");
|
||||
sb.append("uniform vec4 u_Light0Position;\n");
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
sb.append("uniform vec4 u_Light1Ambient;\n");
|
||||
sb.append("uniform vec4 u_Light1Diffuse;\n");
|
||||
sb.append("uniform vec4 u_Light1Specular;\n");
|
||||
sb.append("uniform vec4 u_Light1Position;\n");
|
||||
}
|
||||
} else {
|
||||
// Pre-computed: sceneColor + lightProducts
|
||||
sb.append("uniform vec4 u_SceneColor;\n");
|
||||
|
||||
if (key.light0Enabled()) {
|
||||
sb.append("uniform vec4 u_Light0Position;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Ambient;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Diffuse;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Specular;\n");
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
sb.append("uniform vec4 u_Light1Position;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Ambient;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Diffuse;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Specular;\n");
|
||||
}
|
||||
sb.append("uniform float u_MaterialShininess;\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitOutputs(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Outputs\n");
|
||||
sb.append("out vec4 v_Color;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("out float v_FogCoord;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Position transform\n");
|
||||
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
|
||||
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
|
||||
|
||||
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
|
||||
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
|
||||
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Normal transform\n");
|
||||
if (key.hasVertexNormal()) {
|
||||
sb.append(" vec3 normal = u_NormalMatrix * a_Normal;\n");
|
||||
} else {
|
||||
sb.append(" vec3 normal = u_NormalMatrix * u_CurrentNormal;\n");
|
||||
}
|
||||
|
||||
if (key.normalizeEnabled()) {
|
||||
sb.append(" normal = normalize(normal);\n");
|
||||
} else if (key.rescaleNormalsEnabled()) {
|
||||
sb.append(" normal *= u_NormalScale;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLighting(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Gouraud lighting\n");
|
||||
|
||||
if (key.colorMaterialEnabled()) {
|
||||
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
|
||||
sb.append(" vec4 matColor = ");
|
||||
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
|
||||
sb.append(";\n");
|
||||
|
||||
// Emission source: vertex color for CM_EMISSION, else material uniform
|
||||
final String emSrc = key.cmReplacesEmission() ? "matColor.rgb" : "u_MaterialEmission.rgb";
|
||||
// Ambient source for scene color: vertex color for CM_AMBIENT / CM_AMBIENT_AND_DIFFUSE
|
||||
final String amSrc = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
|
||||
sb.append(" vec3 sceneColor = ").append(emSrc).append(" + ").append(amSrc).append(" * u_LightModelAmbient.rgb;\n");
|
||||
|
||||
// Alpha from diffuse source (per GL spec)
|
||||
sb.append(" float outAlpha = ").append(key.cmReplacesDiffuse() ? "matColor.a" : "u_MaterialDiffuse.a").append(";\n");
|
||||
} else {
|
||||
// Pre-computed scene color
|
||||
sb.append(" vec3 sceneColor = u_SceneColor.rgb;\n");
|
||||
sb.append(" float outAlpha = u_SceneColor.a;\n");
|
||||
}
|
||||
|
||||
sb.append(" vec3 color0 = sceneColor;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" vec3 specAccum = vec3(0.0);\n");
|
||||
}
|
||||
|
||||
// Per-light accumulation
|
||||
if (key.light0Enabled()) {
|
||||
emitLightContribution(sb, key, 0);
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
emitLightContribution(sb, key, 1);
|
||||
}
|
||||
|
||||
sb.append(" v_Color = vec4(clamp(color0, 0.0, 1.0), outAlpha);\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" v_SpecularColor = clamp(specAccum, 0.0, 1.0);\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLightContribution(StringBuilder sb, VertexKey key, int lightIndex) {
|
||||
final String li = String.valueOf(lightIndex);
|
||||
final boolean directional = (lightIndex == 0) ? key.light0Directional() : key.light1Directional();
|
||||
final String posUniform = "u_Light" + li + "Position";
|
||||
|
||||
sb.append(" { // Light ").append(li).append('\n');
|
||||
|
||||
// Light vector
|
||||
if (directional) {
|
||||
// Directional: position is already normalized direction
|
||||
sb.append(" vec3 VPpli = normalize(").append(posUniform).append(".xyz);\n");
|
||||
} else {
|
||||
// Positional light
|
||||
sb.append(" vec3 VPpli = ").append(posUniform).append(".xyz - eyePos.xyz;\n");
|
||||
sb.append(" VPpli = normalize(VPpli);\n");
|
||||
}
|
||||
|
||||
// Dot products
|
||||
sb.append(" float NdotVP = max(dot(normal, VPpli), 0.0);\n");
|
||||
|
||||
// Half vector for specular (infinite viewer: H = normalize(L + (0,0,1)))
|
||||
sb.append(" vec3 halfVec = normalize(VPpli + vec3(0.0, 0.0, 1.0));\n");
|
||||
sb.append(" float NdotHV = max(dot(normal, halfVec), 0.0);\n");
|
||||
|
||||
// Specular power
|
||||
sb.append(" float spec = (NdotVP > 0.0 && u_MaterialShininess > 0.0) ? pow(NdotHV, u_MaterialShininess) : 0.0;\n");
|
||||
|
||||
// Accumulate — per-property source depends on color material mode
|
||||
if (key.colorMaterialEnabled()) {
|
||||
final String ambU = "u_Light" + li + "Ambient";
|
||||
final String difU = "u_Light" + li + "Diffuse";
|
||||
final String speU = "u_Light" + li + "Specular";
|
||||
// Ambient product: light.ambient * (vertex color or material)
|
||||
final String ambMat = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
|
||||
sb.append(" vec3 ambient = ").append(ambU).append(".rgb * ").append(ambMat).append(";\n");
|
||||
// Diffuse product
|
||||
final String difMat = key.cmReplacesDiffuse() ? "matColor.rgb" : "u_MaterialDiffuse.rgb";
|
||||
sb.append(" vec3 diffuse = ").append(difU).append(".rgb * ").append(difMat).append(";\n");
|
||||
// Specular product
|
||||
final String speMat = key.cmReplacesSpecular() ? "matColor.rgb" : "u_MaterialSpecular.rgb";
|
||||
sb.append(" vec3 specular = ").append(speU).append(".rgb * ").append(speMat).append(";\n");
|
||||
} else {
|
||||
sb.append(" vec3 ambient = u_LightProd").append(li).append("Ambient;\n");
|
||||
sb.append(" vec3 diffuse = u_LightProd").append(li).append("Diffuse;\n");
|
||||
sb.append(" vec3 specular = u_LightProd").append(li).append("Specular;\n");
|
||||
}
|
||||
|
||||
sb.append(" color0 += ambient + NdotVP * diffuse;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" specAccum += spec * specular;\n");
|
||||
} else {
|
||||
sb.append(" color0 += spec * specular;\n");
|
||||
}
|
||||
|
||||
sb.append(" }\n");
|
||||
}
|
||||
|
||||
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Color passthrough (no lighting)\n");
|
||||
if (key.hasVertexColor()) {
|
||||
sb.append(" v_Color = a_Color;\n");
|
||||
} else {
|
||||
sb.append(" v_Color = u_CurrentColor;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
|
||||
if (key.texGenEnabled()) {
|
||||
emitTexGenCoordGeneration(sb, key);
|
||||
} else if (key.textureEnabled() || key.hasVertexTexCoord()) {
|
||||
sb.append(" // Texture coordinates\n");
|
||||
if (key.hasVertexTexCoord()) {
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0);\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord0 = vec4(a_TexCoord0, 0.0, 1.0);\n");
|
||||
}
|
||||
} else {
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
|
||||
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
|
||||
* Coordinates without texgen use vertex attrib or default (0,0,0,1).
|
||||
*/
|
||||
private static void emitTexGenCoordGeneration(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // TexGen coordinate generation\n");
|
||||
sb.append(" vec4 texGenCoord = vec4(0.0, 0.0, 0.0, 1.0);\n");
|
||||
|
||||
emitTexGenComponent(sb, key.texGenModeS(), "s", "S", key);
|
||||
emitTexGenComponent(sb, key.texGenModeT(), "t", "T", key);
|
||||
emitTexGenComponent(sb, key.texGenModeR(), "r", "R", key);
|
||||
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
|
||||
|
||||
// Always apply texture matrix when texgen is active (forced on in VertexKey)
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * texGenCoord;\n");
|
||||
}
|
||||
|
||||
private static void emitTexGenComponent(StringBuilder sb, int mode, String swizzle, String coordName, VertexKey key) {
|
||||
switch (mode) {
|
||||
case VertexKey.TG_OBJ_LINEAR ->
|
||||
sb.append(" texGenCoord.").append(swizzle).append(" = dot(pos4, u_TexGenObjPlane").append(coordName).append(");\n");
|
||||
case VertexKey.TG_EYE_LINEAR ->
|
||||
sb.append(" texGenCoord.").append(swizzle).append(" = dot(eyePos, u_TexGenEyePlane").append(coordName).append(");\n");
|
||||
// TG_NONE: keep default (0 for s/t/r, 1 for q) — already set in texGenCoord init
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean texGenNeedsEyePos(VertexKey key) {
|
||||
return key.texGenModeS() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeT() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeR() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeQ() == VertexKey.TG_EYE_LINEAR;
|
||||
}
|
||||
|
||||
private static void emitFogDistance(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Fog distance\n");
|
||||
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
|
||||
final int fogDistMode = key.fogDistanceMode();
|
||||
switch (fogDistMode) {
|
||||
case 0 -> // FDM_EYE_RADIAL: Euclidean distance
|
||||
sb.append(" v_FogCoord = length(eyePos.xyz);\n");
|
||||
case 1 -> // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
|
||||
sb.append(" v_FogCoord = eyePos.z;\n");
|
||||
case 2 -> // FDM_EYE_PLANE_ABS: absolute Z
|
||||
sb.append(" v_FogCoord = abs(eyePos.z);\n");
|
||||
default ->
|
||||
sb.append(" v_FogCoord = abs(eyePos.z);\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitClipDistances(StringBuilder sb) {
|
||||
sb.append(" // Clip distances\n");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = dot(u_ClipPlane[").append(i).append("], eyePos);\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an accumulated draw call during display list compilation.
|
||||
* Matrix-as-Data Architecture: Stores untransformed vertices + captured matrix.
|
||||
* The matrix will be applied at runtime, not baked into vertices.
|
||||
*/
|
||||
public final class AccumulatedDraw {
|
||||
public final VertexFormat format;
|
||||
public final List<ByteBuffer> drawBuffers;
|
||||
public final int drawMode;
|
||||
public final int commandIndex; // Position in command list for state tracking
|
||||
public final int stateGeneration; // Position in command list for state tracking
|
||||
public RestoreData restoreData;
|
||||
|
||||
public AccumulatedDraw(DirectTessellator tessellator, int commandIndex, int stateGeneration, boolean copyLast) {
|
||||
this.format = tessellator.getVertexFormat();
|
||||
this.drawMode = tessellator.drawMode;
|
||||
this.drawBuffers = new ArrayList<>();
|
||||
this.drawBuffers.add(tessellator.allocateBufferCopy());
|
||||
this.commandIndex = commandIndex;
|
||||
this.stateGeneration = stateGeneration;
|
||||
|
||||
if (copyLast) {
|
||||
this.restoreData = new RestoreData(tessellator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void mergeDraw(DirectTessellator tessellator, boolean copyLast) {
|
||||
this.drawBuffers.add(tessellator.allocateBufferCopy());
|
||||
|
||||
if (copyLast) {
|
||||
this.restoreData = new RestoreData(tessellator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds last vertex attribute values for GL state restoration after VBO draw.
|
||||
* Used to sync GLSM cache with actual GL state after VBO rendering.
|
||||
*/
|
||||
public static final class RestoreData {
|
||||
public final int lastColor;
|
||||
public final int lastNormal;
|
||||
public final float lastTexCoordU;
|
||||
public final float lastTexCoordV;
|
||||
public final int flags;
|
||||
|
||||
public RestoreData(DirectTessellator tessellator) {
|
||||
this.lastColor = tessellator.getPackedColor();
|
||||
this.lastNormal = tessellator.getPackedNormal();
|
||||
this.lastTexCoordU = (float) tessellator.getLastTextureU();
|
||||
this.lastTexCoordV = (float) tessellator.getLastTextureV();
|
||||
this.flags = tessellator.getVertexFormat().getVertexFlags();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,784 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
|
||||
public final class CommandBuffer {
|
||||
private static final int DEFAULT_CAPACITY = 4096; // Guess at typical display list size
|
||||
|
||||
private ByteBuffer buffer;
|
||||
private long basePointer;
|
||||
private long writePointer;
|
||||
private final List<Object> complexObjects = new ArrayList<>();
|
||||
|
||||
public CommandBuffer() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
public CommandBuffer(int initialCapacity) {
|
||||
this.buffer = memAlloc(initialCapacity);
|
||||
this.basePointer = memAddress(buffer);
|
||||
this.writePointer = basePointer;
|
||||
}
|
||||
|
||||
// === Low-level write operations ===
|
||||
|
||||
private void writeInt(int value) {
|
||||
memPutInt(writePointer, value);
|
||||
writePointer += 4;
|
||||
}
|
||||
|
||||
private void writeLong(long value) {
|
||||
memPutLong(writePointer, value);
|
||||
writePointer += 8;
|
||||
}
|
||||
|
||||
private void writeFloat(float value) {
|
||||
memPutFloat(writePointer, value);
|
||||
writePointer += 4;
|
||||
}
|
||||
|
||||
private void writeDouble(double value) {
|
||||
memPutDouble(writePointer, value);
|
||||
writePointer += 8;
|
||||
}
|
||||
|
||||
private void ensureCapacity(int needed) {
|
||||
final int size = size();
|
||||
final int remaining = buffer.capacity() - size;
|
||||
if (remaining < needed) {
|
||||
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
|
||||
buffer = memRealloc(buffer, newCapacity);
|
||||
basePointer = memAddress(buffer);
|
||||
writePointer = basePointer + size;
|
||||
}
|
||||
}
|
||||
|
||||
// === Single int commands ===
|
||||
|
||||
public void writeEnable(int cap) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.ENABLE);
|
||||
writeInt(cap);
|
||||
}
|
||||
|
||||
public void writeDisable(int cap) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DISABLE);
|
||||
writeInt(cap);
|
||||
}
|
||||
|
||||
public void writeClear(int mask) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CLEAR);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeClearStencil(int s) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CLEAR_STENCIL);
|
||||
writeInt(s);
|
||||
}
|
||||
|
||||
public void writeCullFace(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CULL_FACE);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeDepthFunc(int func) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DEPTH_FUNC);
|
||||
writeInt(func);
|
||||
}
|
||||
|
||||
public void writeShadeModel(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.SHADE_MODEL);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeLogicOp(int opcode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.LOGIC_OP);
|
||||
writeInt(opcode);
|
||||
}
|
||||
|
||||
public void writeMatrixMode(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.MATRIX_MODE);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeActiveTexture(int texture) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.ACTIVE_TEXTURE);
|
||||
writeInt(texture);
|
||||
}
|
||||
|
||||
public void writeUseProgram(int program) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.USE_PROGRAM);
|
||||
writeInt(program);
|
||||
}
|
||||
|
||||
public void writePushAttrib(int mask) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.PUSH_ATTRIB);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writePopAttrib() {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.POP_ATTRIB);
|
||||
writeInt(0); // unused padding
|
||||
}
|
||||
|
||||
public void writeLoadIdentity() {
|
||||
ensureCapacity(4);
|
||||
writeInt(GLCommand.LOAD_IDENTITY);
|
||||
}
|
||||
|
||||
public void writePushMatrix() {
|
||||
ensureCapacity(4);
|
||||
writeInt(GLCommand.PUSH_MATRIX);
|
||||
}
|
||||
|
||||
public void writePopMatrix() {
|
||||
ensureCapacity(4);
|
||||
writeInt(GLCommand.POP_MATRIX);
|
||||
}
|
||||
|
||||
public void writeStencilMask(int mask) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.STENCIL_MASK);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeDepthMask(boolean flag) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DEPTH_MASK);
|
||||
writeInt(flag ? 1 : 0);
|
||||
}
|
||||
|
||||
public void writeFrontFace(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.FRONT_FACE);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
// === Two int commands ===
|
||||
|
||||
public void writeBindTexture(int target, int texture) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.BIND_TEXTURE);
|
||||
writeInt(target);
|
||||
writeInt(texture);
|
||||
}
|
||||
|
||||
public void writePolygonMode(int face, int mode) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.POLYGON_MODE);
|
||||
writeInt(face);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeColorMaterial(int face, int mode) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.COLOR_MATERIAL);
|
||||
writeInt(face);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeLineStipple(int factor, int pattern) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.LINE_STIPPLE);
|
||||
writeInt(factor);
|
||||
writeInt(pattern);
|
||||
}
|
||||
|
||||
public void writeStencilMaskSeparate(int face, int mask) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
|
||||
writeInt(face);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeFogi(int pname, int param) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.FOGI);
|
||||
writeInt(pname);
|
||||
writeInt(param);
|
||||
}
|
||||
|
||||
public void writeHint(int target, int mode) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.HINT);
|
||||
writeInt(target);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
// === Three int commands ===
|
||||
|
||||
public void writeStencilFunc(int func, int ref, int mask) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.STENCIL_FUNC);
|
||||
writeInt(func);
|
||||
writeInt(ref);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeStencilOp(int fail, int zfail, int zpass) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.STENCIL_OP);
|
||||
writeInt(fail);
|
||||
writeInt(zfail);
|
||||
writeInt(zpass);
|
||||
}
|
||||
|
||||
public void writeTexParameteri(int target, int pname, int param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.TEX_PARAMETERI);
|
||||
writeInt(target);
|
||||
writeInt(pname);
|
||||
writeInt(param);
|
||||
}
|
||||
|
||||
// === Four int commands ===
|
||||
|
||||
public void writeViewport(int x, int y, int width, int height) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.VIEWPORT);
|
||||
writeInt(x);
|
||||
writeInt(y);
|
||||
writeInt(width);
|
||||
writeInt(height);
|
||||
}
|
||||
|
||||
public void writeScissor(int x, int y, int width, int height) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.SCISSOR);
|
||||
writeInt(x);
|
||||
writeInt(y);
|
||||
writeInt(width);
|
||||
writeInt(height);
|
||||
}
|
||||
|
||||
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.BLEND_FUNC);
|
||||
writeInt(srcRgb);
|
||||
writeInt(dstRgb);
|
||||
writeInt(srcAlpha);
|
||||
writeInt(dstAlpha);
|
||||
}
|
||||
|
||||
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.COLOR_MASK);
|
||||
writeInt(red ? 1 : 0);
|
||||
writeInt(green ? 1 : 0);
|
||||
writeInt(blue ? 1 : 0);
|
||||
writeInt(alpha ? 1 : 0);
|
||||
}
|
||||
|
||||
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
|
||||
writeInt(face);
|
||||
writeInt(func);
|
||||
writeInt(ref);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.STENCIL_OP_SEPARATE);
|
||||
writeInt(face);
|
||||
writeInt(sfail);
|
||||
writeInt(dpfail);
|
||||
writeInt(dppass);
|
||||
}
|
||||
|
||||
// === Float commands ===
|
||||
|
||||
public void writePointSize(float size) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.POINT_SIZE);
|
||||
writeFloat(size);
|
||||
}
|
||||
|
||||
public void writeLineWidth(float width) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.LINE_WIDTH);
|
||||
writeFloat(width);
|
||||
}
|
||||
|
||||
public void writePolygonOffset(float factor, float units) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.POLYGON_OFFSET);
|
||||
writeFloat(factor);
|
||||
writeFloat(units);
|
||||
}
|
||||
|
||||
public void writeColor(float r, float g, float b, float a) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.COLOR);
|
||||
writeFloat(r);
|
||||
writeFloat(g);
|
||||
writeFloat(b);
|
||||
writeFloat(a);
|
||||
}
|
||||
|
||||
public void writeClearColor(float r, float g, float b, float a) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.CLEAR_COLOR);
|
||||
writeFloat(r);
|
||||
writeFloat(g);
|
||||
writeFloat(b);
|
||||
writeFloat(a);
|
||||
}
|
||||
|
||||
public void writeClearDepth(double depth) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.CLEAR_DEPTH);
|
||||
writeDouble(depth);
|
||||
}
|
||||
|
||||
public void writeBlendColor(float r, float g, float b, float a) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.BLEND_COLOR);
|
||||
writeFloat(r);
|
||||
writeFloat(g);
|
||||
writeFloat(b);
|
||||
writeFloat(a);
|
||||
}
|
||||
|
||||
// === Mixed int+float commands ===
|
||||
|
||||
public void writeAlphaFunc(int func, float ref) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.ALPHA_FUNC);
|
||||
writeInt(func);
|
||||
writeFloat(ref);
|
||||
}
|
||||
|
||||
public void writeFogf(int pname, float param) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.FOGF);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
public void writeLightf(int light, int pname, float param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.LIGHTF);
|
||||
writeInt(light);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
public void writeLightModelf(int pname, float param) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.LIGHT_MODELF);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
public void writeLighti(int light, int pname, int param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.LIGHTI);
|
||||
writeInt(light);
|
||||
writeInt(pname);
|
||||
writeInt(param);
|
||||
}
|
||||
|
||||
public void writeLightModeli(int pname, int param) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.LIGHT_MODELI);
|
||||
writeInt(pname);
|
||||
writeInt(param);
|
||||
}
|
||||
|
||||
public void writeMaterialf(int face, int pname, float param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.MATERIALF);
|
||||
writeInt(face);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
public void writeTexParameterf(int target, int pname, float param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.TEX_PARAMETERF);
|
||||
writeInt(target);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
// === Double commands ===
|
||||
|
||||
public void writeTranslate(double x, double y, double z) {
|
||||
ensureCapacity(28);
|
||||
writeInt(GLCommand.TRANSLATE);
|
||||
writeDouble(x);
|
||||
writeDouble(y);
|
||||
writeDouble(z);
|
||||
}
|
||||
|
||||
public void writeRotate(double angle, double x, double y, double z) {
|
||||
ensureCapacity(36);
|
||||
writeInt(GLCommand.ROTATE);
|
||||
writeDouble(angle);
|
||||
writeDouble(x);
|
||||
writeDouble(y);
|
||||
writeDouble(z);
|
||||
}
|
||||
|
||||
public void writeScale(double x, double y, double z) {
|
||||
ensureCapacity(28);
|
||||
writeInt(GLCommand.SCALE);
|
||||
writeDouble(x);
|
||||
writeDouble(y);
|
||||
writeDouble(z);
|
||||
}
|
||||
|
||||
public void writeOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
ensureCapacity(52);
|
||||
writeInt(GLCommand.ORTHO);
|
||||
writeDouble(left);
|
||||
writeDouble(right);
|
||||
writeDouble(bottom);
|
||||
writeDouble(top);
|
||||
writeDouble(zNear);
|
||||
writeDouble(zFar);
|
||||
}
|
||||
|
||||
public void writeFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
ensureCapacity(52);
|
||||
writeInt(GLCommand.FRUSTUM);
|
||||
writeDouble(left);
|
||||
writeDouble(right);
|
||||
writeDouble(bottom);
|
||||
writeDouble(top);
|
||||
writeDouble(zNear);
|
||||
writeDouble(zFar);
|
||||
}
|
||||
|
||||
// === Matrix commands ===
|
||||
|
||||
public void writeMultMatrix(Matrix4f matrix) {
|
||||
ensureCapacity(68);
|
||||
writeInt(GLCommand.MULT_MATRIX);
|
||||
writeMatrix4f(matrix);
|
||||
}
|
||||
|
||||
public void writeLoadMatrix(Matrix4f matrix) {
|
||||
ensureCapacity(68);
|
||||
writeInt(GLCommand.LOAD_MATRIX);
|
||||
writeMatrix4f(matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
|
||||
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
|
||||
*/
|
||||
private void writeMatrix4f(Matrix4f m) {
|
||||
// Column 0
|
||||
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
|
||||
// Column 1
|
||||
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
|
||||
// Column 2
|
||||
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
|
||||
// Column 3
|
||||
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
|
||||
}
|
||||
|
||||
// === Buffer commands (inline 4 floats) ===
|
||||
|
||||
public void writeFog(int pname, FloatBuffer params) {
|
||||
final int count = Math.min(params.remaining(), 4);
|
||||
ensureCapacity(12 + count * 4);
|
||||
writeInt(GLCommand.FOG);
|
||||
writeInt(pname);
|
||||
writeInt(count);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeFloat(params.get(pos + i));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeLight(int light, int pname, FloatBuffer params) {
|
||||
final int count = Math.min(params.remaining(), 4);
|
||||
ensureCapacity(16 + count * 4);
|
||||
writeInt(GLCommand.LIGHT);
|
||||
writeInt(light);
|
||||
writeInt(pname);
|
||||
writeInt(count);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeFloat(params.get(pos + i));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeLightModel(int pname, FloatBuffer params) {
|
||||
final int count = Math.min(params.remaining(), 4);
|
||||
ensureCapacity(12 + count * 4);
|
||||
writeInt(GLCommand.LIGHT_MODEL);
|
||||
writeInt(pname);
|
||||
writeInt(count);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeFloat(params.get(pos + i));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeMaterial(int face, int pname, FloatBuffer params) {
|
||||
final int count = Math.min(params.remaining(), 4);
|
||||
ensureCapacity(16 + count * 4);
|
||||
writeInt(GLCommand.MATERIAL);
|
||||
writeInt(face);
|
||||
writeInt(pname);
|
||||
writeInt(count);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeFloat(params.get(pos + i));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeClipPlane(int plane, double a, double b, double c, double d) {
|
||||
ensureCapacity(40);
|
||||
writeInt(GLCommand.CLIP_PLANE);
|
||||
writeInt(plane);
|
||||
writeDouble(a);
|
||||
writeDouble(b);
|
||||
writeDouble(c);
|
||||
writeDouble(d);
|
||||
}
|
||||
|
||||
// === Draw commands ===
|
||||
|
||||
public void writeDrawRange(int vboIndex) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DRAW_RANGE);
|
||||
writeInt(vboIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a DRAW_RANGE_RESTORE command that draws a VBO range and restores GL current state after.
|
||||
* Used for immediate mode VBOs to restore GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS.
|
||||
*
|
||||
* @param vboIndex Index into ownedVbos array
|
||||
*/
|
||||
public void writeDrawRangeRestore(
|
||||
int vboIndex, AccumulatedDraw.RestoreData restoreData
|
||||
) {
|
||||
ensureCapacity(28);
|
||||
writeInt(GLCommand.DRAW_RANGE_RESTORE);
|
||||
writeInt(vboIndex);
|
||||
writeInt(restoreData.flags);
|
||||
// Last color (4 bytes)
|
||||
writeInt(restoreData.lastColor);
|
||||
// Last normal (4 bytes)
|
||||
writeInt(restoreData.lastNormal);
|
||||
// Last texcoord (2 floats = 8 bytes)
|
||||
writeFloat(restoreData.lastTexCoordU);
|
||||
writeFloat(restoreData.lastTexCoordV);
|
||||
}
|
||||
|
||||
public void writeCallList(int listId) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CALL_LIST);
|
||||
writeInt(listId);
|
||||
}
|
||||
|
||||
public void writeDrawArrays(int mode, int start, int count) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.DRAW_ARRAYS);
|
||||
writeInt(mode);
|
||||
writeInt(start);
|
||||
writeInt(count);
|
||||
}
|
||||
|
||||
public void writeDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
|
||||
ensureCapacity(24);
|
||||
writeInt(GLCommand.DRAW_ELEMENTS);
|
||||
writeInt(mode);
|
||||
writeInt(indices_count);
|
||||
writeInt(type);
|
||||
writeLong(indices_buffer_offset);
|
||||
}
|
||||
|
||||
public void writeBindVBO(int vbo) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.BIND_VBO);
|
||||
writeInt(vbo);
|
||||
}
|
||||
|
||||
public void writeBindVAO(int vao) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.BIND_VAO);
|
||||
writeInt(vao);
|
||||
}
|
||||
|
||||
public void writeDrawBuffer(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DRAW_BUFFER);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
private static final int MAX_DRAW_BUFFERS = 8;
|
||||
// cmd (4) + count (4) + buffers (4 * MAX_DRAW_BUFFERS)
|
||||
private static final int DRAW_BUFFERS_SIZE = 4 + 4 + 4 * MAX_DRAW_BUFFERS;
|
||||
|
||||
public void writeDrawBuffers(int count, int buffer) {
|
||||
ensureCapacity(DRAW_BUFFERS_SIZE);
|
||||
writeInt(GLCommand.DRAW_BUFFERS);
|
||||
writeInt(count);
|
||||
writeInt(buffer);
|
||||
for (int i = 1; i < MAX_DRAW_BUFFERS; i++) {
|
||||
writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeDrawBuffers(int count, IntBuffer bufs) {
|
||||
ensureCapacity(DRAW_BUFFERS_SIZE);
|
||||
writeInt(GLCommand.DRAW_BUFFERS);
|
||||
writeInt(count);
|
||||
final int pos = bufs.position();
|
||||
for (int i = 0; i < count && i < MAX_DRAW_BUFFERS; i++) {
|
||||
writeInt(bufs.get(pos + i));
|
||||
}
|
||||
for (int i = count; i < MAX_DRAW_BUFFERS; i++) {
|
||||
writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// === Complex object reference ===
|
||||
|
||||
public void writeComplexRef(DisplayListCommand cmd) {
|
||||
ensureCapacity(8);
|
||||
final int index = complexObjects.size();
|
||||
complexObjects.add(cmd);
|
||||
writeInt(GLCommand.COMPLEX_REF);
|
||||
writeInt(index);
|
||||
}
|
||||
|
||||
// === Reading methods for optimization pass ===
|
||||
|
||||
private long readPointer = 0;
|
||||
|
||||
/**
|
||||
* Reset read position to the beginning of the buffer.
|
||||
*/
|
||||
public void resetRead() {
|
||||
readPointer = basePointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's more data to read.
|
||||
*/
|
||||
public boolean hasRemaining() {
|
||||
return readPointer < writePointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an int from the current position.
|
||||
*/
|
||||
public int readInt() {
|
||||
final int value = memGetInt(readPointer);
|
||||
readPointer += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
public long readLong() {
|
||||
final long value = memGetLong(readPointer);
|
||||
readPointer += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a float from the current position.
|
||||
*/
|
||||
public float readFloat() {
|
||||
final float value = memGetFloat(readPointer);
|
||||
readPointer += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a double from the current position.
|
||||
*/
|
||||
public double readDouble() {
|
||||
final double value = memGetDouble(readPointer);
|
||||
readPointer += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Matrix4f from the current position (16 floats in column-major order).
|
||||
*/
|
||||
public Matrix4f readMatrix4f(Matrix4f dest) {
|
||||
dest.m00(readFloat()); dest.m01(readFloat()); dest.m02(readFloat()); dest.m03(readFloat());
|
||||
dest.m10(readFloat()); dest.m11(readFloat()); dest.m12(readFloat()); dest.m13(readFloat());
|
||||
dest.m20(readFloat()); dest.m21(readFloat()); dest.m22(readFloat()); dest.m23(readFloat());
|
||||
dest.m30(readFloat()); dest.m31(readFloat()); dest.m32(readFloat()); dest.m33(readFloat());
|
||||
return dest;
|
||||
}
|
||||
|
||||
// === Lifecycle ===
|
||||
|
||||
public int size() {
|
||||
return (int) (writePointer - basePointer);
|
||||
}
|
||||
|
||||
public long address() {
|
||||
return basePointer;
|
||||
}
|
||||
|
||||
public ByteBuffer toBuffer() {
|
||||
buffer.limit(size());
|
||||
buffer.position(0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public Object[] getComplexObjects() {
|
||||
return complexObjects.toArray();
|
||||
}
|
||||
|
||||
public Object getComplexObject(int index) {
|
||||
return complexObjects.get(index);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return size() == 0 && complexObjects.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the buffer for reuse. Does NOT free memory.
|
||||
*/
|
||||
public void clear() {
|
||||
writePointer = basePointer;
|
||||
complexObjects.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the underlying buffer. Must be called when done.
|
||||
*/
|
||||
public void free() {
|
||||
if (buffer != null) {
|
||||
memFree(buffer);
|
||||
buffer = null;
|
||||
basePointer = 0;
|
||||
writePointer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
|
||||
/**
|
||||
* Executes commands from a CommandBuffer
|
||||
*/
|
||||
public final class CommandBufferExecutor {
|
||||
private CommandBufferExecutor() {}
|
||||
|
||||
// Static reusable buffer for matrix ops (render thread only, LWJGL2/3 compatible)
|
||||
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
|
||||
// Static reusable buffer for fog/light/material params
|
||||
private static final FloatBuffer PARAMS_BUFFER = BufferUtils.createFloatBuffer(4);
|
||||
// Static reusable buffer for clip plane equation
|
||||
private static final DoubleBuffer PARAMS_DOUBLE_BUFFER = BufferUtils.createDoubleBuffer(4);
|
||||
// Static reusable buffer for draw buffers
|
||||
private static final int MAX_DRAW_BUFFERS = 8;
|
||||
private static final IntBuffer DRAW_BUFFERS_BUFFER = BufferUtils.createIntBuffer(MAX_DRAW_BUFFERS);
|
||||
|
||||
/**
|
||||
* Execute all commands in the buffer.
|
||||
*
|
||||
* @param buffer The command buffer (position should be at 0, limit at size)
|
||||
* @param complexObjects Array of complex objects (TexImage2DCmd, etc.)
|
||||
* @param ownedVbos Array of VBOs owned by the display list (indexed by DrawRange commands)
|
||||
*/
|
||||
public static void execute(ByteBuffer buffer, Object[] complexObjects, DisplayListVBO ownedVbos) {
|
||||
long ptr = memAddress(buffer);
|
||||
final long end = ptr + buffer.limit();
|
||||
|
||||
while (ptr < end) {
|
||||
final int cmd = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
|
||||
switch (cmd) {
|
||||
// === Single int commands ===
|
||||
case GLCommand.ENABLE -> {
|
||||
GLStateManager.glEnable(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.DISABLE -> {
|
||||
GLStateManager.glDisable(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.CLEAR -> {
|
||||
GLStateManager.glClear(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.CLEAR_STENCIL -> {
|
||||
GLStateManager.glClearStencil(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.CULL_FACE -> {
|
||||
GLStateManager.glCullFace(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.DEPTH_FUNC -> {
|
||||
GLStateManager.glDepthFunc(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.SHADE_MODEL -> {
|
||||
GLStateManager.glShadeModel(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.LOGIC_OP -> {
|
||||
GLStateManager.glLogicOp(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.MATRIX_MODE -> {
|
||||
GLStateManager.glMatrixMode(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.ACTIVE_TEXTURE -> {
|
||||
GLStateManager.glActiveTexture(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.USE_PROGRAM -> {
|
||||
GLStateManager.glUseProgram(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.PUSH_ATTRIB -> {
|
||||
GLStateManager.glPushAttrib(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.POP_ATTRIB -> {
|
||||
GLStateManager.glPopAttrib();
|
||||
ptr += 4; // Skip unused padding
|
||||
}
|
||||
case GLCommand.LOAD_IDENTITY -> {
|
||||
GLStateManager.glLoadIdentity();
|
||||
}
|
||||
case GLCommand.PUSH_MATRIX -> {
|
||||
GLStateManager.glPushMatrix();
|
||||
}
|
||||
case GLCommand.POP_MATRIX -> {
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
case GLCommand.STENCIL_MASK -> {
|
||||
GLStateManager.glStencilMask(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.DEPTH_MASK -> {
|
||||
GLStateManager.glDepthMask(memGetInt(ptr) != 0);
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.FRONT_FACE -> {
|
||||
GLStateManager.glFrontFace(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
// === Two int commands ===
|
||||
case GLCommand.BIND_TEXTURE -> {
|
||||
final int target = memGetInt(ptr);
|
||||
final int texture = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
}
|
||||
case GLCommand.POLYGON_MODE -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int mode = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glPolygonMode(face, mode);
|
||||
}
|
||||
case GLCommand.COLOR_MATERIAL -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int mode = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glColorMaterial(face, mode);
|
||||
}
|
||||
case GLCommand.LINE_STIPPLE -> {
|
||||
final int factor = memGetInt(ptr);
|
||||
final int pattern = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glLineStipple(factor, (short) pattern);
|
||||
}
|
||||
case GLCommand.STENCIL_MASK_SEPARATE -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int mask = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glStencilMaskSeparate(face, mask);
|
||||
}
|
||||
case GLCommand.FOGI -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final int param = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glFogi(pname, param);
|
||||
}
|
||||
case GLCommand.HINT -> {
|
||||
final int target = memGetInt(ptr);
|
||||
final int mode = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glHint(target, mode);
|
||||
}
|
||||
|
||||
// === Three int commands ===
|
||||
case GLCommand.STENCIL_FUNC -> {
|
||||
final int func = memGetInt(ptr);
|
||||
final int ref = memGetInt(ptr + 4);
|
||||
final int mask = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glStencilFunc(func, ref, mask);
|
||||
}
|
||||
case GLCommand.STENCIL_OP -> {
|
||||
final int fail = memGetInt(ptr);
|
||||
final int zfail = memGetInt(ptr + 4);
|
||||
final int zpass = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glStencilOp(fail, zfail, zpass);
|
||||
}
|
||||
case GLCommand.TEX_PARAMETERI -> {
|
||||
final int target = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final int param = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glTexParameteri(target, pname, param);
|
||||
}
|
||||
|
||||
// === Four int commands ===
|
||||
case GLCommand.VIEWPORT -> {
|
||||
final int x = memGetInt(ptr);
|
||||
final int y = memGetInt(ptr + 4);
|
||||
final int width = memGetInt(ptr + 8);
|
||||
final int height = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
case GLCommand.SCISSOR -> {
|
||||
final int x = memGetInt(ptr);
|
||||
final int y = memGetInt(ptr + 4);
|
||||
final int width = memGetInt(ptr + 8);
|
||||
final int height = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glScissor(x, y, width, height);
|
||||
}
|
||||
case GLCommand.BLEND_FUNC -> {
|
||||
final int srcRgb = memGetInt(ptr);
|
||||
final int dstRgb = memGetInt(ptr + 4);
|
||||
final int srcAlpha = memGetInt(ptr + 8);
|
||||
final int dstAlpha = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
if (srcRgb == srcAlpha && dstRgb == dstAlpha) {
|
||||
GLStateManager.glBlendFunc(srcRgb, dstRgb);
|
||||
} else {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
}
|
||||
}
|
||||
case GLCommand.COLOR_MASK -> {
|
||||
final boolean r = memGetInt(ptr) != 0;
|
||||
final boolean g = memGetInt(ptr + 4) != 0;
|
||||
final boolean b = memGetInt(ptr + 8) != 0;
|
||||
final boolean a = memGetInt(ptr + 12) != 0;
|
||||
ptr += 16;
|
||||
GLStateManager.glColorMask(r, g, b, a);
|
||||
}
|
||||
case GLCommand.STENCIL_FUNC_SEPARATE -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int func = memGetInt(ptr + 4);
|
||||
final int ref = memGetInt(ptr + 8);
|
||||
final int mask = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glStencilFuncSeparate(face, func, ref, mask);
|
||||
}
|
||||
case GLCommand.STENCIL_OP_SEPARATE -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int sfail = memGetInt(ptr + 4);
|
||||
final int dpfail = memGetInt(ptr + 8);
|
||||
final int dppass = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glStencilOpSeparate(face, sfail, dpfail, dppass);
|
||||
}
|
||||
|
||||
// === Float commands ===
|
||||
case GLCommand.POINT_SIZE -> {
|
||||
GLStateManager.glPointSize(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.LINE_WIDTH -> {
|
||||
GLStateManager.glLineWidth(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.POLYGON_OFFSET -> {
|
||||
final float factor = memGetFloat(ptr);
|
||||
final float units = memGetFloat(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glPolygonOffset(factor, units);
|
||||
}
|
||||
case GLCommand.COLOR -> {
|
||||
final float r = memGetFloat(ptr);
|
||||
final float g = memGetFloat(ptr + 4);
|
||||
final float b = memGetFloat(ptr + 8);
|
||||
final float a = memGetFloat(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glColor4f(r, g, b, a);
|
||||
}
|
||||
case GLCommand.CLEAR_COLOR -> {
|
||||
final float r = memGetFloat(ptr);
|
||||
final float g = memGetFloat(ptr + 4);
|
||||
final float b = memGetFloat(ptr + 8);
|
||||
final float a = memGetFloat(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glClearColor(r, g, b, a);
|
||||
}
|
||||
case GLCommand.CLEAR_DEPTH -> {
|
||||
final double depth = memGetDouble(ptr);
|
||||
ptr += 8;
|
||||
GLStateManager.glClearDepth(depth);
|
||||
}
|
||||
case GLCommand.BLEND_COLOR -> {
|
||||
final float r = memGetFloat(ptr);
|
||||
final float g = memGetFloat(ptr + 4);
|
||||
final float b = memGetFloat(ptr + 8);
|
||||
final float a = memGetFloat(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glBlendColor(r, g, b, a);
|
||||
}
|
||||
|
||||
// === Mixed int+float commands ===
|
||||
case GLCommand.ALPHA_FUNC -> {
|
||||
final int func = memGetInt(ptr);
|
||||
final float ref = memGetFloat(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glAlphaFunc(func, ref);
|
||||
}
|
||||
case GLCommand.FOGF -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final float param = memGetFloat(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glFogf(pname, param);
|
||||
}
|
||||
case GLCommand.LIGHTF -> {
|
||||
final int light = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final float param = memGetFloat(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glLightf(light, pname, param);
|
||||
}
|
||||
case GLCommand.LIGHT_MODELF -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final float param = memGetFloat(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glLightModelf(pname, param);
|
||||
}
|
||||
case GLCommand.LIGHTI -> {
|
||||
final int light = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final int param = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glLighti(light, pname, param);
|
||||
}
|
||||
case GLCommand.LIGHT_MODELI -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final int param = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glLightModeli(pname, param);
|
||||
}
|
||||
case GLCommand.MATERIALF -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final float param = memGetFloat(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glMaterialf(face, pname, param);
|
||||
}
|
||||
case GLCommand.TEX_PARAMETERF -> {
|
||||
final int target = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final float param = memGetFloat(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glTexParameterf(target, pname, param);
|
||||
}
|
||||
|
||||
// === Double commands ===
|
||||
case GLCommand.TRANSLATE -> {
|
||||
final double x = memGetDouble(ptr);
|
||||
final double y = memGetDouble(ptr + 8);
|
||||
final double z = memGetDouble(ptr + 16);
|
||||
ptr += 24;
|
||||
GLStateManager.glTranslated(x, y, z);
|
||||
}
|
||||
case GLCommand.ROTATE -> {
|
||||
final double angle = memGetDouble(ptr);
|
||||
final double x = memGetDouble(ptr + 8);
|
||||
final double y = memGetDouble(ptr + 16);
|
||||
final double z = memGetDouble(ptr + 24);
|
||||
ptr += 32;
|
||||
GLStateManager.glRotated(angle, x, y, z);
|
||||
}
|
||||
case GLCommand.SCALE -> {
|
||||
final double x = memGetDouble(ptr);
|
||||
final double y = memGetDouble(ptr + 8);
|
||||
final double z = memGetDouble(ptr + 16);
|
||||
ptr += 24;
|
||||
GLStateManager.glScaled(x, y, z);
|
||||
}
|
||||
case GLCommand.ORTHO -> {
|
||||
final double left = memGetDouble(ptr);
|
||||
final double right = memGetDouble(ptr + 8);
|
||||
final double bottom = memGetDouble(ptr + 16);
|
||||
final double top = memGetDouble(ptr + 24);
|
||||
final double zNear = memGetDouble(ptr + 32);
|
||||
final double zFar = memGetDouble(ptr + 40);
|
||||
ptr += 48;
|
||||
GLStateManager.glOrtho(left, right, bottom, top, zNear, zFar);
|
||||
}
|
||||
case GLCommand.FRUSTUM -> {
|
||||
final double left = memGetDouble(ptr);
|
||||
final double right = memGetDouble(ptr + 8);
|
||||
final double bottom = memGetDouble(ptr + 16);
|
||||
final double top = memGetDouble(ptr + 24);
|
||||
final double zNear = memGetDouble(ptr + 32);
|
||||
final double zFar = memGetDouble(ptr + 40);
|
||||
ptr += 48;
|
||||
GLStateManager.glFrustum(left, right, bottom, top, zNear, zFar);
|
||||
}
|
||||
|
||||
// === Matrix commands ===
|
||||
case GLCommand.MULT_MATRIX -> {
|
||||
// Mode-agnostic: just multiply current matrix
|
||||
MATRIX_BUFFER.clear();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
MATRIX_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
MATRIX_BUFFER.flip();
|
||||
GLStateManager.glMultMatrix(MATRIX_BUFFER);
|
||||
}
|
||||
case GLCommand.LOAD_MATRIX -> {
|
||||
MATRIX_BUFFER.clear();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
MATRIX_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
MATRIX_BUFFER.flip();
|
||||
GLStateManager.glLoadMatrix(MATRIX_BUFFER);
|
||||
}
|
||||
|
||||
// === Buffer commands ===
|
||||
case GLCommand.FOG -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final int count = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
PARAMS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PARAMS_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
PARAMS_BUFFER.flip();
|
||||
GLStateManager.glFog(pname, PARAMS_BUFFER);
|
||||
}
|
||||
case GLCommand.LIGHT -> {
|
||||
final int light = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final int count = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
PARAMS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PARAMS_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
PARAMS_BUFFER.flip();
|
||||
GLStateManager.glLight(light, pname, PARAMS_BUFFER);
|
||||
}
|
||||
case GLCommand.LIGHT_MODEL -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final int count = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
PARAMS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PARAMS_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
PARAMS_BUFFER.flip();
|
||||
GLStateManager.glLightModel(pname, PARAMS_BUFFER);
|
||||
}
|
||||
case GLCommand.MATERIAL -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final int count = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
PARAMS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PARAMS_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
PARAMS_BUFFER.flip();
|
||||
GLStateManager.glMaterial(face, pname, PARAMS_BUFFER);
|
||||
}
|
||||
|
||||
case GLCommand.CLIP_PLANE -> {
|
||||
final int plane = memGetInt(ptr);
|
||||
final double a = memGetDouble(ptr + 4);
|
||||
final double b = memGetDouble(ptr + 12);
|
||||
final double c = memGetDouble(ptr + 20);
|
||||
final double d = memGetDouble(ptr + 28);
|
||||
ptr += 36;
|
||||
PARAMS_DOUBLE_BUFFER.clear();
|
||||
PARAMS_DOUBLE_BUFFER.put(a).put(b).put(c).put(d);
|
||||
PARAMS_DOUBLE_BUFFER.flip();
|
||||
GLStateManager.glClipPlane(plane, PARAMS_DOUBLE_BUFFER);
|
||||
}
|
||||
|
||||
// === Draw commands ===
|
||||
case GLCommand.DRAW_RANGE -> {
|
||||
final int vboIndex = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
ownedVbos.render(vboIndex);
|
||||
}
|
||||
case GLCommand.DRAW_RANGE_RESTORE -> {
|
||||
// Draw VBO range then restore GL current state from last vertex attributes
|
||||
final int vboIndex = memGetInt(ptr);
|
||||
final int flags = memGetInt(ptr + 4);
|
||||
|
||||
// Draw the VBO
|
||||
ownedVbos.render(vboIndex);
|
||||
|
||||
// Restore attributes based on flags
|
||||
if ((flags & VertexFlags.COLOR_BIT) != 0) {
|
||||
final int color = memGetInt(ptr + 8);
|
||||
byte a = (byte) ((color >> 24) & 0xFF);
|
||||
byte r = (byte) ((color >> 16) & 0xFF);
|
||||
byte g = (byte) ((color >> 8) & 0xFF);
|
||||
byte b = (byte) ((color & 0xFF));
|
||||
GLStateManager.glColor4ub(r, g, b, a);
|
||||
}
|
||||
if ((flags & VertexFlags.NORMAL_BIT) != 0) {
|
||||
final int normal = memGetInt(ptr + 12);
|
||||
float nx = (byte)(normal) / 127.0f;
|
||||
float ny = (byte)(normal >> 8) / 127.0f;
|
||||
float nz = (byte)(normal >> 16) / 127.0f;
|
||||
GLStateManager.glNormal3f(nx, ny, nz);
|
||||
}
|
||||
if ((flags & VertexFlags.TEXTURE_BIT) != 0) {
|
||||
final float s = memGetFloat(ptr + 16);
|
||||
final float t = memGetFloat(ptr + 20);
|
||||
GLStateManager.glTexCoord2f(s, t);
|
||||
}
|
||||
ptr += 24; // Skip full command size (28 - 4 for cmd already read)
|
||||
}
|
||||
case GLCommand.CALL_LIST -> {
|
||||
final int listId = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
DisplayListManager.glCallList(listId);
|
||||
}
|
||||
case GLCommand.DRAW_BUFFER -> {
|
||||
GLStateManager.glDrawBuffer(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.DRAW_BUFFERS -> {
|
||||
final int count = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
DRAW_BUFFERS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
DRAW_BUFFERS_BUFFER.put(memGetInt(ptr + i * 4));
|
||||
}
|
||||
DRAW_BUFFERS_BUFFER.flip();
|
||||
ptr += 4 * MAX_DRAW_BUFFERS;
|
||||
GLStateManager.glDrawBuffers(DRAW_BUFFERS_BUFFER);
|
||||
}
|
||||
case GLCommand.DRAW_ARRAYS -> {
|
||||
GLStateManager.glDrawArrays(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8));
|
||||
ptr += 12;
|
||||
}
|
||||
case GLCommand.DRAW_ELEMENTS -> {
|
||||
GLStateManager.glDrawElements(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8), memGetLong(ptr + 12));
|
||||
ptr += 20;
|
||||
}
|
||||
case GLCommand.BIND_VBO -> {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.BIND_VAO -> {
|
||||
GLStateManager.glBindVertexArray(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
// === Complex object reference ===
|
||||
case GLCommand.COMPLEX_REF -> {
|
||||
final int index = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
((DisplayListCommand) complexObjects[index]).execute();
|
||||
}
|
||||
|
||||
default -> throw new IllegalStateException("Unknown command opcode: " + cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
public final class CommandBufferProcessor {
|
||||
private static final Logger LOGGER = LogManager.getLogger("CommandBufferProcessor");
|
||||
private static final Matrix4f tempMatrix = new Matrix4f();
|
||||
|
||||
private CommandBufferProcessor() {}
|
||||
|
||||
/**
|
||||
* Copy a complete command (opcode already read) from raw to final buffer.
|
||||
*/
|
||||
public static void copyCommand(CommandBuffer raw, CommandBuffer out) {
|
||||
final int opcode = raw.readInt();
|
||||
copyCommandData(opcode, raw, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy command data based on opcode (opcode already consumed from raw).
|
||||
*/
|
||||
public static void copyCommandData(int opcode, CommandBuffer raw, CommandBuffer out) {
|
||||
switch (opcode) {
|
||||
// Single int commands
|
||||
case GLCommand.ENABLE -> out.writeEnable(raw.readInt());
|
||||
case GLCommand.DISABLE -> out.writeDisable(raw.readInt());
|
||||
case GLCommand.CLEAR -> out.writeClear(raw.readInt());
|
||||
case GLCommand.CLEAR_STENCIL -> out.writeClearStencil(raw.readInt());
|
||||
case GLCommand.CULL_FACE -> out.writeCullFace(raw.readInt());
|
||||
case GLCommand.DEPTH_FUNC -> out.writeDepthFunc(raw.readInt());
|
||||
case GLCommand.SHADE_MODEL -> out.writeShadeModel(raw.readInt());
|
||||
case GLCommand.LOGIC_OP -> out.writeLogicOp(raw.readInt());
|
||||
case GLCommand.MATRIX_MODE -> out.writeMatrixMode(raw.readInt());
|
||||
case GLCommand.ACTIVE_TEXTURE -> out.writeActiveTexture(raw.readInt());
|
||||
case GLCommand.USE_PROGRAM -> out.writeUseProgram(raw.readInt());
|
||||
case GLCommand.PUSH_ATTRIB -> out.writePushAttrib(raw.readInt());
|
||||
case GLCommand.POP_ATTRIB -> { raw.readInt(); out.writePopAttrib(); }
|
||||
case GLCommand.LOAD_IDENTITY -> out.writeLoadIdentity();
|
||||
case GLCommand.PUSH_MATRIX -> out.writePushMatrix();
|
||||
case GLCommand.POP_MATRIX -> out.writePopMatrix();
|
||||
case GLCommand.STENCIL_MASK -> out.writeStencilMask(raw.readInt());
|
||||
case GLCommand.DEPTH_MASK -> out.writeDepthMask(raw.readInt() != 0);
|
||||
case GLCommand.FRONT_FACE -> out.writeFrontFace(raw.readInt());
|
||||
|
||||
// Two int commands
|
||||
case GLCommand.BIND_TEXTURE -> out.writeBindTexture(raw.readInt(), raw.readInt());
|
||||
case GLCommand.POLYGON_MODE -> out.writePolygonMode(raw.readInt(), raw.readInt());
|
||||
case GLCommand.COLOR_MATERIAL -> out.writeColorMaterial(raw.readInt(), raw.readInt());
|
||||
case GLCommand.LINE_STIPPLE -> out.writeLineStipple(raw.readInt(), raw.readInt());
|
||||
case GLCommand.STENCIL_MASK_SEPARATE -> out.writeStencilMaskSeparate(raw.readInt(), raw.readInt());
|
||||
case GLCommand.FOGI -> out.writeFogi(raw.readInt(), raw.readInt());
|
||||
case GLCommand.HINT -> out.writeHint(raw.readInt(), raw.readInt());
|
||||
|
||||
// Three int commands
|
||||
case GLCommand.STENCIL_FUNC -> out.writeStencilFunc(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.STENCIL_OP -> out.writeStencilOp(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.TEX_PARAMETERI -> out.writeTexParameteri(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
|
||||
// Four int commands
|
||||
case GLCommand.VIEWPORT -> out.writeViewport(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.SCISSOR -> out.writeScissor(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.BLEND_FUNC -> out.writeBlendFunc(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.COLOR_MASK -> out.writeColorMask(raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0);
|
||||
case GLCommand.STENCIL_FUNC_SEPARATE -> out.writeStencilFuncSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.STENCIL_OP_SEPARATE -> out.writeStencilOpSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
|
||||
// Float commands
|
||||
case GLCommand.POINT_SIZE -> out.writePointSize(raw.readFloat());
|
||||
case GLCommand.LINE_WIDTH -> out.writeLineWidth(raw.readFloat());
|
||||
case GLCommand.POLYGON_OFFSET -> out.writePolygonOffset(raw.readFloat(), raw.readFloat());
|
||||
case GLCommand.COLOR -> out.writeColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
|
||||
case GLCommand.CLEAR_COLOR -> out.writeClearColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
|
||||
|
||||
// Single double commands
|
||||
case GLCommand.CLEAR_DEPTH -> out.writeClearDepth(raw.readDouble());
|
||||
|
||||
// Four float commands
|
||||
case GLCommand.BLEND_COLOR -> out.writeBlendColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
|
||||
|
||||
// Mixed int+float commands
|
||||
case GLCommand.ALPHA_FUNC -> out.writeAlphaFunc(raw.readInt(), raw.readFloat());
|
||||
case GLCommand.FOGF -> out.writeFogf(raw.readInt(), raw.readFloat());
|
||||
case GLCommand.LIGHTF -> out.writeLightf(raw.readInt(), raw.readInt(), raw.readFloat());
|
||||
case GLCommand.LIGHT_MODELF -> out.writeLightModelf(raw.readInt(), raw.readFloat());
|
||||
case GLCommand.LIGHTI -> out.writeLighti(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.LIGHT_MODELI -> out.writeLightModeli(raw.readInt(), raw.readInt());
|
||||
case GLCommand.MATERIALF -> out.writeMaterialf(raw.readInt(), raw.readInt(), raw.readFloat());
|
||||
case GLCommand.TEX_PARAMETERF -> out.writeTexParameterf(raw.readInt(), raw.readInt(), raw.readFloat());
|
||||
|
||||
// Double commands
|
||||
case GLCommand.TRANSLATE -> out.writeTranslate(raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
case GLCommand.ROTATE -> out.writeRotate(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
case GLCommand.SCALE -> out.writeScale(raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
case GLCommand.ORTHO -> out.writeOrtho(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
case GLCommand.FRUSTUM -> out.writeFrustum(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
|
||||
// Matrix commands
|
||||
case GLCommand.MULT_MATRIX -> { out.writeMultMatrix(raw.readMatrix4f(tempMatrix)); }
|
||||
case GLCommand.LOAD_MATRIX -> { out.writeLoadMatrix(raw.readMatrix4f(tempMatrix)); }
|
||||
|
||||
// FloatBuffer commands (inline 4 floats max)
|
||||
case GLCommand.FOG -> {
|
||||
final int pname = raw.readInt();
|
||||
final int count = raw.readInt();
|
||||
final FloatBuffer fb = FloatBuffer.allocate(count);
|
||||
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
|
||||
fb.flip();
|
||||
out.writeFog(pname, fb);
|
||||
}
|
||||
case GLCommand.LIGHT -> {
|
||||
final int light = raw.readInt();
|
||||
final int pname = raw.readInt();
|
||||
final int count = raw.readInt();
|
||||
final FloatBuffer fb = FloatBuffer.allocate(count);
|
||||
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
|
||||
fb.flip();
|
||||
out.writeLight(light, pname, fb);
|
||||
}
|
||||
case GLCommand.LIGHT_MODEL -> {
|
||||
final int pname = raw.readInt();
|
||||
final int count = raw.readInt();
|
||||
final FloatBuffer fb = FloatBuffer.allocate(count);
|
||||
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
|
||||
fb.flip();
|
||||
out.writeLightModel(pname, fb);
|
||||
}
|
||||
case GLCommand.MATERIAL -> {
|
||||
final int face = raw.readInt();
|
||||
final int pname = raw.readInt();
|
||||
final int count = raw.readInt();
|
||||
final FloatBuffer fb = FloatBuffer.allocate(count);
|
||||
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
|
||||
fb.flip();
|
||||
out.writeMaterial(face, pname, fb);
|
||||
}
|
||||
|
||||
// Clip plane command (4 doubles)
|
||||
case GLCommand.CLIP_PLANE -> {
|
||||
final int plane = raw.readInt();
|
||||
final double a = raw.readDouble();
|
||||
final double b = raw.readDouble();
|
||||
final double c = raw.readDouble();
|
||||
final double d = raw.readDouble();
|
||||
out.writeClipPlane(plane, a, b, c, d);
|
||||
}
|
||||
|
||||
// Draw/call commands
|
||||
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt());
|
||||
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
|
||||
case GLCommand.DRAW_ARRAYS -> out.writeDrawArrays(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.DRAW_ELEMENTS -> out.writeDrawElements(raw.readInt(), raw.readInt(), raw.readInt(), raw.readLong());
|
||||
case GLCommand.BIND_VBO -> out.writeBindVBO(raw.readInt());
|
||||
case GLCommand.BIND_VAO -> out.writeBindVAO(raw.readInt());
|
||||
|
||||
// Complex object reference - transfer the object to output buffer
|
||||
case GLCommand.COMPLEX_REF -> {
|
||||
final int index = raw.readInt();
|
||||
final Object obj = raw.getComplexObject(index);
|
||||
out.writeComplexRef((DisplayListCommand) obj);
|
||||
}
|
||||
|
||||
default -> LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* Records GL commands to a CommandBuffer during display list compilation.
|
||||
*/
|
||||
public final class CommandRecorder {
|
||||
private final CommandBuffer buffer;
|
||||
private int commandCount;
|
||||
|
||||
public CommandRecorder() {
|
||||
this.buffer = new CommandBuffer();
|
||||
this.commandCount = 0;
|
||||
}
|
||||
|
||||
public CommandBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public int getCommandCount() {
|
||||
return commandCount;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
buffer.free();
|
||||
}
|
||||
|
||||
// ==================== Recording Methods ====================
|
||||
|
||||
public void recordEnable(int cap) {
|
||||
buffer.writeEnable(cap);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDisable(int cap) {
|
||||
buffer.writeDisable(cap);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClear(int mask) {
|
||||
buffer.writeClear(mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClearColor(float r, float g, float b, float a) {
|
||||
buffer.writeClearColor(r, g, b, a);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClearDepth(double depth) {
|
||||
buffer.writeClearDepth(depth);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBlendColor(float r, float g, float b, float a) {
|
||||
buffer.writeBlendColor(r, g, b, a);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClearStencil(int s) {
|
||||
buffer.writeClearStencil(s);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordColor(float r, float g, float b, float a) {
|
||||
buffer.writeColor(r, g, b, a);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
|
||||
buffer.writeColorMask(r, g, b, a);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDepthMask(boolean flag) {
|
||||
buffer.writeDepthMask(flag);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFrontFace(int mode) {
|
||||
buffer.writeFrontFace(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDepthFunc(int func) {
|
||||
buffer.writeDepthFunc(func);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
buffer.writeBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordAlphaFunc(int func, float ref) {
|
||||
buffer.writeAlphaFunc(func, ref);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordCullFace(int mode) {
|
||||
buffer.writeCullFace(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordShadeModel(int mode) {
|
||||
buffer.writeShadeModel(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBindTexture(int target, int texture) {
|
||||
buffer.writeBindTexture(target, texture);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordTexParameteri(int target, int pname, int param) {
|
||||
buffer.writeTexParameteri(target, pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordTexParameterf(int target, int pname, float param) {
|
||||
buffer.writeTexParameterf(target, pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordMatrixMode(int mode) {
|
||||
buffer.writeMatrixMode(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLoadIdentity() {
|
||||
buffer.writeLoadIdentity();
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPushMatrix() {
|
||||
buffer.writePushMatrix();
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPopMatrix() {
|
||||
buffer.writePopMatrix();
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordMultMatrix(Matrix4f matrix) {
|
||||
buffer.writeMultMatrix(matrix);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLoadMatrix(Matrix4f matrix) {
|
||||
buffer.writeLoadMatrix(matrix);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
buffer.writeOrtho(left, right, bottom, top, zNear, zFar);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
buffer.writeFrustum(left, right, bottom, top, zNear, zFar);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordViewport(int x, int y, int width, int height) {
|
||||
buffer.writeViewport(x, y, width, height);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordScissor(int x, int y, int width, int height) {
|
||||
buffer.writeScissor(x, y, width, height);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPointSize(float size) {
|
||||
buffer.writePointSize(size);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLineWidth(float width) {
|
||||
buffer.writeLineWidth(width);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLineStipple(int factor, int pattern) {
|
||||
buffer.writeLineStipple(factor, pattern);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPolygonOffset(float factor, float units) {
|
||||
buffer.writePolygonOffset(factor, units);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPolygonMode(int face, int mode) {
|
||||
buffer.writePolygonMode(face, mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordColorMaterial(int face, int mode) {
|
||||
buffer.writeColorMaterial(face, mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLogicOp(int opcode) {
|
||||
buffer.writeLogicOp(opcode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordActiveTexture(int texture) {
|
||||
buffer.writeActiveTexture(texture);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordUseProgram(int program) {
|
||||
buffer.writeUseProgram(program);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPushAttrib(int mask) {
|
||||
buffer.writePushAttrib(mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPopAttrib() {
|
||||
buffer.writePopAttrib();
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFogf(int pname, float param) {
|
||||
buffer.writeFogf(pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFogi(int pname, int param) {
|
||||
buffer.writeFogi(pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordHint(int target, int mode) {
|
||||
buffer.writeHint(target, mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFog(int pname, FloatBuffer params) {
|
||||
buffer.writeFog(pname, params);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLightf(int light, int pname, float param) {
|
||||
buffer.writeLightf(light, pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLighti(int light, int pname, int param) {
|
||||
buffer.writeLighti(light, pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLight(int light, int pname, FloatBuffer params) {
|
||||
buffer.writeLight(light, pname, params);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLightModelf(int pname, float param) {
|
||||
buffer.writeLightModelf(pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLightModeli(int pname, int param) {
|
||||
buffer.writeLightModeli(pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLightModel(int pname, FloatBuffer params) {
|
||||
buffer.writeLightModel(pname, params);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordMaterialf(int face, int pname, float val) {
|
||||
buffer.writeMaterialf(face, pname, val);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordMaterial(int face, int pname, FloatBuffer params) {
|
||||
buffer.writeMaterial(face, pname, params);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClipPlane(int plane, double a, double b, double c, double d) {
|
||||
buffer.writeClipPlane(plane, a, b, c, d);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilFunc(int func, int ref, int mask) {
|
||||
buffer.writeStencilFunc(func, ref, mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilFuncSeparate(int face, int func, int ref, int mask) {
|
||||
buffer.writeStencilFuncSeparate(face, func, ref, mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilOp(int fail, int zfail, int zpass) {
|
||||
buffer.writeStencilOp(fail, zfail, zpass);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
|
||||
buffer.writeStencilOpSeparate(face, sfail, dpfail, dppass);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilMask(int mask) {
|
||||
buffer.writeStencilMask(mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilMaskSeparate(int face, int mask) {
|
||||
buffer.writeStencilMaskSeparate(face, mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordCallList(int listId) {
|
||||
buffer.writeCallList(listId);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawBuffer(int mode) {
|
||||
buffer.writeDrawBuffer(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawBuffers(int count, int buf) {
|
||||
buffer.writeDrawBuffers(count, buf);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawBuffers(int count, IntBuffer bufs) {
|
||||
buffer.writeDrawBuffers(count, bufs);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawArrays(int mode, int start, int count) {
|
||||
buffer.writeDrawArrays(mode, start, count);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
|
||||
buffer.writeDrawElements(mode, indices_count, type, indices_buffer_offset);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBindVBO(int vbo) {
|
||||
buffer.writeBindVBO(vbo);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBindVAO(int vao) {
|
||||
buffer.writeBindVAO(vao);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordComplexCommand(DisplayListCommand cmd) {
|
||||
buffer.writeComplexRef(cmd);
|
||||
commandCount++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
|
||||
/**
|
||||
* Represents a compiled display list.
|
||||
*
|
||||
* <p>Contains commands serialized to a direct ByteBuffer for off-heap storage,
|
||||
* with complex objects (TexImage2DCmd, TexSubImage2DCmd) stored separately.
|
||||
*
|
||||
* <p>The ownedVbos array contains VBOs that are referenced by DrawRange commands.
|
||||
*/
|
||||
public final class CompiledDisplayList {
|
||||
/**
|
||||
* Empty display list singleton. Per OpenGL spec, an empty list is still valid.
|
||||
* This instance does nothing when rendered and is never deleted (shared singleton).
|
||||
*/
|
||||
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null);
|
||||
|
||||
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
|
||||
private final Object[] complexObjects; // Complex commands (TexImage2D, etc.)
|
||||
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
|
||||
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO ownedVbos) {
|
||||
this.commandBuffer = commandBuffer;
|
||||
this.complexObjects = complexObjects;
|
||||
this.ownedVbos = ownedVbos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this display list by executing all commands.
|
||||
*/
|
||||
public void render() {
|
||||
if (commandBuffer != null && commandBuffer.limit() > 0) {
|
||||
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all resources held by this display list.
|
||||
*/
|
||||
public void delete() {
|
||||
// Delete complex objects that hold resources
|
||||
if (complexObjects != null) {
|
||||
for (Object obj : complexObjects) {
|
||||
if (obj instanceof DisplayListCommand cmd) {
|
||||
cmd.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close VBOs
|
||||
if (ownedVbos != null) {
|
||||
ownedVbos.delete();
|
||||
}
|
||||
|
||||
// Free the command buffer (off-heap memory)
|
||||
if (commandBuffer != null) {
|
||||
memFree(commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command buffer for debugging/inspection.
|
||||
*/
|
||||
public ByteBuffer getCommandBuffer() {
|
||||
return commandBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complex objects for inlining into another display list (nested lists).
|
||||
*/
|
||||
public Object[] getComplexObjects() {
|
||||
return complexObjects;
|
||||
}
|
||||
|
||||
public DisplayListVBO getOwnedVbos() {
|
||||
return ownedVbos;
|
||||
}
|
||||
|
||||
// === Test inspection methods ===
|
||||
|
||||
/**
|
||||
* Get counts of each command type in the buffer.
|
||||
* Used for testing to verify optimization results.
|
||||
*/
|
||||
public Int2IntMap getCommandCounts() {
|
||||
final Int2IntMap counts = new Int2IntOpenHashMap();
|
||||
if (commandBuffer == null || commandBuffer.limit() == 0) {
|
||||
return counts;
|
||||
}
|
||||
long ptr = memAddress(commandBuffer);
|
||||
final long end = ptr + commandBuffer.limit();
|
||||
while (ptr < end) {
|
||||
final int cmd = memGetInt(ptr);
|
||||
counts.mergeInt(cmd, 1, Integer::sum);
|
||||
ptr += GLCommand.getCommandSize(cmd, ptr);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sequence of command opcodes in order.
|
||||
* Used for testing to verify command ordering.
|
||||
*/
|
||||
public IntList getCommandOpcodes() {
|
||||
final IntList opcodes = new IntArrayList();
|
||||
if (commandBuffer == null || commandBuffer.limit() == 0) {
|
||||
return opcodes;
|
||||
}
|
||||
long ptr = memAddress(commandBuffer);
|
||||
final long end = ptr + commandBuffer.limit();
|
||||
while (ptr < end) {
|
||||
final int cmd = memGetInt(ptr);
|
||||
opcodes.add(cmd);
|
||||
ptr += GLCommand.getCommandSize(cmd, ptr);
|
||||
}
|
||||
return opcodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return DisplayListManager.getCompiledDisplayListString(0, this, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
|
||||
/**
|
||||
* A class that stores multiple vertex formats & their corresponding buffers.
|
||||
*/
|
||||
public final class DisplayListVBO {
|
||||
|
||||
private final SubVBO[] vbos;
|
||||
|
||||
DisplayListVBO(SubVBO[] vbos) {
|
||||
this.vbos = vbos;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
for (SubVBO vbo : vbos) {
|
||||
vbo.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public SubVBO[] getVBOs() {
|
||||
return vbos;
|
||||
}
|
||||
|
||||
public void render(int index) {
|
||||
vbos[index].render();
|
||||
}
|
||||
|
||||
public static final class SubVBO {
|
||||
|
||||
private final IVertexArrayObject vao;
|
||||
private final int drawMode;
|
||||
private final int start;
|
||||
private final int count;
|
||||
|
||||
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count) {
|
||||
this.vao = vao;
|
||||
this.drawMode = drawMode;
|
||||
this.start = start;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
vao.delete();
|
||||
}
|
||||
|
||||
public void render() {
|
||||
vao.bind();
|
||||
vao.draw(drawMode, start, count);
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
public IVertexArrayObject getVAO() {
|
||||
return vao;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 779 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user