mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 17:24:43 +00:00
Compare commits
176
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
4b2d57a32d | ||
|
|
06bf71126f | ||
|
|
4c0f2f2685 | ||
|
|
78334717e7 | ||
|
|
a77765a2c9 | ||
|
|
1a9fe45ee9 | ||
|
|
21ef5905c8 | ||
|
|
5b67eaa474 | ||
|
|
c9d59c3b90 | ||
|
|
fa7ff74fec | ||
|
|
a34a3ebd39 | ||
|
|
2e7476d287 | ||
|
|
3244071012 | ||
|
|
7035dbde02 | ||
|
|
97ae04c2fa | ||
|
|
e7fa7f62d0 | ||
|
|
ea39712749 | ||
|
|
66dd3eaa94 | ||
|
|
270697cd10 | ||
|
|
2b53a851ef | ||
|
|
34b6efde7b | ||
|
|
86edb721da | ||
|
|
39c5f4bf7b | ||
|
|
5dc54bb1da | ||
|
|
7924769ab1 | ||
|
|
8afe2e2ccf | ||
|
|
e8dd60e088 | ||
|
|
9a621405ba | ||
|
|
45a677030d | ||
|
|
4d531022d2 | ||
|
|
48ba2160ad | ||
|
|
334ec81f9b | ||
|
|
86c726e50b | ||
|
|
e6d778dad3 | ||
|
|
dce5013ffb | ||
|
|
6eef63619c | ||
|
|
a8153fa0f7 | ||
|
|
49c04cf776 | ||
|
|
85b0277a66 | ||
|
|
0525b31262 | ||
|
|
fdb2053c50 | ||
|
|
31abbcce32 | ||
|
|
063b2242e8 | ||
|
|
577f905dc5 | ||
|
|
071d2d4a39 | ||
|
|
891715b113 | ||
|
|
30a35ef25e | ||
|
|
33453b3b8c | ||
|
|
62e699f6d0 | ||
|
|
f107d173b7 | ||
|
|
d0948b36f3 | ||
|
|
ae1211f280 | ||
|
|
3d76cb3073 | ||
|
|
45fb120223 | ||
|
|
d7a196eb5c | ||
|
|
4d53e2af9b | ||
|
|
cb6336a365 | ||
|
|
5095021e0e | ||
|
|
f88ecbf68d | ||
|
|
7da9b9eb1c | ||
|
|
02c394108f | ||
|
|
720aaca1b7 | ||
|
|
1e71b1c4d8 | ||
|
|
df4c423e93 | ||
|
|
78e0074e5a | ||
|
|
0350ea2cb6 | ||
|
|
bed724ba59 | ||
|
|
6fa973dbc8 | ||
|
|
c4ad460f34 | ||
|
|
2a8404c6c2 | ||
|
|
108db9031b | ||
|
|
d6d8a5dad6 | ||
|
|
ad2b73edf9 | ||
|
|
bb5526598e | ||
|
|
611bbf02c8 | ||
|
|
ca16036604 | ||
|
|
f118211257 | ||
|
|
81d03ea552 | ||
|
|
67c8628aeb | ||
|
|
f9b9873637 | ||
|
|
c66bf8658e | ||
|
|
052824017d | ||
|
|
6a5d914a43 | ||
|
|
208373deaa | ||
|
|
f22a3f612b | ||
|
|
9ff1015d19 | ||
|
|
0023fe3da2 | ||
|
|
7e2399714c | ||
|
|
9029182353 | ||
|
|
e6039691b5 | ||
|
|
5db1edba33 | ||
|
|
d450461726 | ||
|
|
95f8afc0f3 | ||
|
|
19635e20ec | ||
|
|
ead4e11376 | ||
|
|
b0ceb5306a | ||
|
|
99a81369f0 | ||
|
|
e2ee822948 | ||
|
|
9fe7e800f6 | ||
|
|
debfcdfda0 | ||
|
|
df662188f4 | ||
|
|
842916405f | ||
|
|
09afe81290 | ||
|
|
292809d8e8 | ||
|
|
47503ce254 | ||
|
|
f738adbdef | ||
|
|
70d15f2383 | ||
|
|
d66b5577f4 | ||
|
|
e293f12fb8 | ||
|
|
b69eca6749 | ||
|
|
03342f08c6 | ||
|
|
b85614f983 |
No files matched your search
@@ -39,6 +39,7 @@ body:
|
||||
- Java 19
|
||||
- Java 20
|
||||
- Java 21
|
||||
- Java 25
|
||||
- Other (Please Specify)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -7,40 +7,41 @@
|
||||
* It's a cute angle -mitchej123
|
||||
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
|
||||
|
||||
## **Beta Quality - Here there be Dragons**
|
||||
* Shaders sneak preview - Please don't report issues yet, but please do submit fixes
|
||||
* Complementary & BSL mostly function, but have some bugs
|
||||
* Sildurs, Seus, and any other shader probably doesn't work
|
||||
## **Beta Quality - Here there be Dragons**
|
||||
* Shaders - Beta
|
||||
* Complementary + Euphoria Patches is the primary compatibility target
|
||||
* Sildurs, Seus, and any other shaders might work
|
||||
|
||||
|
||||
# Required Dependencies
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.14
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.4.2
|
||||
* [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
|
||||
* Forge Relocation - Currently non Thread Safe; safe if not in use
|
||||
* ProjectRed Frames - Currently non Thread Safe; safe if not in use'
|
||||
* ProjectRed Frames - Currently non Thread Safe; safe if not in use
|
||||
|
||||
# Potential Incompatibilities
|
||||
* EndlessIDs - Compat commits have been made, might work, might not - not fully tested
|
||||
|
||||
# Known Incompatibilities
|
||||
* FalseTweaks - Not currently planned (Looking for volunteers) [Might work if you effectively disable most of the mod]
|
||||
* SmoothFont - Not currently planned (Looking for volunteers)
|
||||
* RPLE - Not currently planned (Looking for volunteers)
|
||||
* LiteLoader - Install [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
|
||||
|
||||
# Permanent Incompatibilities
|
||||
* OptiFine [Disabled, won't fix]
|
||||
* FastCraft [Disabled, won't fix]
|
||||
* BetterFPS [Disabled, won't fix]
|
||||
* OptimizationsAndTweaks [Disabled, won't fix]
|
||||
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - use 1.2.2+
|
||||
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
|
||||
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
|
||||
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
|
||||
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 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
|
||||
|
||||
# Credits
|
||||
* ShadersMod
|
||||
@@ -64,7 +65,7 @@
|
||||
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
|
||||
* MCPatcherForge
|
||||
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
|
||||
* [Forge version](https://bitbucket.org/prupe/mcpatcher/src/master/): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
|
||||
* [Forge version](https://github.com/mist475/MCPatcherForge): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
|
||||
* HUDCaching
|
||||
* Created by [Moulberry](https://github.com/Moulberry/MCHUDCaching), backported from [Patcher](https://github.com/Sk1erLLC/Patcher) mod by Alexdoru under the CC-BY-SA-NC 4.0 license
|
||||
* Angelica
|
||||
@@ -82,3 +83,5 @@
|
||||
* 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
|
||||
+22
-2
@@ -4,6 +4,10 @@ plugins {
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
|
||||
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
@@ -11,17 +15,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,3 +42,12 @@ tasks.register<Copy>("copyDependencies") {
|
||||
from(configurations.default)
|
||||
into("testDependencies")
|
||||
}
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
|
||||
tasks.shadowJar {
|
||||
from(embedOnly.map(::zipTree))
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
}
|
||||
|
||||
+53
-28
@@ -46,45 +46,58 @@ 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)
|
||||
}
|
||||
|
||||
|
||||
// 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.5.78")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
|
||||
|
||||
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:0.4.0") { transitive = false }
|
||||
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.22") { transitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.22")
|
||||
|
||||
// Use modern models+textures to inject QuadProviders
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:TX-Loader:1.7.2:dev')
|
||||
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.42")
|
||||
|
||||
// Iris Shaders
|
||||
compileOnly('org.jetbrains:annotations:24.0.1')
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.5.20:dev")
|
||||
compileOnly('org.jetbrains:annotations:26.0.2')
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.9.0: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-25.g3a943bb") {
|
||||
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.1-GTNH") { transitive = false }
|
||||
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.1-GTNH") { transitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.6.45-GTNH:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.3.10:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
|
||||
|
||||
// Notfine Deps
|
||||
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles:1.0.4:dev")
|
||||
compileOnly("com.github.GTNewHorizons:twilightforest:2.6.35:dev") { transitive = false }
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.13:dev") { transitive = false }
|
||||
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
|
||||
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.12.14-GTNH:dev") { transitive = false }
|
||||
compileOnly("com.github.GTNewHorizons:Natura:2.7.5:dev")
|
||||
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.6-GTNH:dev") { transitive = false }
|
||||
compileOnly("com.github.GTNewHorizons:Natura:2.8.9:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.6.4:dev")
|
||||
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.4:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.1:dev") { transitive = false }
|
||||
compileOnly(rfg.deobf('maven.modrinth:backhand:1.4.1')) { transitive = false }
|
||||
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { transitive = false }
|
||||
|
||||
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { transitive = false }
|
||||
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { transitive = false }
|
||||
@@ -94,15 +107,18 @@ dependencies {
|
||||
|
||||
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.6:dev") // Mixin Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev") // Mixin Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:ntm-space-1121235:6974420"))
|
||||
|
||||
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("com.github.GTNewHorizons:HoloInventory:2.4.13-GTNH:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
|
||||
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:CoreTweaks:0.3.3.6-GTNH")
|
||||
// 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")))
|
||||
|
||||
// Hodgepodge
|
||||
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
|
||||
@@ -110,9 +126,18 @@ dependencies {
|
||||
// Better Crashes
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
|
||||
|
||||
// Tests
|
||||
testImplementation(platform('org.junit:junit-bom:5.9.2'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testImplementation("org.mockito:mockito-core:3.+")
|
||||
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
|
||||
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")
|
||||
}
|
||||
+43
-16
@@ -14,7 +14,7 @@ modId = angelica
|
||||
modGroup = com.gtnewhorizons.angelica
|
||||
|
||||
# Whether to use modGroup as the maven publishing group.
|
||||
# Due to a history of using JitPack, the default is com.github.GTNewHorizons for all mods.
|
||||
# When false, com.github.GTNewHorizons is used.
|
||||
useModGroupForPublishing = false
|
||||
|
||||
# Updates your build.gradle and settings.gradle automatically whenever an update is available.
|
||||
@@ -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.
|
||||
@@ -96,7 +111,9 @@ usesMixinDebug = false
|
||||
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
|
||||
mixinPlugin =
|
||||
|
||||
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
|
||||
# Specify the package that contains all of your Mixins. The package must exist or
|
||||
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
|
||||
# it must match with this or the build will fail.
|
||||
mixinsPackage = mixins
|
||||
|
||||
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
|
||||
@@ -151,7 +168,7 @@ modrinthProjectId = angelica
|
||||
# type can be one of [project, version],
|
||||
# and the name is the Modrinth project or version slug/id of the other mod.
|
||||
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
|
||||
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
modrinthRelations = required-project\:gtnhlib
|
||||
|
||||
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
|
||||
@@ -172,6 +189,12 @@ curseForgeRelations = requiredDependency\:gtnhlib
|
||||
# projects. New projects should not use this parameter.
|
||||
# customArchiveBaseName =
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
|
||||
# runClientWorkingDirectory = run/client
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
|
||||
# runServerWorkingDirectory = run/server
|
||||
|
||||
# Optional parameter to have the build automatically fail if an illegal version is used.
|
||||
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
|
||||
# The check is ONLY performed if the version is a git tag.
|
||||
@@ -201,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/5ee463876563a46fff651cce1bc53a4c/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/5ee463876563a46fff651cce1bc53a4c/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/5ee463876563a46fff651cce1bc53a4c/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/832229f0f6f0be60ed817c5a5e5848eb/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/b96cb7670c095245220e7d01d5927b58/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.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-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.
|
||||
@@ -86,8 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -115,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.
|
||||
@@ -173,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" )
|
||||
|
||||
@@ -206,15 +203,14 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
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
|
||||
|
||||
+5
-15
@@ -8,21 +8,11 @@ repositories {
|
||||
includeGroup 'com.falsepattern'
|
||||
}
|
||||
}
|
||||
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 = 'mega'
|
||||
url = 'https://mvn.falsepattern.com/gtmega_releases/'
|
||||
content {
|
||||
includeGroup 'mega'
|
||||
}
|
||||
}
|
||||
maven {
|
||||
|
||||
@@ -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.29'
|
||||
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.13")
|
||||
}
|
||||
@@ -2,21 +2,26 @@ package com.gtnewhorizons.angelica;
|
||||
|
||||
import com.gtnewhorizon.gtnhmixins.ILateMixinLoader;
|
||||
import com.gtnewhorizon.gtnhmixins.LateMixin;
|
||||
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
|
||||
import com.gtnewhorizons.angelica.mixins.Mixins;
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMixins(Set<String> loadedMods) {
|
||||
return Mixins.getLateMixins(loadedMods);
|
||||
return IMixins.getLateMixins(Mixins.class, loadedMods);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
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.proxy.CommonProxy;
|
||||
@@ -7,20 +9,30 @@ import com.gtnewhorizons.angelica.utils.AnimationMode;
|
||||
import com.gtnewhorizons.angelica.utils.ManagedEnum;
|
||||
import cpw.mods.fml.common.Mod;
|
||||
import cpw.mods.fml.common.SidedProxy;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
|
||||
@Mod(
|
||||
modid = "angelica",
|
||||
modid = MOD_ID,
|
||||
name = "Angelica",
|
||||
version = Tags.VERSION,
|
||||
dependencies = " before:lwjgl3ify@[1.5.3,);" + " after:hodgepodge@[2.4.4,);" + " after:CodeChickenCore@[1.2.0,);"
|
||||
+ " after:archaicfix@[0.7.0,);" + " required-after:gtnhlib@[0.4.2,);",
|
||||
dependencies = """
|
||||
required-after:gtnhlib@[0.8.21,);\
|
||||
before:lwjgl3ify@[1.5.3,);\
|
||||
after:hodgepodge@[2.4.4,);\
|
||||
after:CodeChickenCore@[1.2.0,);\
|
||||
after:archaicfix@[0.7.0,);\
|
||||
""",
|
||||
acceptedMinecraftVersions = "[1.7.10]",
|
||||
acceptableRemoteVersions = "*",
|
||||
guiFactory = "com.gtnewhorizons.angelica.config.AngelicaGuiConfigFactory")
|
||||
public class AngelicaMod {
|
||||
public static final String MOD_ID = "angelica";
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
@SidedProxy(clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy", serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
|
||||
public static CommonProxy proxy;
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public interface IBlockAccessExtended extends IBlockAccess {
|
||||
World getWorld();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Exposes a method to the API that allow mods to add texture update compat
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class TextureServices {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void updateBlockTextureAnimation(IIcon icon, RenderBlocks renderBlocks) {
|
||||
if (renderBlocks instanceof ITexturesCache texturesCache) {
|
||||
texturesCache.getRenderedTextures().add(icon);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void updateTextureAnimation(IIcon icon) {
|
||||
if (icon instanceof SpriteExtension ext) {
|
||||
ext.celeritas$markActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
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 it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.coderbot.iris.gl.program.Program;
|
||||
import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
|
||||
/**
|
||||
* A batching replacement for {@code FontRenderer}
|
||||
*
|
||||
@@ -29,8 +36,6 @@ public class BatchingFontRenderer {
|
||||
|
||||
/** The underlying FontRenderer object that's being accelerated */
|
||||
protected FontRenderer underlying;
|
||||
/** Cached locations for each unicode page atlas */
|
||||
private final ResourceLocation[] unicodePageLocations;
|
||||
/** 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. */
|
||||
@@ -42,50 +47,59 @@ public class BatchingFontRenderer {
|
||||
private int[] colorCode;
|
||||
/** Location of the primary font atlas to bind. */
|
||||
protected final ResourceLocation locationFontTexture;
|
||||
/** The RenderEngine used to load and setup glyph textures. */
|
||||
private final TextureManager renderEngine;
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
|
||||
/** The full list of characters present in the default Minecraft font, excluding the Unicode font */
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||
private final int AAMode;
|
||||
private final int AAStrength;
|
||||
private final int texBoundAttrLocation;
|
||||
private final int fontShaderId;
|
||||
|
||||
private static final short[] MCFONT_ASCII_LUT = new short[512];
|
||||
private static final Char2ShortOpenHashMap MCFONT_UNI_LUT = new Char2ShortOpenHashMap();
|
||||
final boolean isSGA;
|
||||
final boolean isSplash;
|
||||
|
||||
static {
|
||||
Arrays.fill(MCFONT_ASCII_LUT, (short) -1);
|
||||
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
char ch = MCFONT_CHARS.charAt(i);
|
||||
if (ch < MCFONT_ASCII_LUT.length) {
|
||||
MCFONT_ASCII_LUT[ch] = i;
|
||||
} else {
|
||||
MCFONT_UNI_LUT.put(ch, i);
|
||||
private static class FontAAShader {
|
||||
|
||||
private static Program fontShader = null;
|
||||
public static Program getProgram() {
|
||||
if (fontShader == null) {
|
||||
String vsh, fsh;
|
||||
try {
|
||||
fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.fsh"))), StandardCharsets.UTF_8);
|
||||
vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.vsh"))), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
ProgramBuilder builder = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0));
|
||||
fontShader = builder.build();
|
||||
}
|
||||
return fontShader;
|
||||
}
|
||||
}
|
||||
|
||||
public static int lookupMcFontPosition(char ch) {
|
||||
if (ch < MCFONT_ASCII_LUT.length) {
|
||||
return MCFONT_ASCII_LUT[ch];
|
||||
} else {
|
||||
return MCFONT_UNI_LUT.getOrDefault(ch, (short) -1);
|
||||
}
|
||||
}
|
||||
|
||||
public BatchingFontRenderer(FontRenderer underlying, ResourceLocation[] unicodePageLocations, int[] charWidth,
|
||||
byte[] glyphWidth, int[] colorCode, ResourceLocation locationFontTexture, TextureManager renderEngine) {
|
||||
public BatchingFontRenderer(FontRenderer underlying, int[] charWidth, byte[] glyphWidth,
|
||||
int[] colorCode, ResourceLocation locationFontTexture) {
|
||||
this.underlying = underlying;
|
||||
this.unicodePageLocations = unicodePageLocations;
|
||||
this.charWidth = charWidth;
|
||||
this.glyphWidth = glyphWidth;
|
||||
this.colorCode = colorCode;
|
||||
this.locationFontTexture = locationFontTexture;
|
||||
this.renderEngine = renderEngine;
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
batchCommandPool.add(new FontDrawCmd());
|
||||
}
|
||||
|
||||
this.isSGA = Objects.equals(this.locationFontTexture.getResourcePath(), "textures/font/ascii_sga.png");
|
||||
this.isSplash = FontStrategist.isSplashFontRendererActive(underlying);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
// === Batched rendering
|
||||
@@ -97,24 +111,29 @@ public class BatchingFontRenderer {
|
||||
private static final int INITIAL_BATCH_SIZE = 256;
|
||||
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
|
||||
"this is invalid!");
|
||||
private FloatBuffer batchVtxPositions = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
|
||||
private ByteBuffer batchVtxColors = BufferUtils.createByteBuffer(INITIAL_BATCH_SIZE * 4);
|
||||
private FloatBuffer batchVtxTexCoords = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
|
||||
private IntBuffer batchIndices = BufferUtils.createIntBuffer(INITIAL_BATCH_SIZE / 2 * 3);
|
||||
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);
|
||||
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) {
|
||||
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 = CompatMemoryUtil.memReallocDirect(batchVtxPositions, newCap * 2);
|
||||
batchVtxColors = CompatMemoryUtil.memReallocDirect(batchVtxColors, newCap * 4);
|
||||
batchVtxTexCoords = CompatMemoryUtil.memReallocDirect(batchVtxTexCoords, newCap * 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 = CompatMemoryUtil.memReallocDirect(batchIndices, newIdxCap);
|
||||
batchIndices = memRealloc(batchIndices, newIdxCap);
|
||||
}
|
||||
final int idx = vtxWriterIndex;
|
||||
final int idx2 = idx * 2;
|
||||
@@ -128,15 +147,28 @@ public class BatchingFontRenderer {
|
||||
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++;
|
||||
}
|
||||
|
||||
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
|
||||
final int vtxId = vtxWriterIndex;
|
||||
pushVtx(x, y, rgba, 0, 0);
|
||||
pushVtx(x, y + h, rgba, 0, 0);
|
||||
pushVtx(x + w, y, rgba, 0, 0);
|
||||
pushVtx(x + w, y + h, rgba, 0, 0);
|
||||
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);
|
||||
}
|
||||
|
||||
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
|
||||
final int vtxId = vtxWriterIndex;
|
||||
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(vtxId);
|
||||
}
|
||||
|
||||
@@ -153,7 +185,7 @@ public class BatchingFontRenderer {
|
||||
return idx;
|
||||
}
|
||||
|
||||
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture) {
|
||||
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
|
||||
if (!batchCommands.isEmpty()) {
|
||||
final FontDrawCmd lastCmd = batchCommands.get(batchCommands.size() - 1);
|
||||
final int prevEndVtx = lastCmd.startVtx + lastCmd.idxCount;
|
||||
@@ -169,7 +201,7 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
final FontDrawCmd cmd = batchCommandPool.pop();
|
||||
cmd.reset(startIdx, idxCount, texture);
|
||||
cmd.reset(startIdx, idxCount, texture, isUnicode);
|
||||
batchCommands.add(cmd);
|
||||
}
|
||||
|
||||
@@ -177,12 +209,14 @@ public class BatchingFontRenderer {
|
||||
|
||||
public int startVtx;
|
||||
public int idxCount;
|
||||
public boolean isUnicode;
|
||||
public ResourceLocation texture;
|
||||
|
||||
public void reset(int startVtx, int vtxCount, ResourceLocation texture) {
|
||||
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode) {
|
||||
this.startVtx = startVtx;
|
||||
this.idxCount = vtxCount;
|
||||
this.texture = texture;
|
||||
this.isUnicode = isUnicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -241,7 +275,12 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
int lastActiveProgram;
|
||||
int fontAAModeLast = -1;
|
||||
int fontAAStrengthLast = -1;
|
||||
private void flushBatch() {
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
// Sort&Draw
|
||||
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
@@ -253,9 +292,26 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
|
||||
final boolean canUseAA = FontConfig.fontAAMode != 0 && prevProgram == 0;
|
||||
if (canUseAA) {
|
||||
GL20.glVertexAttribPointer(texBoundAttrLocation, 4, false, 0, batchVtxTexBounds);
|
||||
GL20.glEnableVertexAttribArray(texBoundAttrLocation);
|
||||
lastActiveProgram = prevProgram;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
GL11.glTexCoordPointer(2, 0, batchVtxTexCoords);
|
||||
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 0, batchVtxColors);
|
||||
@@ -264,6 +320,9 @@ public class BatchingFontRenderer {
|
||||
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY);
|
||||
GL11.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
|
||||
// Use plain for loop to avoid allocations
|
||||
final FontDrawCmd[] cmdsData = batchCommands.elements();
|
||||
final int cmdsSize = batchCommands.size();
|
||||
@@ -283,8 +342,13 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
batchIndices.limit(cmd.startVtx + cmd.idxCount);
|
||||
batchIndices.position(cmd.startVtx);
|
||||
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, batchIndices);
|
||||
}
|
||||
if (canUseAA) {
|
||||
GLStateManager.glUseProgram(lastActiveProgram);
|
||||
GL20.glDisableVertexAttribArray(texBoundAttrLocation);
|
||||
}
|
||||
|
||||
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
|
||||
@@ -312,17 +376,28 @@ public class BatchingFontRenderer {
|
||||
return (what >= fromInclusive) && (what <= toInclusive);
|
||||
}
|
||||
|
||||
private ResourceLocation getUnicodePageLocation(int page) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
if (lookup == null) {
|
||||
final ResourceLocation rl = new ResourceLocation(String.format(
|
||||
"textures/font/unicode_page_%02x.png",
|
||||
page));
|
||||
unicodePageLocations[page] = rl;
|
||||
return rl;
|
||||
} else {
|
||||
return lookup;
|
||||
}
|
||||
public boolean forceDefaults() {
|
||||
return this.isSGA || this.isSplash;
|
||||
}
|
||||
|
||||
public float getGlyphScaleX() {
|
||||
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));
|
||||
}
|
||||
|
||||
public float getGlyphSpacing() {
|
||||
return forceDefaults() ? 0 : FontConfig.glyphSpacing;
|
||||
}
|
||||
|
||||
public float getWhitespaceScale() {
|
||||
return forceDefaults() ? 1 : FontConfig.whitespaceScale;
|
||||
}
|
||||
|
||||
public float getShadowOffset() {
|
||||
return forceDefaults() ? 1 : FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
private static final char FORMATTING_CHAR = 167; // §
|
||||
@@ -335,6 +410,9 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
final int shadowColor = (color & 0xfcfcfc) >> 2 | color & 0xff000000;
|
||||
|
||||
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
|
||||
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
|
||||
|
||||
this.beginBatch();
|
||||
float curX = anchorX;
|
||||
try {
|
||||
@@ -354,10 +432,14 @@ public class BatchingFontRenderer {
|
||||
boolean curStrikethrough = false;
|
||||
boolean curUnderline = false;
|
||||
|
||||
final float underlineY = anchorY + underlying.FONT_HEIGHT - 1.0f;
|
||||
float glyphScaleY = getGlyphScaleY();
|
||||
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 = anchorY + (float) (underlying.FONT_HEIGHT / 2) - 1.0F;
|
||||
|
||||
final float strikethroughY = heightNorth + ((float) (underlying.FONT_HEIGHT / 2) - 1.0f) * glyphScaleY;
|
||||
float strikethroughStartX = 0.0f;
|
||||
float strikethroughEndX = 0.0f;
|
||||
|
||||
@@ -369,8 +451,8 @@ public class BatchingFontRenderer {
|
||||
|
||||
if (curUnderline && underlineStartX != underlineEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
underlineStartX = underlineEndX;
|
||||
}
|
||||
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
|
||||
@@ -379,9 +461,9 @@ public class BatchingFontRenderer {
|
||||
strikethroughStartX,
|
||||
strikethroughY,
|
||||
strikethroughEndX - strikethroughStartX,
|
||||
1.0f,
|
||||
glyphScaleY,
|
||||
curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
strikethroughStartX = strikethroughEndX;
|
||||
}
|
||||
|
||||
@@ -426,148 +508,83 @@ public class BatchingFontRenderer {
|
||||
continue;
|
||||
}
|
||||
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
if (curRandom && lutIndex != -1) {
|
||||
int randomReplacementIndex;
|
||||
do {
|
||||
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
|
||||
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
|
||||
|
||||
lutIndex = randomReplacementIndex;
|
||||
chr = MCFONT_CHARS.charAt(lutIndex);
|
||||
if (FontConfig.enableCustomFont) {
|
||||
String chrReplacement = GlyphReplacements.customGlyphs.get(String.valueOf(chr));
|
||||
if (chrReplacement != null) {
|
||||
chr = chrReplacement.charAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
final float shadowOffset = unicodeFlag ? 0.5F : 1.0F;
|
||||
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;
|
||||
curX += 4 * this.getWhitespaceScale();
|
||||
continue;
|
||||
}
|
||||
|
||||
final float uStart;
|
||||
final float vStart;
|
||||
final float xAdvance;
|
||||
final float glyphW;
|
||||
final float uSz;
|
||||
final float vSz;
|
||||
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 uSz = fontProvider.getUSize(chr);
|
||||
final float vSz = fontProvider.getVSize(chr);
|
||||
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
|
||||
final ResourceLocation texture;
|
||||
|
||||
if (lutIndex == -1 || unicodeFlag) {
|
||||
if (glyphWidth[chr] == 0) {
|
||||
continue;
|
||||
}
|
||||
// Draw unicode char
|
||||
final int uniPage = chr / 256;
|
||||
texture = getUnicodePageLocation(uniPage);
|
||||
final int startColumn = this.glyphWidth[chr] >>> 4;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
uStart = ((float) (chr % 16 * 16) + startColumnF) / 256.0f;
|
||||
vStart = ((float) ((chr & 255) / 16 * 16)) / 256.0f;
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
glyphW = chrWidth / 2.0f + 1.0f;
|
||||
xAdvance = (endColumnF - startColumnF) / 2.0F + 1.0F;
|
||||
uSz = chrWidth / 256.0f;
|
||||
vSz = 15.98f / 256.0f;
|
||||
|
||||
} else {
|
||||
// Draw "ASCII" char
|
||||
uStart = ((lutIndex % 16) * 8) / 128.0F;
|
||||
vStart = (float) ((lutIndex / 16) * 8) / 128.0F;
|
||||
xAdvance = this.charWidth[lutIndex];
|
||||
if (xAdvance == 0) {
|
||||
continue;
|
||||
}
|
||||
glyphW = xAdvance - 0.01F;
|
||||
uSz = (glyphW - 1.0F) / 128.0F;
|
||||
vSz = 7.99F / 128.0F;
|
||||
texture = locationFontTexture;
|
||||
}
|
||||
|
||||
final int vtxId = vtxWriterIndex;
|
||||
final float shadowOffset = fontProvider.getShadowOffset();
|
||||
final int shadowCopies = FontConfig.shadowCopies;
|
||||
final int boldCopies = FontConfig.boldCopies;
|
||||
final ResourceLocation texture = fontProvider.getTexture(chr);
|
||||
final int idxId = idxWriterIndex;
|
||||
|
||||
int vtxCount = 0;
|
||||
|
||||
if (enableShadow) {
|
||||
pushVtx(curX + itOff + shadowOffset, anchorY + shadowOffset, curShadowColor, uStart, vStart);
|
||||
pushVtx(
|
||||
curX - itOff + shadowOffset,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart,
|
||||
vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff + shadowOffset,
|
||||
anchorY + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
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, anchorY + shadowOffset, curShadowColor, uStart, vStart);
|
||||
pushVtx(
|
||||
curX - itOff + shadowOffset2,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart,
|
||||
vStart + vSz);
|
||||
pushVtx(
|
||||
curX + glyphW - 1.0F + itOff + shadowOffset2,
|
||||
anchorY + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset2,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
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, anchorY, curColor, uStart, vStart);
|
||||
pushVtx(curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff, anchorY + 7.99F, curColor, uStart + uSz, 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, anchorY, curColor, uStart, vStart);
|
||||
pushVtx(shadowOffset + curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
|
||||
pushVtx(shadowOffset + curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
|
||||
pushVtx(shadowOffset + curX + glyphW - 1.0F - itOff,
|
||||
anchorY + 7.99F,
|
||||
curColor,
|
||||
uStart + uSz,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
pushDrawCmd(idxId, vtxCount / 2 * 3, texture);
|
||||
curX += xAdvance + (curBold ? shadowOffset : 0.0f);
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
if (curUnderline && underlineStartX != underlineEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
}
|
||||
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
@@ -575,9 +592,9 @@ public class BatchingFontRenderer {
|
||||
strikethroughStartX,
|
||||
strikethroughY,
|
||||
strikethroughEndX - strikethroughStartX,
|
||||
1.0f,
|
||||
glyphScaleY,
|
||||
curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
}
|
||||
|
||||
} finally {
|
||||
@@ -586,4 +603,25 @@ public class BatchingFontRenderer {
|
||||
return curX + (enableShadow ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
public float getCharWidthFine(char chr) {
|
||||
if (chr == FORMATTING_CHAR) { return -1; }
|
||||
|
||||
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
|
||||
return 4 * this.getWhitespaceScale();
|
||||
}
|
||||
|
||||
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, underlying.getUnicodeFlag());
|
||||
|
||||
return fp.getXAdvance(chr) * this.getGlyphScaleX();
|
||||
}
|
||||
|
||||
public void overrideBlendFunc(int srcRgb, int dstRgb) {
|
||||
blendSrcRGB = srcRgb;
|
||||
blendDstRGB = dstRgb;
|
||||
}
|
||||
|
||||
public void resetBlendFunc() {
|
||||
blendSrcRGB = GL11.GL_SRC_ALPHA;
|
||||
blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public interface FontProvider {
|
||||
|
||||
/**
|
||||
* For use with §k. Should fetch a character of the same width as provided.
|
||||
*/
|
||||
char getRandomReplacement(char chr);
|
||||
boolean isGlyphAvailable(char chr);
|
||||
float getUStart(char chr);
|
||||
float getVStart(char chr);
|
||||
float getXAdvance(char chr);
|
||||
float getGlyphW(char chr);
|
||||
float getUSize(char chr);
|
||||
float getVSize(char chr);
|
||||
float getShadowOffset();
|
||||
ResourceLocation getTexture(char chr);
|
||||
float getYScaleMultiplier();
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.ITextureObject;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class FontProviderCustom implements FontProvider {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
public static final String FONT_DIR = "fonts/custom/";
|
||||
static final int ATLAS_SIZE = 128;
|
||||
static final int ATLAS_COUNT = 512;
|
||||
private final byte id; // 0 - primary font, 1 - fallback font
|
||||
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
private float currentFontQuality = FontConfig.customFontQuality;
|
||||
@Setter
|
||||
private Font font;
|
||||
|
||||
private FontProviderCustom(byte id) {
|
||||
this.id = id;
|
||||
Font[] availableFonts = FontStrategist.getAvailableFonts();
|
||||
String myFontName = switch (this.id) {
|
||||
case 0 -> FontConfig.customFontNamePrimary;
|
||||
case 1 -> FontConfig.customFontNameFallback;
|
||||
default -> null;
|
||||
};
|
||||
if (Objects.equals(myFontName, "(none)")) {
|
||||
font = null;
|
||||
return;
|
||||
}
|
||||
int fontPos = -1;
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(myFontName, availableFonts[i].getFontName())) {
|
||||
fontPos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fontPos == -1) {
|
||||
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
|
||||
font = null;
|
||||
return;
|
||||
}
|
||||
font = availableFonts[fontPos].deriveFont(currentFontQuality);
|
||||
}
|
||||
private static class InstLoader {
|
||||
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
|
||||
static final FontProviderCustom instance1 = new FontProviderCustom((byte)1);
|
||||
}
|
||||
public static FontProviderCustom getPrimary() { return InstLoader.instance0; }
|
||||
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
|
||||
|
||||
public void reloadFont(int fontID) {
|
||||
currentFontQuality = FontConfig.customFontQuality;
|
||||
font = FontStrategist.getAvailableFonts()[fontID].deriveFont(currentFontQuality);
|
||||
|
||||
File[] files = new File(getFontDir()).listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (!Files.isSymbolicLink(f.toPath())) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
|
||||
Map mapTextureObjects = tm.mapTextureObjects;
|
||||
for (int i = 0; i < ATLAS_COUNT; i++) {
|
||||
ResourceLocation key = new ResourceLocation(getAtlasResourceName(i));
|
||||
if (mapTextureObjects.containsKey(key)) {
|
||||
ITextureObject obj = (ITextureObject) mapTextureObjects.get(key);
|
||||
TextureUtil.deleteTexture(obj.getGlTextureId());
|
||||
mapTextureObjects.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
}
|
||||
|
||||
private String getFontDir() {
|
||||
return FONT_DIR + "f" + this.id + "/";
|
||||
}
|
||||
|
||||
private String getAtlasFilename(int atlasId) {
|
||||
return "f" + this.id + "p" + atlasId;
|
||||
}
|
||||
|
||||
String getAtlasResourceName(int atlasId) {
|
||||
return "minecraft:angelica_c" + getAtlasFilename(atlasId);
|
||||
}
|
||||
|
||||
String getAtlasFullPath(int atlasId) {
|
||||
return getFontDir() + getAtlasFilename(atlasId) + ".png";
|
||||
}
|
||||
|
||||
@Value
|
||||
private class GlyphData {
|
||||
float uStart;
|
||||
float vStart;
|
||||
float xAdvance;
|
||||
float glyphW;
|
||||
float uSz;
|
||||
float vSz;
|
||||
}
|
||||
|
||||
private class FontAtlas {
|
||||
|
||||
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
|
||||
private ResourceLocation texture;
|
||||
private final int id;
|
||||
|
||||
FontAtlas(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
void construct(Font font) {
|
||||
float separator = currentFontQuality / 3f;
|
||||
|
||||
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;
|
||||
int maxRowWidth = 0;
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
if (actualChars % atlasTilesX == 0) {
|
||||
maxRowWidth = Math.max(maxRowWidth, width);
|
||||
width = 0;
|
||||
}
|
||||
final char ch = (char)(i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) {
|
||||
width += (int) (separator + fm.charWidth(ch));
|
||||
actualChars++;
|
||||
}
|
||||
}
|
||||
maxRowWidth = Math.max(maxRowWidth, width);
|
||||
|
||||
final int lineHeight = fm.getHeight();
|
||||
final float desc = fm.getDescent();
|
||||
|
||||
final int imageWidth = (int) (maxRowWidth + separator);
|
||||
final int imageHeight = (int) ((separator + lineHeight) * atlasTilesY + separator);
|
||||
|
||||
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
g2d = image.createGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
g2d.setFont(font);
|
||||
fm = g2d.getFontMetrics();
|
||||
|
||||
int tileX = 0, tileY = 0; // position in atlas tiles
|
||||
int imgX = (int) separator; // position in pixels
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char)(i + ATLAS_SIZE * this.id);
|
||||
if (!font.canDisplay(ch)) { continue; }
|
||||
|
||||
if (tileX >= atlasTilesX) {
|
||||
tileX = 0;
|
||||
imgX = (int) separator;
|
||||
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);
|
||||
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
|
||||
final float vStart = ((lineHeight + separator) * (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);
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.texture = new ResourceLocation(getAtlasResourceName(this.id));
|
||||
}
|
||||
}
|
||||
|
||||
private FontAtlas getAtlas(char chr) {
|
||||
int id = chr / ATLAS_SIZE;
|
||||
FontAtlas fa = fontAtlases[id];
|
||||
if (fa == null) {
|
||||
fa = new FontAtlas(id);
|
||||
fa.construct(font);
|
||||
}
|
||||
fontAtlases[id] = fa;
|
||||
return fa;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
if (font == null) { return false; }
|
||||
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uSz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vSz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
return getAtlas(chr).texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return FontConfig.customFontScale;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public final class FontProviderMC implements FontProvider {
|
||||
|
||||
/**
|
||||
* The full list of characters present in the default Minecraft font, excluding the Unicode font
|
||||
*/
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||
|
||||
private FontProviderMC() {}
|
||||
private static class InstLoader {
|
||||
static final FontProviderMC instance = new FontProviderMC();
|
||||
static final FontProviderMC instanceSGA = new FontProviderMC();
|
||||
}
|
||||
public static FontProviderMC get(boolean isSGA) {
|
||||
return (isSGA ? InstLoader.instanceSGA : InstLoader.instance);
|
||||
}
|
||||
|
||||
private static final Char2ShortOpenHashMap MCFONT_ASCII_MAP = new Char2ShortOpenHashMap();
|
||||
ResourceLocation locationFontTexture = null;
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
public int[] charWidth;
|
||||
|
||||
static {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return MCFONT_ASCII_MAP.containsKey(chr);
|
||||
}
|
||||
|
||||
public char getRandomReplacement(char chr){
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
if (lutIndex != -1) {
|
||||
int randomReplacementIndex;
|
||||
do {
|
||||
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
|
||||
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
|
||||
|
||||
lutIndex = randomReplacementIndex;
|
||||
return MCFONT_CHARS.charAt(lutIndex);
|
||||
}
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return ((lutIndex % 16) * 8) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return (float) ((lutIndex / 16) * 8) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return charWidth[lutIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return charWidth[lutIndex] - 0.01F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return (charWidth[lutIndex] - 1.01F) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return 7.99F / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
return locationFontTexture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
private FontProviderUnicode() {}
|
||||
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 ResourceLocation getUnicodePageLocation(int page) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
if (lookup == null) {
|
||||
final ResourceLocation rl = new ResourceLocation(String.format(
|
||||
"textures/font/unicode_page_%02x.png",
|
||||
page));
|
||||
unicodePageLocations[page] = rl;
|
||||
return rl;
|
||||
} else {
|
||||
return lookup;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
|
||||
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return ((float) ((chr & 255) / 16 * 16) + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
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);
|
||||
return (endColumnF - startColumnF) / 2.0F + 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
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);
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
return chrWidth / 2.0f + 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
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);
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
return (chrWidth - 0.42f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return (16.0f - 0.42f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
final int uniPage = chr / 256;
|
||||
return getUnicodePageLocation(uniPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ResourceAccessor;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.resources.DefaultResourcePack;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FontStrategist {
|
||||
|
||||
@Getter
|
||||
private static final Font[] availableFonts;
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
static {
|
||||
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(", ");
|
||||
}
|
||||
}
|
||||
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<>();
|
||||
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
|
||||
packMap.put(FontProviderCustom.getPrimary().getAtlasResourceName(i), new File(FontProviderCustom.getPrimary().getAtlasFullPath(i)));
|
||||
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
|
||||
}
|
||||
|
||||
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
|
||||
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
|
||||
defaultResourcePacks.add(fontResourcePack);
|
||||
Minecraft.getMinecraft().refreshResources();
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether or not
|
||||
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
|
||||
*/
|
||||
public static FontProvider getFontProvider(BatchingFontRenderer me, char chr, boolean customFontEnabled, boolean forceUnicode) {
|
||||
if (me.isSGA && FontProviderMC.get(true).isGlyphAvailable(chr)) {
|
||||
return FontProviderMC.get(true);
|
||||
}
|
||||
if (customFontEnabled && !me.isSplash) {
|
||||
FontProvider fp;
|
||||
fp = FontProviderCustom.getPrimary();
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
fp = FontProviderCustom.getFallback();
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
return FontProviderUnicode.get();
|
||||
} else {
|
||||
if (!forceUnicode && FontProviderMC.get(false).isGlyphAvailable(chr)) {
|
||||
return FontProviderMC.get(false);
|
||||
} else {
|
||||
return FontProviderUnicode.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void reloadCustomFontProviders() {
|
||||
FontProviderCustom.getPrimary().setFont(null);
|
||||
FontProviderCustom.getFallback().setFont(null);
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(FontConfig.customFontNamePrimary, availableFonts[i].getFontName())) {
|
||||
FontProviderCustom.getPrimary().reloadFont(i);
|
||||
}
|
||||
if (Objects.equals(FontConfig.customFontNameFallback, availableFonts[i].getFontName())) {
|
||||
FontProviderCustom.getFallback().reloadFont(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSplashFontRendererActive(FontRenderer fontRenderer) {
|
||||
// noinspection deprecation
|
||||
boolean active = fontRenderer instanceof SplashProgress.SplashFontRenderer;
|
||||
|
||||
try {
|
||||
Class<?> customSplashClass = Class.forName("gkappa.modernsplash.CustomSplash$SplashFontRenderer");
|
||||
active = active || customSplashClass.isInstance(fontRenderer);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigException;
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
|
||||
import com.gtnewhorizons.angelica.client.font.FontStrategist;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.coderbot.iris.gui.element.widget.IrisButton;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
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;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FontConfigScreen extends GuiScreen {
|
||||
|
||||
private static final Font[] availableFonts = FontStrategist.getAvailableFonts();
|
||||
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;
|
||||
private int selectedPrimaryFontListPos = -1;
|
||||
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;
|
||||
this.displayedFonts = new ArrayList<>(Arrays.asList(availableFonts));
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(this.currentPrimaryFontName, availableFonts[i].getFontName())) {
|
||||
selectedPrimaryFontListPos = i;
|
||||
}
|
||||
if (Objects.equals(this.currentFallbackFontName, availableFonts[i].getFontName())) {
|
||||
selectedFallbackFontListPos = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
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(x2 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
I18n.format("options.angelica.fontconfig.reset_values"), this::resetValues));
|
||||
this.buttonList.add(new IrisButton(x3 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
I18n.format("gui.done"), button -> this.onClose()));
|
||||
|
||||
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() {
|
||||
applyChanges(true);
|
||||
this.mc.displayGuiScreen(parent);
|
||||
}
|
||||
|
||||
private void applyChanges(boolean finalApply) {
|
||||
int pos;
|
||||
pos = selectedPrimaryFontListPos;
|
||||
if (pos >= 0 && pos < displayedFonts.size()) {
|
||||
FontConfig.customFontNamePrimary = displayedFonts.get(pos).getFontName();
|
||||
}
|
||||
pos = selectedFallbackFontListPos;
|
||||
if (pos >= 0 && pos < displayedFonts.size()) {
|
||||
FontConfig.customFontNameFallback = displayedFonts.get(pos).getFontName();
|
||||
}
|
||||
|
||||
FontStrategist.reloadCustomFontProviders();
|
||||
|
||||
if (finalApply) {
|
||||
ConfigurationManager.save(FontConfig.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetValues(IrisButton button) {
|
||||
try {
|
||||
ConfigurationManager.getConfigElements(FontConfig.class).forEach(elem -> elem.set(elem.getDefault()));
|
||||
} catch (ConfigException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
currentPrimaryFontName = null;
|
||||
currentFallbackFontName = null;
|
||||
selectedPrimaryFontListPos = -1;
|
||||
selectedFallbackFontListPos = -1;
|
||||
super.buttonList.clear();
|
||||
this.initButtons();
|
||||
}
|
||||
|
||||
private void toggleCustomFont(IrisButton button) {
|
||||
FontConfig.enableCustomFont = !FontConfig.enableCustomFont;
|
||||
applyChanges(false);
|
||||
button.displayString = FontConfig.enableCustomFont ?
|
||||
I18n.format("options.angelica.fontconfig.disable_custom_font") :
|
||||
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) {
|
||||
FontConfig.customFontQuality = (int) quality;
|
||||
if (qualityLast != (int) quality) {
|
||||
applyChanges(false);
|
||||
}
|
||||
qualityLast = (int) quality;
|
||||
}
|
||||
|
||||
private String fontAAModeFormat(float AAmode) {
|
||||
return switch ((int) AAmode) {
|
||||
case 2 -> I18n.format("options.angelica.fontconfig.aamode.aa_16x");
|
||||
case 1 -> I18n.format("options.angelica.fontconfig.aamode.aa_4x");
|
||||
default -> I18n.format("options.angelica.fontconfig.aamode.aa_none");
|
||||
};
|
||||
}
|
||||
|
||||
private float lastMouseX = 0;
|
||||
private float lastMouseY = 0;
|
||||
private long lastStillTime = 0;
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float delta) {
|
||||
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,
|
||||
this.searchBox.yPosition + this.searchBox.height / 2 - 4, 0xFFFFFF);
|
||||
}
|
||||
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) {
|
||||
if (!(guiButton instanceof SliderClone slider)) { continue; }
|
||||
final int top = slider.yPosition;
|
||||
final int bot = slider.yPosition + slider.height;
|
||||
final int left = slider.xPosition;
|
||||
final int right = slider.xPosition + slider.width;
|
||||
if (mouseY < top || mouseY >= bot || mouseX < left || mouseX >= right) { continue; }
|
||||
if (mouseX == lastMouseX && mouseY == lastMouseY) {
|
||||
if (lastStillTime == 0) {
|
||||
lastStillTime = System.currentTimeMillis();
|
||||
}
|
||||
if (lastStillTime + 500L < System.currentTimeMillis()) {
|
||||
displayTooltip(mouseX, mouseY, slider.tooltipKey);
|
||||
}
|
||||
} else {
|
||||
lastStillTime = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
lastMouseX = mouseX;
|
||||
lastMouseY = mouseY;
|
||||
}
|
||||
|
||||
private void displayTooltip(int x, int y, String langKey) {
|
||||
List<String> lines = this.fontRendererObj.listFormattedStringToWidth(I18n.format(langKey), this.width / 2);
|
||||
this.drawHoveringText(lines, x, y, this.fontRendererObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton guiButton) {
|
||||
if (guiButton.enabled) {
|
||||
if (guiButton instanceof IrisButton irisButton) {
|
||||
irisButton.onPress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void keyTyped(char typedChar, int keyCode) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
private ArrayList<Font> filterFonts(String search) {
|
||||
ArrayList<Font> results;
|
||||
if (search == null || search.isEmpty()) {
|
||||
results = new ArrayList<>(Arrays.asList(availableFonts));
|
||||
} else {
|
||||
results = Arrays.stream(availableFonts).filter((font -> font.getFontName().toLowerCase().contains(search))).collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
selectedPrimaryFontListPos = -1;
|
||||
selectedFallbackFontListPos = -1;
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
if (Objects.equals(currentPrimaryFontName, results.get(i).getFontName())) {
|
||||
selectedPrimaryFontListPos = i;
|
||||
}
|
||||
if (Objects.equals(currentFallbackFontName, results.get(i).getFontName())) {
|
||||
selectedFallbackFontListPos = i;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
class FontList extends GuiSlot {
|
||||
|
||||
public FontList() {
|
||||
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) {
|
||||
if (mouseY < this.top || mouseY > this.bottom) { return; }
|
||||
|
||||
int listLeftBound = this.width / 2 - this.getListWidth() / 2;
|
||||
int listRightBound = this.width / 2 + this.getListWidth() / 2;
|
||||
if (mouseX < listLeftBound || mouseX > listRightBound) { return; }
|
||||
|
||||
int mousePosInList = mouseY - this.top - this.headerPadding + (int)this.amountScrolled - 4;
|
||||
int slotIndex = mousePosInList / this.slotHeight;
|
||||
if (slotIndex < 0 || slotIndex >= this.getSize()) { return; }
|
||||
|
||||
if (mouseButton == 0) {
|
||||
onElemClicked(slotIndex, false);
|
||||
} else if (mouseButton == 1) {
|
||||
onElemClicked(slotIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onElemClicked(int index, boolean rightClick) {
|
||||
if (!rightClick) {
|
||||
selectedPrimaryFontListPos = index;
|
||||
currentPrimaryFontName = displayedFonts.get(index).getFontName();
|
||||
} else {
|
||||
selectedFallbackFontListPos = index;
|
||||
currentFallbackFontName = displayedFonts.get(index).getFontName();
|
||||
}
|
||||
applyChanges(false);
|
||||
}
|
||||
|
||||
protected boolean isSelected(int index) {
|
||||
return (index == selectedPrimaryFontListPos || index == selectedFallbackFontListPos);
|
||||
}
|
||||
|
||||
protected int getSize() {
|
||||
return displayedFonts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentHeight() {
|
||||
return this.getSize() * 18;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getScrollBarX() {
|
||||
return this.width * 15 / 16 - 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getListWidth() {
|
||||
return this.width * 2 / 3;
|
||||
}
|
||||
|
||||
// see onElemClicked for the proper version that supports right-clicking
|
||||
protected void elementClicked(int index, boolean doubleClicked, int mouseX, int mouseY) {}
|
||||
|
||||
protected void drawBackground() {
|
||||
// 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_) {
|
||||
int color = 0xffffff;
|
||||
if (index == selectedPrimaryFontListPos) {
|
||||
color &= 0xffff55;
|
||||
}
|
||||
if (index == selectedFallbackFontListPos) {
|
||||
color &= 0x55ffff;
|
||||
}
|
||||
drawCenteredString(fontRendererObj, displayedFonts.get(index).getFontName(), this.width / 2, y + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SliderClone extends GuiButton {
|
||||
|
||||
private final Function<Float, String> displayStringFormatter;
|
||||
private final Consumer<Float> valueSetter;
|
||||
private final Option option;
|
||||
private float sliderPercentage;
|
||||
public final String tooltipKey;
|
||||
public boolean mouseDragging;
|
||||
|
||||
@AllArgsConstructor
|
||||
public static class Option {
|
||||
float min;
|
||||
float max;
|
||||
float step;
|
||||
|
||||
float denormalize(float alpha) {
|
||||
float lerp = min * (1 - alpha) + max * alpha;
|
||||
return Math.round(lerp / step) * step;
|
||||
}
|
||||
float normalize(float value) {
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
}
|
||||
|
||||
public SliderClone(int x, int y, int width, int height, Option option, float initialValue,
|
||||
Consumer<Float> setter, Function<Float, String> formatter, String tooltipKey) {
|
||||
super(0, x, y, width, height, "");
|
||||
this.option = option;
|
||||
this.sliderPercentage = option.normalize(initialValue);
|
||||
this.displayString = formatter.apply(initialValue);
|
||||
this.valueSetter = setter;
|
||||
this.displayStringFormatter = formatter;
|
||||
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.
|
||||
*/
|
||||
public int getHoverState(boolean mouseOver) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
|
||||
*/
|
||||
protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) {
|
||||
if (this.visible) {
|
||||
if (this.mouseDragging) {
|
||||
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
|
||||
|
||||
if (this.sliderPercentage < 0.0F) {
|
||||
this.sliderPercentage = 0.0F;
|
||||
}
|
||||
|
||||
if (this.sliderPercentage > 1.0F) {
|
||||
this.sliderPercentage = 1.0F;
|
||||
}
|
||||
|
||||
float f = this.option.denormalize(this.sliderPercentage);
|
||||
valueSetter.accept(f);
|
||||
this.sliderPercentage = this.option.normalize(f);
|
||||
this.displayString = displayStringFormatter.apply(f);
|
||||
}
|
||||
|
||||
GL11.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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent e).
|
||||
*/
|
||||
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
|
||||
if (super.mousePressed(mc, mouseX, mouseY)) {
|
||||
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
|
||||
|
||||
if (this.sliderPercentage < 0.0F) {
|
||||
this.sliderPercentage = 0.0F;
|
||||
}
|
||||
|
||||
if (this.sliderPercentage > 1.0F) {
|
||||
this.sliderPercentage = 1.0F;
|
||||
}
|
||||
|
||||
float f = this.option.denormalize(this.sliderPercentage);
|
||||
valueSetter.accept(f);
|
||||
this.sliderPercentage = this.option.normalize(f);
|
||||
this.displayString = displayStringFormatter.apply(f);
|
||||
this.mouseDragging = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
|
||||
*/
|
||||
public void mouseReleased(int mouseX, int mouseY) {
|
||||
this.mouseDragging = false;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,7 @@ public class BlockError extends Block {
|
||||
public BlockError() {
|
||||
super(Material.rock);
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void onTextureStitch(TextureStitchEvent.Pre event) {
|
||||
if (event.map.getTextureType() == 0) {
|
||||
for (int i = 0; i < icons.length; i++) {
|
||||
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
|
||||
}
|
||||
}
|
||||
MinecraftForge.EVENT_BUS.register(new EventHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,4 +35,15 @@ public class BlockError extends Block {
|
||||
return meta >= 0 && meta < icons.length ? icons[meta] : null;
|
||||
}
|
||||
|
||||
public static class EventHandler{
|
||||
@SubscribeEvent
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void onTextureStitch(TextureStitchEvent.Pre event) {
|
||||
if (event.map.getTextureType() == 0) {
|
||||
for (int i = 0; i < icons.length; i++) {
|
||||
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadProvider;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.util.DirectionUtil;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ModeledBlock;
|
||||
import com.gtnewhorizons.angelica.utils.AssetLoader;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import static com.gtnewhorizon.gtnhlib.client.model.ModelISBRH.JSON_ISBRH_ID;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.client.renderer.texture.IIconRegister;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.gtnewhorizons.angelica.models.VanillaModels.LECTERN;
|
||||
|
||||
public class BlockTest extends Block implements ModeledBlock {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private QuadProvider model;
|
||||
/// Unline {@link BlockError}, this *is* registered on both sides. It's intended to test functionality in a SP
|
||||
/// development environment.
|
||||
public class BlockTest extends Block {
|
||||
|
||||
public BlockTest() {
|
||||
|
||||
super(Material.rock);
|
||||
this.setBlockTextureName("missingno");
|
||||
this.setModel((world, pos, block, meta, dir, random, color, quadPool) -> {
|
||||
if (meta < 2 || meta > 5) meta = 2;
|
||||
return LECTERN.models[meta - 2].getQuads(world, pos, block, meta, dir, random, color, quadPool);
|
||||
});
|
||||
super(Material.wood);
|
||||
setHardness(0.7f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,13 +22,6 @@ public class BlockTest extends Block implements ModeledBlock {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBlockIcons(IIconRegister reg) {
|
||||
for (String s : AssetLoader.testTexs) {
|
||||
reg.registerIcon(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a block is placed using its ItemBlock. Args: World, X, Y, Z, side, hitX, hitY, hitZ, block metadata
|
||||
*/
|
||||
@@ -50,11 +29,16 @@ public class BlockTest extends Block implements ModeledBlock {
|
||||
public int onBlockPlaced(@NotNull World worldIn, int x, int y, int z, int side, float subX, float subY, float subZ, int meta) {
|
||||
|
||||
// Face NORTH if placed up or down
|
||||
final ForgeDirection s = DirectionUtil.ALL_DIRECTIONS[side];
|
||||
final var s = ForgeDirection.getOrientation(side);
|
||||
if (s == ForgeDirection.UP || s == ForgeDirection.DOWN)
|
||||
return 2;
|
||||
|
||||
// Face the placed side
|
||||
return side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRenderType() {
|
||||
return JSON_ISBRH_ID;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import com.falsepattern.chunk.api.DataRegistry;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedBlockStorageAccessor;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedNibbleArray;
|
||||
import com.gtnewhorizons.neid.mixins.interfaces.IExtendedBlockStorageMixin;
|
||||
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
@@ -19,7 +17,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
}
|
||||
|
||||
public ExtendedBlockStorageExt(Chunk chunk, ExtendedBlockStorage storage) {
|
||||
super(((ExtendedBlockStorageAccessor) storage).getYBase(), storage.getSkylightArray() != null);
|
||||
super(storage.yBase, storage.getSkylightArray() != null);
|
||||
|
||||
if (ModStatus.isChunkAPILoaded) {
|
||||
if (storage.getSkylightArray() != null) {
|
||||
@@ -33,7 +31,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BArray(), 0, block16BArray, 0, block16BArray.length);
|
||||
if(storage.getBlockMSBArray() != null) {
|
||||
this.setBlockMSBArray(new NibbleArray(block16BArray.length, 4));
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
|
||||
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
|
||||
}
|
||||
arrayLen = block16BArray.length;
|
||||
if (ModStatus.isNEIDMetadataExtended) {
|
||||
@@ -52,31 +50,31 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
|
||||
if(storage.getBlockMSBArray() != null) {
|
||||
this.setBlockMSBArray(new NibbleArray(blockLSBArray.length, 4));
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
|
||||
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
|
||||
}
|
||||
arrayLen = blockLSBArray.length;
|
||||
}
|
||||
|
||||
|
||||
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray((ExtendedNibbleArray) storage.getMetadataArray(), (ExtendedNibbleArray)this.getMetadataArray());
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlocklightArray(), (ExtendedNibbleArray)this.getBlocklightArray());
|
||||
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray(storage.getMetadataArray(), this.getMetadataArray());
|
||||
copyNibbleArray(storage.getBlocklightArray(), this.getBlocklightArray());
|
||||
if(storage.getSkylightArray() != null) {
|
||||
hasSky = true;
|
||||
if(this.getSkylightArray() == null) {
|
||||
this.setSkylightArray(new NibbleArray(arrayLen, 4));
|
||||
}
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getSkylightArray(), (ExtendedNibbleArray) this.getSkylightArray());
|
||||
copyNibbleArray(storage.getSkylightArray(), this.getSkylightArray());
|
||||
}
|
||||
}
|
||||
((ExtendedBlockStorageAccessor) this).setBlockRefCount(((ExtendedBlockStorageAccessor) storage).getBlockRefCount());
|
||||
this.blockRefCount = storage.blockRefCount;
|
||||
}
|
||||
|
||||
|
||||
private static void copyNibbleArray(ExtendedNibbleArray srcArray, ExtendedNibbleArray dstArray) {
|
||||
private static void copyNibbleArray(NibbleArray srcArray, NibbleArray dstArray) {
|
||||
if (srcArray == null || dstArray == null) {
|
||||
throw new RuntimeException("NibbleArray is null src: " + (srcArray == null) + " dst: " + (dstArray == null));
|
||||
}
|
||||
final byte[] data = srcArray.getData();
|
||||
System.arraycopy(data, 0, dstArray.getData(), 0, data.length);
|
||||
final byte[] data = srcArray.data;
|
||||
System.arraycopy(data, 0, dstArray.data, 0, data.length);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.versioning.ArtifactVersion;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import mods.battlegear2.Battlegear;
|
||||
import net.dries007.holoInventory.HoloInventory;
|
||||
import xonin.backhand.Backhand;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class ModStatus {
|
||||
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
|
||||
|
||||
public static BackhandReflectionCompat backhandCompat;
|
||||
/**
|
||||
* Mixin Version
|
||||
*/
|
||||
@@ -17,7 +20,6 @@ public class ModStatus {
|
||||
* ASM Version
|
||||
*/
|
||||
public static boolean isOldNEIDLoaded;
|
||||
|
||||
public static boolean isBetterCrashesLoaded;
|
||||
public static boolean isNEIDMetadataExtended;
|
||||
public static boolean isLotrLoaded;
|
||||
@@ -30,8 +32,14 @@ public class ModStatus {
|
||||
public static boolean isThaumcraftLoaded;
|
||||
public static boolean isThaumicHorizonsLoaded;
|
||||
public static boolean isBaublesLoaded;
|
||||
public static boolean isFluidLoggedLoaded;
|
||||
|
||||
public static void preInit(){
|
||||
isBackhandLoaded = Loader.isModLoaded("backhand");
|
||||
if(isBackhandLoaded) {
|
||||
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
|
||||
}
|
||||
|
||||
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
|
||||
isNEIDLoaded = Loader.isModLoaded("neid");
|
||||
isOldNEIDLoaded = Loader.isModLoaded("notenoughIDs");
|
||||
@@ -41,17 +49,12 @@ public class ModStatus {
|
||||
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
|
||||
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
|
||||
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
|
||||
isBackhandLoaded = Loader.isModLoaded("backhand");
|
||||
isThaumcraftLoaded = Loader.isModLoaded("Thaumcraft");
|
||||
isThaumicHorizonsLoaded = Loader.isModLoaded("ThaumicHorizons");
|
||||
isBaublesLoaded = Loader.isModLoaded("Baubles");
|
||||
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
|
||||
|
||||
if (isHoloInventoryLoaded){
|
||||
isHoloInventoryLoaded = new DefaultArtifactVersion("2.4.4-GTNH")
|
||||
.compareTo(
|
||||
LoadControllerHelper.getOwningMod(HoloInventory.class).getProcessedVersion()
|
||||
) <= 0;
|
||||
}
|
||||
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
|
||||
|
||||
// remove compat with original release of BG2
|
||||
if (isBattlegearLoaded){
|
||||
@@ -61,17 +64,9 @@ public class ModStatus {
|
||||
) <= 0;
|
||||
}
|
||||
|
||||
if (isBackhandLoaded){
|
||||
isBackhandLoaded = new DefaultArtifactVersion("1.4.1")
|
||||
.compareTo(
|
||||
LoadControllerHelper.getOwningMod(Backhand.class).getProcessedVersion()
|
||||
) <= 0;
|
||||
}
|
||||
|
||||
|
||||
isNEIDMetadataExtended = false;
|
||||
if (isNEIDLoaded) {
|
||||
int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
|
||||
final int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
|
||||
if (majorVersion >= 2) {
|
||||
isNEIDMetadataExtended = true;
|
||||
}
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.gtnewhorizons.angelica.compat.backhand;
|
||||
|
||||
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
|
||||
|
||||
public class BackhandReflectionCompat {
|
||||
private static final String BACKHAND_CLASS = "xonin.backhand.Backhand";
|
||||
private static final String ITEM_RENDERER_HOOKS_CLASS = "xonin.backhand.client.hooks.ItemRendererHooks";
|
||||
private static final String BACKHAND_UTILS_CLASS = "xonin.backhand.api.core.BackhandUtils";
|
||||
|
||||
private static boolean isLoaded;
|
||||
private static final MethodHandle renderOffhandReturn;
|
||||
private static final MethodHandle getOffhandItem;
|
||||
|
||||
static {
|
||||
MethodHandle renderOffhandReturnTemp = null;
|
||||
MethodHandle getOffhandItemTemp = null;
|
||||
|
||||
try {
|
||||
final Class<?> backhandClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_CLASS);
|
||||
final boolean versionCheck = new DefaultArtifactVersion("1.6.9").compareTo(LoadControllerHelper.getOwningMod(backhandClass).getProcessedVersion()) <= 0;
|
||||
|
||||
if (versionCheck) {
|
||||
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
final Class<?> itemRendererHooksClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), ITEM_RENDERER_HOOKS_CLASS);
|
||||
final Class<?> backhandUtilsClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_UTILS_CLASS);
|
||||
|
||||
renderOffhandReturnTemp = lookup.findStatic(itemRendererHooksClass, "renderOffhandReturn", MethodType.methodType(void.class, float.class));
|
||||
getOffhandItemTemp = lookup.findStatic(backhandUtilsClass, "getOffhandItem", MethodType.methodType(ItemStack.class, EntityPlayer.class));
|
||||
isLoaded = true;
|
||||
LOGGER.info("Backhand compat loaded");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load Backhand compat", e);
|
||||
isLoaded = false;
|
||||
}
|
||||
|
||||
renderOffhandReturn = renderOffhandReturnTemp;
|
||||
getOffhandItem = getOffhandItemTemp;
|
||||
}
|
||||
|
||||
public static boolean isBackhandLoaded() {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
public static void renderOffhand(float partialTicks) {
|
||||
if (isLoaded && renderOffhandReturn != null) {
|
||||
try {
|
||||
renderOffhandReturn.invokeExact(partialTicks);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack getOffhandItem(EntityPlayer player) {
|
||||
if (isLoaded && getOffhandItem != null) {
|
||||
try {
|
||||
return (ItemStack) getOffhandItem.invokeExact(player);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.compat.battlegear2;
|
||||
|
||||
import mods.battlegear2.api.core.IInventoryPlayerBattle;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* This class exists to isolate classes that exist in Battlegear 2 but not
|
||||
* in Battlegear 2 for Backhand in order to avoid class loading exceptions.
|
||||
*/
|
||||
public class Battlegear2Compat {
|
||||
public static ItemStack getBattlegear2Offhand(EntityPlayer player) {
|
||||
return ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
|
||||
}
|
||||
}
|
||||
+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();
|
||||
}
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.gtnewhorizons.angelica.compat.holoinventory;
|
||||
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
|
||||
|
||||
public class HoloInventoryReflectionCompat {
|
||||
private static final String RENDERER_CLASS = "net.dries007.holoInventory.client.Renderer";
|
||||
|
||||
private static boolean isLoaded;
|
||||
private static final MethodHandle renderEventPostMethod;
|
||||
private static final MethodHandle renderEventMethod;
|
||||
private static final MethodHandle angelicaOverrideSetter;
|
||||
|
||||
static {
|
||||
MethodHandle renderEventPostMethodTemp = null;
|
||||
MethodHandle renderEventMethodTemp = null;
|
||||
MethodHandle angelicaOverrideSetterTemp = null;
|
||||
final Object rendererInstance;
|
||||
final Class<?> rendererClass;
|
||||
|
||||
try {
|
||||
rendererClass = Class.forName(RENDERER_CLASS);
|
||||
|
||||
// Get and cache the Renderer.INSTANCE
|
||||
final Field instanceField = rendererClass.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
rendererInstance = instanceField.get(null);
|
||||
|
||||
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
try {
|
||||
renderEventPostMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.Post.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Fallback for 2.5.0-GTNH and earlier
|
||||
renderEventMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.class));
|
||||
}
|
||||
|
||||
final Field angelicaOverrideField = rendererClass.getDeclaredField("angelicaOverride");
|
||||
angelicaOverrideField.setAccessible(true);
|
||||
angelicaOverrideSetterTemp = lookup.unreflectSetter(angelicaOverrideField);
|
||||
|
||||
// Bind the method handles to the instance
|
||||
if (rendererInstance != null) {
|
||||
if (renderEventPostMethodTemp != null) {
|
||||
renderEventPostMethodTemp = renderEventPostMethodTemp.bindTo(rendererInstance);
|
||||
}
|
||||
if (renderEventMethodTemp != null) {
|
||||
renderEventMethodTemp = renderEventMethodTemp.bindTo(rendererInstance);
|
||||
}
|
||||
angelicaOverrideSetterTemp = angelicaOverrideSetterTemp.bindTo(rendererInstance);
|
||||
}
|
||||
|
||||
isLoaded = true;
|
||||
LOGGER.info("Successfully initialized HoloInventory compatibility layer");
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to initialize HoloInventory compatibility layer", e);
|
||||
isLoaded = false;
|
||||
}
|
||||
|
||||
renderEventPostMethod = renderEventPostMethodTemp;
|
||||
renderEventMethod = renderEventMethodTemp;
|
||||
angelicaOverrideSetter = angelicaOverrideSetterTemp;
|
||||
}
|
||||
|
||||
public static void renderEvent(RenderGameOverlayEvent event) {
|
||||
if (!isLoaded) return;
|
||||
try {
|
||||
if (event instanceof RenderGameOverlayEvent.Post && renderEventPostMethod != null) {
|
||||
renderEventPostMethod.invokeExact((RenderGameOverlayEvent.Post) event);
|
||||
} else {
|
||||
renderEventMethod.invokeExact(event);
|
||||
}
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAngelicaOverride(boolean value) {
|
||||
if (!isLoaded) return;
|
||||
try {
|
||||
angelicaOverrideSetter.invokeExact(value);
|
||||
} 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,102 @@
|
||||
package com.gtnewhorizons.angelica.compat.iris;
|
||||
|
||||
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)
|
||||
*/
|
||||
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 synchronized void initialize() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
initializeBOP();
|
||||
initializeRWG();
|
||||
}
|
||||
|
||||
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 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
|
||||
for (Map.Entry<Class<?>, BiomeCategories> entry : rwgClassMap.entrySet()) {
|
||||
if (entry.getKey().isInstance(biome)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.lwjgl;
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.Pointer.BITS64;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongPredicate;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
/**
|
||||
* Backported from LWJGL3 under the BSD 3-Clause "New" or "Revised" License license
|
||||
*
|
||||
* <p>This class provides functionality for managing native memory.
|
||||
*
|
||||
* <p>All methods in this class will make use of {@link sun.misc.Unsafe} if it's available, for performance. If Unsafe is not available, the fallback
|
||||
* implementations make use of reflection and, in the worst-case, JNI.</p>
|
||||
*
|
||||
* <p>Method names in this class are prefixed with {@code mem} to avoid ambiguities when used with static imports.</p>
|
||||
*/
|
||||
public class CompatMemoryUtil {
|
||||
public static final long NULL = 0;
|
||||
|
||||
static final sun.misc.Unsafe UNSAFE;
|
||||
|
||||
private static final Class<? extends ByteBuffer> BUFFER_BYTE;
|
||||
private static final Class<? extends IntBuffer> BUFFER_INT;
|
||||
private static final Class<? extends FloatBuffer> BUFFER_FLOAT;
|
||||
|
||||
private static final long MARK;
|
||||
private static final long POSITION;
|
||||
private static final long LIMIT;
|
||||
private static final long CAPACITY;
|
||||
|
||||
private static final long ADDRESS;
|
||||
|
||||
static {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder());
|
||||
BUFFER_BYTE = bb.getClass();
|
||||
BUFFER_INT = bb.asIntBuffer().getClass();
|
||||
BUFFER_FLOAT = bb.asFloatBuffer().getClass();
|
||||
|
||||
UNSAFE = getUnsafeInstance();
|
||||
|
||||
MARK = getMarkOffset();
|
||||
POSITION = getPositionOffset();
|
||||
LIMIT = getLimitOffset();
|
||||
CAPACITY = getCapacityOffset();
|
||||
|
||||
ADDRESS = getAddressOffset();
|
||||
}
|
||||
|
||||
public static ByteBuffer memReallocDirect(ByteBuffer old, int capacity) {
|
||||
ByteBuffer newBuf = BufferUtils.createByteBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
public static IntBuffer memReallocDirect(IntBuffer old, int capacity) {
|
||||
IntBuffer newBuf = BufferUtils.createIntBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
public static FloatBuffer memReallocDirect(FloatBuffer old, int capacity) {
|
||||
FloatBuffer newBuf = BufferUtils.createFloatBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
-------------------------------------
|
||||
UNSAFE MEMORY ACCESS API
|
||||
-------------------------------------
|
||||
------------------------------------- */
|
||||
|
||||
private static final int FILL_PATTERN_32 = Integer.divideUnsigned(-1, 255);
|
||||
private static final long FILL_PATTERN_64 = Long.divideUnsigned(-1L, 255L);
|
||||
|
||||
/**
|
||||
* Sets all bytes in a specified block of memory to a fixed value (usually zero).
|
||||
*
|
||||
* @param ptr the starting memory address
|
||||
* @param value the value to set (memSet will convert it to unsigned byte)
|
||||
* @param bytes the number of bytes to set
|
||||
*/
|
||||
public static void memSet(long ptr, int value, long bytes) {
|
||||
if (/*DEBUG*/ false && (ptr == NULL || bytes < 0)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
/*
|
||||
- Unsafe.setMemory is very slow.
|
||||
- A custom Java loop is fastest at small sizes, approximately up to 256 bytes.
|
||||
- The native memset becomes fastest at bigger sizes, when the JNI overhead becomes negligible.
|
||||
TODO: verify this
|
||||
*/
|
||||
|
||||
//UNSAFE.setMemory(ptr, bytes, (byte)(value & 0xFF));
|
||||
//if (bytes < 256L) {
|
||||
int p = (int)ptr;
|
||||
if (BITS64) {
|
||||
if ((p & 7) == 0) {
|
||||
memSet64(ptr, value, (int)bytes & 0xFF);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if ((p & 3) == 0) {
|
||||
memSet32(p, value, (int)bytes & 0xFF);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//}
|
||||
//nmemset(ptr, value, bytes);
|
||||
}
|
||||
private static void memSet64(long ptr, int value, int bytes) {
|
||||
int aligned = bytes & ~7;
|
||||
|
||||
// Aligned body
|
||||
long valuel = (value & 0xFF) * FILL_PATTERN_64;
|
||||
for (int i = 0; i < aligned; i += 8) {
|
||||
UNSAFE.putLong(null, ptr + i, valuel);
|
||||
}
|
||||
|
||||
// Unaligned tail
|
||||
byte valueb = (byte)(value & 0xFF);
|
||||
for (int i = aligned; i < bytes; i++) {
|
||||
UNSAFE.putByte(null, ptr + i, valueb);
|
||||
}
|
||||
}
|
||||
private static void memSet32(int ptr, int value, int bytes) {
|
||||
int aligned = bytes & ~3;
|
||||
|
||||
// Aligned body
|
||||
int vi = (value & 0xFF) * FILL_PATTERN_32;
|
||||
for (int i = 0; i < aligned; i += 4) {
|
||||
UNSAFE.putInt(null, (ptr + i) & 0xFFFF_FFFFL, vi);
|
||||
}
|
||||
|
||||
// Unaligned tail
|
||||
byte vb = (byte)(value & 0xFF);
|
||||
for (int i = aligned; i < bytes; i++) {
|
||||
UNSAFE.putByte(null, (ptr + i) & 0xFFFF_FFFFL, vb);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean memGetBoolean(long ptr) { return UNSAFE.getByte(null, ptr) != 0; }
|
||||
public static byte memGetByte(long ptr) { return UNSAFE.getByte(null, ptr); }
|
||||
public static short memGetShort(long ptr) { return UNSAFE.getShort(null, ptr); }
|
||||
public static int memGetInt(long ptr) { return UNSAFE.getInt(null, ptr); }
|
||||
public static long memGetLong(long ptr) { return UNSAFE.getLong(null, ptr); }
|
||||
public static float memGetFloat(long ptr) { return UNSAFE.getFloat(null, ptr); }
|
||||
public static double memGetDouble(long ptr) { return UNSAFE.getDouble(null, ptr); }
|
||||
|
||||
public static void memPutByte(long ptr, byte value) { UNSAFE.putByte(null, ptr, value); }
|
||||
public static void memPutShort(long ptr, short value) { UNSAFE.putShort(null, ptr, value); }
|
||||
public static void memPutInt(long ptr, int value) { UNSAFE.putInt(null, ptr, value); }
|
||||
public static void memPutLong(long ptr, long value) { UNSAFE.putLong(null, ptr, value); }
|
||||
public static void memPutFloat(long ptr, float value) { UNSAFE.putFloat(null, ptr, value); }
|
||||
public static void memPutDouble(long ptr, double value) { UNSAFE.putDouble(null, ptr, value); }
|
||||
|
||||
// -------------------------------------------------
|
||||
// -------------------------------------------------
|
||||
// -------------------------------------------------
|
||||
|
||||
private static sun.misc.Unsafe getUnsafeInstance() {
|
||||
java.lang.reflect.Field[] fields = sun.misc.Unsafe.class.getDeclaredFields();
|
||||
|
||||
/*
|
||||
Different runtimes use different names for the Unsafe singleton,
|
||||
so we cannot use .getDeclaredField and we scan instead. For example:
|
||||
|
||||
Oracle: theUnsafe
|
||||
PERC : m_unsafe_instance
|
||||
Android: THE_ONE
|
||||
*/
|
||||
for (java.lang.reflect.Field field : fields) {
|
||||
if (!field.getType().equals(sun.misc.Unsafe.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int modifiers = field.getModifiers();
|
||||
if (!(java.lang.reflect.Modifier.isStatic(modifiers) && java.lang.reflect.Modifier.isFinal(modifiers))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
return (sun.misc.Unsafe)field.get(null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("LWJGL requires sun.misc.Unsafe to be available.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches an object and its superclasses for a field with the same name and type. If the field isn't
|
||||
* public, it may not be stable across library/JVM versions, and these calls are likely not portable. You have been
|
||||
* warned!
|
||||
*/
|
||||
private static long getFieldOffset(Class<?> containerType, Class<?> fieldType, String name) {
|
||||
Class<?> c = containerType;
|
||||
while (c != Object.class) {
|
||||
Field[] fields = c.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (!field.getType().isAssignableFrom(fieldType) || Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long offset = UNSAFE.objectFieldOffset(field);
|
||||
if (field.getName().equals(name)) {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
throw new UnsupportedOperationException("Failed to find field offset in class.");
|
||||
}
|
||||
|
||||
private static long getFieldOffset(Class<?> containerType, Class<?> fieldType, LongPredicate predicate) {
|
||||
Class<?> c = containerType;
|
||||
while (c != Object.class) {
|
||||
Field[] fields = c.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (!field.getType().isAssignableFrom(fieldType) || Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long offset = UNSAFE.objectFieldOffset(field);
|
||||
if (predicate.test(offset)) {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
throw new UnsupportedOperationException("Failed to find field offset in class.");
|
||||
}
|
||||
|
||||
private static long getFieldOffsetInt(Object container, int value) {
|
||||
return getFieldOffset(container.getClass(), int.class, offset -> UNSAFE.getInt(container, offset) == value);
|
||||
}
|
||||
|
||||
private static long getAddressOffset() {
|
||||
final ByteBuffer bb = ByteBuffer.allocateDirect(0);
|
||||
return getFieldOffset(bb.getClass(), long.class, "address");
|
||||
|
||||
// TODO: do this portably
|
||||
// long MAGIC_ADDRESS = 0xDEADBEEF8BADF00DL & (BITS32 ? 0xFFFF_FFFFL : 0xFFFF_FFFF_FFFF_FFFFL);
|
||||
// ByteBuffer bb = Objects.requireNonNull(NewDirectByteBuffer(MAGIC_ADDRESS, 0));
|
||||
//return getFieldOffset(bb.getClass(), long.class, offset -> UNSAFE.getLong(bb, offset) == MAGIC_ADDRESS);
|
||||
}
|
||||
|
||||
private static final int MAGIC_CAPACITY = 0x0D15EA5E;
|
||||
private static final int MAGIC_POSITION = 0x00FACADE;
|
||||
|
||||
private static long getMarkOffset() {
|
||||
// TODO: is this actually reliable without NewDirectByteBuffer?
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(0);
|
||||
return getFieldOffsetInt(bb, -1);
|
||||
// ByteBuffer bb = Objects.requireNonNull(NewDirectByteBuffer(1L, 0));
|
||||
// return getFieldOffsetInt(bb, -1);
|
||||
}
|
||||
|
||||
private static long getPositionOffset() {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
|
||||
bb.position(MAGIC_POSITION);
|
||||
return getFieldOffsetInt(bb, MAGIC_POSITION);
|
||||
}
|
||||
|
||||
private static long getLimitOffset() {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
|
||||
bb.limit(MAGIC_POSITION);
|
||||
return getFieldOffsetInt(bb, MAGIC_POSITION);
|
||||
}
|
||||
|
||||
private static long getCapacityOffset() {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
|
||||
bb.limit(0);
|
||||
return getFieldOffsetInt(bb, MAGIC_CAPACITY);
|
||||
}
|
||||
|
||||
static IntBuffer wrapBufferInt(long address, int capacity) {
|
||||
IntBuffer buffer;
|
||||
try {
|
||||
buffer = (IntBuffer)UNSAFE.allocateInstance(BUFFER_INT);
|
||||
} catch (InstantiationException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
|
||||
UNSAFE.putLong(buffer, ADDRESS, address);
|
||||
UNSAFE.putInt(buffer, MARK, -1);
|
||||
UNSAFE.putInt(buffer, LIMIT, capacity);
|
||||
UNSAFE.putInt(buffer, CAPACITY, capacity);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static FloatBuffer wrapBufferFloat(long address, int capacity) {
|
||||
FloatBuffer buffer;
|
||||
try {
|
||||
buffer = (FloatBuffer)UNSAFE.allocateInstance(BUFFER_FLOAT);
|
||||
} catch (InstantiationException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
|
||||
UNSAFE.putLong(buffer, ADDRESS, address);
|
||||
UNSAFE.putInt(buffer, MARK, -1);
|
||||
UNSAFE.putInt(buffer, LIMIT, capacity);
|
||||
UNSAFE.putInt(buffer, CAPACITY, capacity);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
/*
|
||||
* This class is backported from LWJGL3 under the BSD 3-clause "New" or "Revised" License
|
||||
*/
|
||||
|
||||
package com.gtnewhorizons.angelica.compat.lwjgl;
|
||||
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutDouble;
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutFloat;
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutInt;
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutLong;
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutShort;
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memSet;
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.wrapBufferFloat;
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.wrapBufferInt;
|
||||
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.MemoryUtil;
|
||||
|
||||
/**
|
||||
* An off-heap memory stack.
|
||||
*
|
||||
* <p>This class should be used in a thread-local manner for stack allocations.</p>
|
||||
*
|
||||
* @ see Configuration#STACK_SIZE
|
||||
* @ see Configuration#DEBUG_STACK
|
||||
*/
|
||||
@SuppressWarnings("LombokGetterMayBeUsed")
|
||||
public class MemoryStack extends Pointer.Default implements AutoCloseable {
|
||||
|
||||
private static final int DEFAULT_STACK_SIZE = /*Configuration.STACK_SIZE.get(64)*/ 64 * 1024;
|
||||
private static final int DEFAULT_STACK_FRAMES = 8;
|
||||
|
||||
private static final ThreadLocal<MemoryStack> TLS = ThreadLocal.withInitial(MemoryStack::create);
|
||||
|
||||
static {
|
||||
if (DEFAULT_STACK_SIZE < 0) {
|
||||
throw new IllegalStateException("Invalid stack size.");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private final @Nullable ByteBuffer container;
|
||||
|
||||
private final int size;
|
||||
|
||||
private int pointer;
|
||||
|
||||
private int[] frames;
|
||||
protected int frameIndex;
|
||||
|
||||
/**
|
||||
* Creates a new {@code MemoryStack} backed by the specified memory region.
|
||||
*
|
||||
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
|
||||
*
|
||||
* @param container the backing memory buffer, may be null
|
||||
* @param address the backing memory address
|
||||
* @param size the backing memory size
|
||||
*/
|
||||
protected MemoryStack(@Nullable ByteBuffer container, long address, int size) {
|
||||
super(address);
|
||||
this.container = container;
|
||||
|
||||
this.size = size;
|
||||
this.pointer = size;
|
||||
|
||||
this.frames = new int[DEFAULT_STACK_FRAMES];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code MemoryStack} with the default size.
|
||||
*
|
||||
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
|
||||
*/
|
||||
public static MemoryStack create() {
|
||||
return create(DEFAULT_STACK_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code MemoryStack} with the specified size.
|
||||
*
|
||||
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
|
||||
*
|
||||
* @param capacity the maximum number of bytes that may be allocated on the stack
|
||||
*/
|
||||
public static MemoryStack create(int capacity) {
|
||||
return create(BufferUtils.createByteBuffer(capacity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code MemoryStack} backed by the specified memory buffer.
|
||||
*
|
||||
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
|
||||
*
|
||||
* @param buffer the backing memory buffer
|
||||
*/
|
||||
public static MemoryStack create(ByteBuffer buffer) {
|
||||
long address = MemoryUtil.getAddress(buffer);
|
||||
int size = buffer.remaining();
|
||||
return new MemoryStack(buffer, address, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code MemoryStack} backed by the specified memory region.
|
||||
*
|
||||
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
|
||||
*
|
||||
* @param address the backing memory address
|
||||
* @param size the backing memory size
|
||||
*/
|
||||
public static MemoryStack ncreate(long address, int size) {
|
||||
return new MemoryStack(null, address, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the current stack pointer and pushes a new frame to the stack.
|
||||
*
|
||||
* <p>This method should be called when entering a method, before doing any stack allocations. When exiting a method, call the {@link #pop} method to
|
||||
* restore the previous stack frame.</p>
|
||||
*
|
||||
* <p>Pairs of push/pop calls may be nested. Care must be taken to:</p>
|
||||
* <ul>
|
||||
* <li>match every push with a pop</li>
|
||||
* <li>not call pop before push has been called at least once</li>
|
||||
* <li>not nest push calls to more than the maximum supported depth</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return this stack
|
||||
*/
|
||||
public MemoryStack push() {
|
||||
if (frameIndex == frames.length) {
|
||||
frameOverflow();
|
||||
}
|
||||
|
||||
frames[frameIndex++] = pointer;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void frameOverflow() {
|
||||
if (/*DEBUG*/ false) {
|
||||
AngelicaTweaker.LOGGER.warn("[WARNING] Out of frame stack space (" + frames.length + ") in thread: " + Thread.currentThread());
|
||||
}
|
||||
frames = Arrays.copyOf(frames, frames.length * 3 / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops the current stack frame and moves the stack pointer to the end of the previous stack frame.
|
||||
*
|
||||
* @return this stack
|
||||
*/
|
||||
public MemoryStack pop() {
|
||||
pointer = frames[--frameIndex];
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #pop} on this {@code MemoryStack}.
|
||||
*
|
||||
* <p>This method should not be used directly. It is called automatically when the {@code MemoryStack} is used as a resource in a try-with-resources
|
||||
* statement.</p>
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
//noinspection resource
|
||||
pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the backing off-heap memory.
|
||||
*
|
||||
* <p>The stack grows "downwards", so the bottom of the stack is at {@code address + size}, while the top is at {@code address}.</p>
|
||||
*/
|
||||
public long getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the backing off-heap memory.
|
||||
*
|
||||
* <p>This is the maximum number of bytes that may be allocated on the stack.</p>
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current frame index.
|
||||
*
|
||||
* <p>This is the current number of nested {@link #push} calls.</p>
|
||||
*/
|
||||
public int getFrameIndex() {
|
||||
return frameIndex;
|
||||
}
|
||||
|
||||
/** Returns the memory address at the current stack pointer. */
|
||||
public long getPointerAddress() {
|
||||
return address + (pointer & 0xFFFF_FFFFL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current stack pointer.
|
||||
*
|
||||
* <p>The stack grows "downwards", so when the stack is empty {@code pointer} is equal to {@code size}. On every allocation {@code pointer} is reduced by
|
||||
* the allocated size (after alignment) and {@code address + pointer} points to the first byte of the last allocation.</p>
|
||||
*
|
||||
* <p>Effectively, this methods returns how many more bytes may be allocated on the stack.</p>
|
||||
*/
|
||||
public int getPointer() {
|
||||
return pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current stack pointer.
|
||||
*
|
||||
* <p>This method directly manipulates the stack pointer. Using it irresponsibly may break the internal state of the stack. It should only be used in rare
|
||||
* cases or in auto-generated code.</p>
|
||||
*/
|
||||
public void setPointer(int pointer) {
|
||||
if (/*CHECKS*/ false) {
|
||||
checkPointer(pointer);
|
||||
}
|
||||
|
||||
this.pointer = pointer;
|
||||
}
|
||||
|
||||
private void checkPointer(int pointer) {
|
||||
if (pointer < 0 || size < pointer) {
|
||||
throw new IndexOutOfBoundsException("Invalid stack pointer");
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkAlignment(int alignment) {
|
||||
if (Integer.bitCount(alignment) != 1) {
|
||||
throw new IllegalArgumentException("Alignment must be a power-of-two value.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #nmalloc(int, int)} with {@code alignment} equal to {@link Pointer#POINTER_SIZE POINTER_SIZE}.
|
||||
*
|
||||
* @param size the allocation size
|
||||
*
|
||||
* @return the memory address on the stack for the requested allocation
|
||||
*/
|
||||
public long nmalloc(int size) {
|
||||
return nmalloc(POINTER_SIZE, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a block of {@code size} bytes of memory on the stack. The content of the newly allocated block of memory is not initialized, remaining with
|
||||
* indeterminate values.
|
||||
*
|
||||
* @param alignment the required alignment
|
||||
* @param size the allocation size
|
||||
*
|
||||
* @return the memory address on the stack for the requested allocation
|
||||
*/
|
||||
public long nmalloc(int alignment, int size) {
|
||||
// Align address to the specified alignment
|
||||
long address = (this.address + pointer - size) & ~Integer.toUnsignedLong(alignment - 1);
|
||||
|
||||
pointer = (int)(address - this.address);
|
||||
if (/*CHECKS*/ false && pointer < 0) {
|
||||
throw new OutOfMemoryError("Out of stack space.");
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a block of memory on the stack for an array of {@code num} elements, each of them {@code size} bytes long, and initializes all its bits to
|
||||
* zero.
|
||||
*
|
||||
* @param alignment the required element alignment
|
||||
* @param num num the number of elements to allocate
|
||||
* @param size the size of each element
|
||||
*
|
||||
* @return the memory address on the stack for the requested allocation
|
||||
*/
|
||||
public long ncalloc(int alignment, int num, int size) {
|
||||
int bytes = num * size;
|
||||
long address = nmalloc(alignment, bytes);
|
||||
memSet(address, 0, bytes);
|
||||
return address;
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
|
||||
/** Unsafe version of {@link #shorts(short)}. */
|
||||
public long nshort(short value) {
|
||||
long a = nmalloc(2, 2);
|
||||
memPutShort(a, value);
|
||||
return a;
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
|
||||
/** Int version of {@link #malloc(int)}. */
|
||||
public IntBuffer mallocInt(int size) { return wrapBufferInt(nmalloc(4, size << 2), size); }
|
||||
|
||||
/** Unsafe version of {@link #ints(int)}. */
|
||||
public long nint(int value) {
|
||||
long a = nmalloc(4, 4);
|
||||
memPutInt(a, value);
|
||||
return a;
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
|
||||
/** Unsafe version of {@link #longs(long)}. */
|
||||
public long nlong(long value) {
|
||||
long a = nmalloc(8, 8);
|
||||
memPutLong(a, value);
|
||||
return a;
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
|
||||
/** Float version of {@link #malloc(int)}. */
|
||||
public FloatBuffer mallocFloat(int size) { return wrapBufferFloat(nmalloc(4, size << 2), size); }
|
||||
|
||||
/** Unsafe version of {@link #floats(float)}. */
|
||||
public long nfloat(float value) {
|
||||
long a = nmalloc(4, 4);
|
||||
memPutFloat(a, value);
|
||||
return a;
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
|
||||
/** Unsafe version of {@link #doubles(double)}. */
|
||||
public long ndouble(double value) {
|
||||
long a = nmalloc(8, 8);
|
||||
memPutDouble(a, value);
|
||||
return a;
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// -------------------------------------------------
|
||||
// -------------------------------------------------
|
||||
|
||||
|
||||
/** Returns the stack of the current thread. */
|
||||
public static MemoryStack stackGet() {
|
||||
return TLS.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #push} on the stack of the current thread.
|
||||
*
|
||||
* @return the stack of the current thread.
|
||||
*/
|
||||
public static MemoryStack stackPush() {
|
||||
return stackGet().push();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #pop} on the stack of the current thread.
|
||||
*
|
||||
* @return the stack of the current thread.
|
||||
*/
|
||||
public static MemoryStack stackPop() {
|
||||
return stackGet().pop();
|
||||
}
|
||||
|
||||
/** Thread-local version of {@link #nmalloc(int)}. */
|
||||
public static long nstackMalloc(int size) { return stackGet().nmalloc(size); }
|
||||
/** Thread-local version of {@link #nmalloc(int, int)}. */
|
||||
public static long nstackMalloc(int alignment, int size) { return stackGet().nmalloc(alignment, size); }
|
||||
/** Thread-local version of {@link #ncalloc}. */
|
||||
public static long nstackCalloc(int alignment, int num, int size) { return stackGet().ncalloc(alignment, num, size); }
|
||||
|
||||
// -------------------------------------------------
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* This class is backported from LWJGL3 under the BSD 3-clause "New" or "Revised" License
|
||||
*/
|
||||
package com.gtnewhorizons.angelica.compat.lwjgl;
|
||||
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.NULL;
|
||||
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.UNSAFE;
|
||||
import static org.lwjgl.LWJGLUtil.CHECKS;
|
||||
|
||||
import net.minecraft.util.Util;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Pointer interface.
|
||||
*
|
||||
* <p>LWJGL can run on both 32bit and 64bit architectures. Since LWJGL applications deal with native memory directly, this interface provides necessary
|
||||
* information about the underlying architecture of the running JVM process.</p>
|
||||
*
|
||||
* <p>When interacting with native functions, pointer values are mapped to Java {@code long}. LWJGL automatically converts long values to the correct pointer
|
||||
* addresses when used in native code. Native functions sometimes require arrays of pointer values; the {@link PointerBuffer} class may be used for that
|
||||
* purpose. It has an API similar to a {@link java.nio.LongBuffer} but handles pointer casts automatically.</p>
|
||||
*/
|
||||
public interface Pointer {
|
||||
|
||||
/** The pointer size in bytes. Will be 4 on a 32bit JVM and 8 on a 64bit one. */
|
||||
int POINTER_SIZE = UNSAFE.addressSize();
|
||||
|
||||
/** The pointer size power-of-two. Will be 2 on a 32bit JVM and 3 on a 64bit one. */
|
||||
int POINTER_SHIFT = POINTER_SIZE == 8 ? 3 : 2;
|
||||
|
||||
/** The value of {@code sizeof(long)} for the current platform. */
|
||||
int CLONG_SIZE = POINTER_SIZE == 8 && Util.getOSType() == Util.EnumOS.WINDOWS ? 4 : POINTER_SIZE;
|
||||
|
||||
/** The value of {@code sizeof(long)} as a power-of-two. */
|
||||
int CLONG_SHIFT = CLONG_SIZE == 8 ? 3 : 2;
|
||||
|
||||
/** Will be true on a 32bit JVM. */
|
||||
boolean BITS32 = POINTER_SIZE * 8 == 32;
|
||||
|
||||
/** Will be true on a 64bit JVM. */
|
||||
boolean BITS64 = POINTER_SIZE * 8 == 64;
|
||||
|
||||
/**
|
||||
* Returns the raw pointer address as a {@code long} value.
|
||||
*
|
||||
* @return the pointer address
|
||||
*/
|
||||
long address();
|
||||
|
||||
/** Default {@link Pointer} implementation. */
|
||||
abstract class Default implements Pointer {
|
||||
|
||||
// Removed final due to JDK-8139758. TODO: Restore if the fix is backported to JDK 8.
|
||||
protected long address;
|
||||
|
||||
protected Default(long address) {
|
||||
if (CHECKS && address == NULL) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long address() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Pointer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Pointer that = (Pointer)o;
|
||||
|
||||
return address == that.address();
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return (int)(address ^ (address >>> 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s pointer [0x%X]", getClass().getSimpleName(), address);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -15,6 +15,7 @@ public class ByteBufferBackedInputStream extends InputStream {
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (!buf.hasRemaining()) {
|
||||
return -1;
|
||||
|
||||
@@ -9,35 +9,62 @@ 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 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 feet 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();
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,6 +40,7 @@ public class ChunkPos {
|
||||
return (long)x & 4294967295L | ((long)z & 4294967295L) << 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int i = 1664525 * this.x + 1013904223;
|
||||
@@ -47,6 +48,7 @@ public class ChunkPos {
|
||||
return i ^ j;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
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
|
||||
return player.getHeldItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -8,6 +7,8 @@ import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -58,15 +59,20 @@ public class NativeImage extends BufferedImage {
|
||||
|
||||
// final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
|
||||
// final int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_HEIGHT);
|
||||
try (final MemoryStack stack = MemoryStack.stackPush()) {
|
||||
final IntBuffer buffer = stack.mallocInt(size);
|
||||
|
||||
final IntBuffer buffer = MemoryUtilities.memAllocInt(size);
|
||||
|
||||
try {
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
|
||||
|
||||
int[] data = new int[size];
|
||||
buffer.get(data);
|
||||
setRGB(0, 0, width, height, data, 0, width);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeToFile(File file) throws IOException{
|
||||
try {
|
||||
ImageIO.write(this, "png", file);
|
||||
@@ -104,18 +110,15 @@ public class NativeImage extends BufferedImage {
|
||||
|
||||
|
||||
public enum Format {
|
||||
RGBA(4, GL11.GL_RGBA, BufferedImage.TYPE_INT_ARGB),
|
||||
RGB(3, GL11.GL_RGB, BufferedImage.TYPE_INT_RGB);
|
||||
RGBA(4, GL11.GL_RGBA),
|
||||
RGB(3, GL11.GL_RGB);
|
||||
|
||||
private final int components;
|
||||
private final int glFormat;
|
||||
private final int imageType;
|
||||
|
||||
|
||||
Format(int components, int glFormat, int imageType) {
|
||||
Format(int components, int glFormat) {
|
||||
this.components = components;
|
||||
this.glFormat = glFormat;
|
||||
this.imageType = imageType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public class BufferWriter {
|
||||
// Adapted from Neodymium
|
||||
|
||||
private final ByteBuffer buf;
|
||||
|
||||
private final FloatBuffer floatBuffer;
|
||||
private final ShortBuffer shortBuffer;
|
||||
private final IntBuffer intBuffer;
|
||||
|
||||
public BufferWriter(ByteBuffer buf) {
|
||||
this.buf = buf;
|
||||
this.floatBuffer = buf.asFloatBuffer();
|
||||
this.shortBuffer = buf.asShortBuffer();
|
||||
this.intBuffer = buf.asIntBuffer();
|
||||
}
|
||||
|
||||
private void incrementPosition(int add) {
|
||||
buf.position(buf.position() + add);
|
||||
floatBuffer.position(buf.position() / 4);
|
||||
shortBuffer.position(buf.position() / 2);
|
||||
intBuffer.position(buf.position() / 4);
|
||||
}
|
||||
|
||||
public void writeFloat(float x) {
|
||||
try {
|
||||
floatBuffer.put(x);
|
||||
|
||||
incrementPosition(4);
|
||||
} catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeInt(int x) {
|
||||
intBuffer.put(x);
|
||||
|
||||
incrementPosition(4);
|
||||
}
|
||||
|
||||
public void writeByte(byte x) {
|
||||
buf.put(x); // this increments the buffer position by 1
|
||||
|
||||
incrementPosition(0);
|
||||
}
|
||||
|
||||
public int position() {
|
||||
return buf.position();
|
||||
}
|
||||
|
||||
public void writeShort(short s) {
|
||||
shortBuffer.put(s);
|
||||
|
||||
incrementPosition(2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ItemVBOQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public ItemVBOQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
|
||||
// Texture
|
||||
buf.putFloat(quad.getTexU(idx));
|
||||
buf.putFloat(quad.getTexV(idx));
|
||||
|
||||
// Normals + Padding
|
||||
buf.putInt(quad.getNormal(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PositionColorTextureQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public PositionColorTextureQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
|
||||
// Color
|
||||
buf.putInt(quad.getColor(idx));
|
||||
|
||||
// Texture
|
||||
buf.putFloat(quad.getTexU(idx));
|
||||
buf.putFloat(quad.getTexV(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PositionQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public PositionQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PositionTextureColorQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public PositionTextureColorQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
|
||||
// Texture
|
||||
buf.putFloat(quad.getTexU(idx));
|
||||
buf.putFloat(quad.getTexV(idx));
|
||||
|
||||
// Color
|
||||
buf.putInt(quad.getColor(idx));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PositionTextureQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public PositionTextureQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
|
||||
// Texture
|
||||
buf.putFloat(quad.getTexU(idx));
|
||||
buf.putFloat(quad.getTexV(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -72,7 +72,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
private final MultiPhaseParameters phases;
|
||||
private final int hash;
|
||||
private final Optional<RenderLayer> affectedOutline;
|
||||
private final boolean outline;
|
||||
|
||||
private MultiPhase(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
|
||||
super(name, vertexFormat, drawMode, expectedBufferSize, () -> {
|
||||
@@ -84,7 +83,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE ? phases.texture.getId().map((arg2) -> {
|
||||
return getOutline(arg2, phases.cull);
|
||||
}) : Optional.empty();
|
||||
this.outline = phases.outlineMode == RenderLayer.OutlineMode.IS_OUTLINE;
|
||||
this.hash = Objects.hash(new Object[]{super.hashCode(), phases});
|
||||
}
|
||||
|
||||
@@ -97,20 +95,23 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return this.affectedOutline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
return this == object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RenderType[" + this.phases + ']';
|
||||
}
|
||||
|
||||
static {
|
||||
CACHE = new ObjectOpenCustomHashSet(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
|
||||
CACHE = new ObjectOpenCustomHashSet<>(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
static enum HashStrategy implements Hash.Strategy<MultiPhase> {
|
||||
@@ -168,6 +169,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
@@ -179,10 +181,12 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(new Object[]{this.phases, this.outlineMode});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CompositeState[" + this.phases + ", outlineProperty=" + this.outlineMode + ']';
|
||||
}
|
||||
@@ -293,6 +297,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.lwjgl.opengl.GL11;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
|
||||
public abstract class RenderPhase {
|
||||
@@ -98,6 +97,7 @@ public abstract class RenderPhase {
|
||||
this.endAction.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
@@ -109,10 +109,12 @@ public abstract class RenderPhase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@@ -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,12 +28,7 @@ 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 QuadProvider rendering into some vanilla blocks")
|
||||
@Config.Comment("Inject BakedModel rendering into some vanilla blocks")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean injectQPRendering;
|
||||
@@ -32,20 +38,25 @@ 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;
|
||||
|
||||
@Config.Comment("Enable MCPatcherForge features, still in Alpha. Individual features are toggled in mcpatcher.json")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.DefaultBoolean(true)
|
||||
@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 cloud renderer with a VBO version.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVBO;
|
||||
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)
|
||||
@@ -133,6 +144,11 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableDebugLogging;
|
||||
|
||||
@Config.Comment("Enables PBR atlas dumping")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.Name("Enable PBR Debug")
|
||||
public static boolean enablePBRDebug;
|
||||
|
||||
@Config.Comment("Enable Zoom")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
@@ -143,9 +159,66 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeInWorldItemRendering;
|
||||
|
||||
@Config.Comment("Max amount of display lists to cache for optimized item rendering. Higher number will use more VRAM")
|
||||
@Config.DefaultInt(256)
|
||||
@Config.RangeInt(min = 64, max = 1024)
|
||||
public static int itemRendererDisplayListCacheSize;
|
||||
@Config.Comment("Upper limit for the amount of VBO's to cache for optimized item rendering. Higher number can potentially use more VRAM.")
|
||||
@Config.DefaultInt(512)
|
||||
@Config.RangeInt(min = 256, max = 1024)
|
||||
public static int itemRendererCacheSize;
|
||||
|
||||
@Config.Comment("Render distance for the spinning mob inside mod spawners")
|
||||
@Config.DefaultDouble(16D)
|
||||
@Config.RangeDouble(min = 16D, max = 64D)
|
||||
public static double mobSpawnerRenderDistance;
|
||||
|
||||
@Config.Comment("Switches to an alternate FPS limiter that gives more stable frametimes, in exchange for slightly " +
|
||||
"more latency. Will never introduce more than one frame of latency, and has a lower impact at higher framerates.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean sleepBeforeSwap;
|
||||
|
||||
@Config.Comment("Allows unicode languages to use an odd gui scale")
|
||||
@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",
|
||||
})
|
||||
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("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;
|
||||
|
||||
}
|
||||
@@ -6,6 +6,13 @@ import net.minecraft.client.gui.GuiScreen;
|
||||
|
||||
public class AngelicaGuiConfig extends SimpleGuiConfig {
|
||||
public AngelicaGuiConfig(GuiScreen parent) throws ConfigException {
|
||||
super(parent, AngelicaConfig.class, "angelica", "Angelica");
|
||||
super(parent,
|
||||
"angelica",
|
||||
"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")
|
||||
@@ -39,4 +39,29 @@ public class CompatConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixImmersiveEngineering;
|
||||
|
||||
@Config.Comment("Apply fixes to Security Craft")
|
||||
@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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", category = "customfont")
|
||||
public class FontConfig {
|
||||
@Config.Comment("Whether or not to use custom fonts when drawing text.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableCustomFont;
|
||||
|
||||
@Config.Comment("Name of the primary custom font. Best not to set it from here.")
|
||||
@Config.DefaultString("(none)")
|
||||
public static String customFontNamePrimary;
|
||||
|
||||
@Config.Comment("Name of the fallback custom font. Best not to set it from here.")
|
||||
@Config.DefaultString("(none)")
|
||||
public static String customFontNameFallback;
|
||||
|
||||
@Config.Comment("The quality at which custom fonts are rendered, making them less pixelated but increasing memory usage.")
|
||||
@Config.DefaultInt(30)
|
||||
@Config.RangeInt(min = 6, max = 72)
|
||||
public static int customFontQuality;
|
||||
|
||||
@Config.Comment("Controls the distance at which the font's shadow is drawn.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@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)
|
||||
public static float glyphAspect;
|
||||
|
||||
@Config.Comment("Scale of each glyph, whitespace excluded.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float glyphScale;
|
||||
|
||||
@Config.Comment("Whitespace scale.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float whitespaceScale;
|
||||
|
||||
@Config.Comment("Adds extra spacing between glyphs.")
|
||||
@Config.DefaultFloat(0F)
|
||||
@Config.RangeFloat(min = -2F, max = 2F)
|
||||
public static float glyphSpacing;
|
||||
|
||||
@Config.Comment("Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.")
|
||||
@Config.DefaultInt(2)
|
||||
@Config.RangeInt(min = 0, max = 2)
|
||||
public static int fontAAMode;
|
||||
|
||||
@Config.Comment("Affects font antialiasing sample spacing. Higher values increase blur.")
|
||||
@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;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.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);
|
||||
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
|
||||
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(x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glEnd();
|
||||
|
||||
// Restore GL state
|
||||
GLStateManager.enableLighting();
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
|
||||
private void drawChunk(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
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glColor4f(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;
|
||||
|
||||
GLStateManager.glVertex3d(xStart, zStart, 0);
|
||||
GLStateManager.glVertex3d(xStart, zEnd, 0);
|
||||
GLStateManager.glVertex3d(xEnd, zEnd, 0);
|
||||
GLStateManager.glVertex3d(xEnd, zStart, 0);
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,10 @@ 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 com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
|
||||
import java.nio.FloatBuffer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
@@ -131,7 +132,7 @@ public abstract class F3Graph {
|
||||
// Load vertex buffer
|
||||
vertBuf = glGenBuffers();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
|
||||
try (final MemoryStack stack = MemoryStack.stackPush()) {
|
||||
try (final MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer vertices = stack.mallocFloat(VERT_COUNT * VERT_FLOATS);
|
||||
// Since we use a triangle strip, we only need 4 vertices. The quad extends to the top of the screen so spikes
|
||||
// don't get truncated. The max height is replaced in the vert shader, no need to be precise.
|
||||
|
||||
@@ -6,19 +6,17 @@ 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.battlegear2.Battlegear2Compat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
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 mods.battlegear2.api.core.IInventoryPlayerBattle;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.item.EntityItem;
|
||||
import net.minecraft.entity.monster.EntityCreeper;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
@@ -28,7 +26,6 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xonin.backhand.api.core.BackhandUtils;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
@@ -36,9 +33,12 @@ 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 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<>();
|
||||
@@ -53,19 +53,27 @@ 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 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;
|
||||
|
||||
@@ -107,8 +115,8 @@ public class DynamicLights {
|
||||
it = dynamicLightSources.next();
|
||||
if (it.equals(lightSource)) {
|
||||
dynamicLightSources.remove();
|
||||
if (SodiumWorldRenderer.getInstance() != null)
|
||||
lightSource.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
|
||||
if (activeRenderer != null)
|
||||
lightSource.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -130,10 +138,10 @@ public class DynamicLights {
|
||||
it = dynamicLightSources.next();
|
||||
if (filter.test(it)) {
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -153,10 +161,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +211,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());
|
||||
}
|
||||
@@ -229,6 +251,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.
|
||||
*
|
||||
@@ -275,7 +320,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());
|
||||
}
|
||||
|
||||
@@ -285,11 +330,11 @@ 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) {
|
||||
renderer.scheduleRebuildForChunk(x, y, z, false);
|
||||
}
|
||||
|
||||
@@ -378,13 +423,13 @@ public class DynamicLights {
|
||||
player instanceof IBattlePlayer battlePlayer &&
|
||||
battlePlayer.battlegear2$isBattlemode()
|
||||
) {
|
||||
ItemStack offhand = ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
|
||||
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
|
||||
}
|
||||
}
|
||||
else if (ModStatus.isBackhandLoaded && living instanceof EntityPlayer player){
|
||||
ItemStack offhand = BackhandUtils.getOffhandItem(player);
|
||||
ItemStack offhand = ModStatus.backhandCompat.getOffhandItem(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -73,7 +74,7 @@ public interface IDynamicLightSource {
|
||||
*/
|
||||
void angelica$dynamicLightTick();
|
||||
|
||||
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,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,369 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
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 GL30.glGenVertexArrays();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindVertexArray(int array) {
|
||||
GL30.glBindVertexArray(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteVertexArrays(int array) {
|
||||
GL30.glDeleteVertexArrays(array);
|
||||
}
|
||||
|
||||
// ===================== BUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenBuffers() {
|
||||
return GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindBuffer(int target, int buffer) {
|
||||
GL15.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(int buffer) {
|
||||
GL15.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenTextures() {
|
||||
return GL11.glGenTextures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenTextures(int[] textures) {
|
||||
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) {
|
||||
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) {
|
||||
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,238 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor.BufferTransformOptimizer;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Builds final command buffers from raw command buffers and compiled VBO data.
|
||||
* Transforms are already collapsed to MultMatrix during recording; the optimizer
|
||||
* tracks transform state to emit deltas when draws need a different transform.
|
||||
*/
|
||||
public final class CommandBufferBuilder {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("CommandBufferBuilder");
|
||||
|
||||
private CommandBufferBuilder() {}
|
||||
|
||||
/** Build with transform delta optimization and merged draw ranges. */
|
||||
public static void buildOptimizedFromRawBuffer(
|
||||
CommandBuffer rawBuffer,
|
||||
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
|
||||
final CompiledLineBuffer compiledLineBuffer,
|
||||
final Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
|
||||
VertexBuffer[] ownedVbos,
|
||||
int glListId,
|
||||
CommandBuffer finalBuffer) {
|
||||
|
||||
// Build VBO index map for efficient lookup (no boxing)
|
||||
final Object2IntMap<VertexBuffer> vboIndexMap = new Object2IntOpenHashMap<>();
|
||||
vboIndexMap.defaultReturnValue(-1);
|
||||
for (int i = 0; i < ownedVbos.length; i++) {
|
||||
vboIndexMap.put(ownedVbos[i], i);
|
||||
}
|
||||
|
||||
// Collect all merged draw ranges sorted by command index
|
||||
final List<DrawRangeWithBuffer> allRanges = collectDrawRanges(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
|
||||
|
||||
// Buffer optimizer state
|
||||
final BufferTransformOptimizer opt = new BufferTransformOptimizer();
|
||||
|
||||
rawBuffer.resetRead();
|
||||
int cmdIndex = 0;
|
||||
int rangeIndex = 0;
|
||||
|
||||
while (rawBuffer.hasRemaining()) {
|
||||
// Emit draw ranges at this command position
|
||||
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == cmdIndex) {
|
||||
emitDrawRangeToBuffer(allRanges.get(rangeIndex++), opt, finalBuffer, vboIndexMap);
|
||||
}
|
||||
|
||||
// Read and process the command
|
||||
final int opcode = rawBuffer.readInt();
|
||||
CommandBufferProcessor.processCommandForOptimization(opcode, rawBuffer, opt, finalBuffer);
|
||||
cmdIndex++;
|
||||
}
|
||||
|
||||
// Emit remaining draw ranges at end of command stream
|
||||
while (rangeIndex < allRanges.size()) {
|
||||
emitDrawRangeToBuffer(allRanges.get(rangeIndex++), opt, finalBuffer, vboIndexMap);
|
||||
}
|
||||
|
||||
// Emit residual transform if not identity
|
||||
if (!opt.isIdentity()) {
|
||||
opt.emitPendingTransform(finalBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Debug build: no optimizations, 1:1 draw commands. */
|
||||
public static void buildUnoptimizedFromRawBuffer(
|
||||
CommandBuffer rawBuffer,
|
||||
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
|
||||
CompiledLineBuffer compiledLineBuffer,
|
||||
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
|
||||
VertexBuffer[] ownedVbos,
|
||||
CommandBuffer finalBuffer) {
|
||||
|
||||
// Build VBO index map for efficient lookup (no boxing)
|
||||
final Object2IntMap<VertexBuffer> vboIndexMap = new Object2IntOpenHashMap<>();
|
||||
vboIndexMap.defaultReturnValue(-1);
|
||||
for (int i = 0; i < ownedVbos.length; i++) {
|
||||
vboIndexMap.put(ownedVbos[i], i);
|
||||
}
|
||||
|
||||
// Collect per-draw ranges (no merging) sorted by command index
|
||||
final List<DrawRangeWithBuffer> allRanges = collectDrawRangesUnoptimized(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
|
||||
|
||||
rawBuffer.resetRead();
|
||||
int cmdIndex = 0;
|
||||
int rangeIndex = 0;
|
||||
|
||||
while (rawBuffer.hasRemaining()) {
|
||||
// Emit draw ranges at this command position
|
||||
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == cmdIndex) {
|
||||
final DrawRangeWithBuffer drb = allRanges.get(rangeIndex++);
|
||||
final int vboIdx = vboIndexMap.getInt(drb.vbo());
|
||||
if (vboIdx >= 0) {
|
||||
finalBuffer.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
|
||||
DisplayListManager.trackDrawRangeSource(drb.source().toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the command directly
|
||||
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
|
||||
cmdIndex++;
|
||||
}
|
||||
|
||||
// Emit remaining draw ranges at end of command stream
|
||||
while (rangeIndex < allRanges.size()) {
|
||||
final DrawRangeWithBuffer drb = allRanges.get(rangeIndex++);
|
||||
final int vboIdx = vboIndexMap.getInt(drb.vbo());
|
||||
if (vboIdx >= 0) {
|
||||
finalBuffer.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
|
||||
DisplayListManager.trackDrawRangeSource(drb.source().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Collect merged draw ranges, sorted by command index. */
|
||||
static List<DrawRangeWithBuffer> collectDrawRanges(
|
||||
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
|
||||
CompiledLineBuffer compiledLineBuffer,
|
||||
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers) {
|
||||
return collectDrawRangesInternal(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers, true);
|
||||
}
|
||||
|
||||
/** Collect unmerged draw ranges for debug mode. */
|
||||
static List<DrawRangeWithBuffer> collectDrawRangesUnoptimized(
|
||||
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
|
||||
CompiledLineBuffer compiledLineBuffer,
|
||||
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers) {
|
||||
return collectDrawRangesInternal(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers, false);
|
||||
}
|
||||
|
||||
private static List<DrawRangeWithBuffer> collectDrawRangesInternal(
|
||||
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
|
||||
CompiledLineBuffer compiledLineBuffer,
|
||||
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
|
||||
boolean useMerged) {
|
||||
|
||||
final List<DrawRangeWithBuffer> allRanges = new ArrayList<>();
|
||||
|
||||
// Add quad ranges
|
||||
final CompiledFormatBuffer[] quadBufferArray = compiledQuadBuffers.values().toArray(new CompiledFormatBuffer[0]);
|
||||
for (int b = 0; b < quadBufferArray.length; b++) {
|
||||
final CompiledFormatBuffer compiledBuffer = quadBufferArray[b];
|
||||
final DrawRange[] ranges = useMerged ? compiledBuffer.mergedRanges() : compiledBuffer.perDrawRanges();
|
||||
for (int i = 0; i < ranges.length; i++) {
|
||||
allRanges.add(new DrawRangeWithBuffer(ranges[i], compiledBuffer.vbo(), compiledBuffer.flags(),
|
||||
DrawRangeWithBuffer.DrawSource.TESSELLATOR_QUADS));
|
||||
}
|
||||
}
|
||||
|
||||
// Add immediate mode line ranges
|
||||
// Lines use a minimal flags (color only, no texture/brightness/normals)
|
||||
if (compiledLineBuffer != null) {
|
||||
final CapturingTessellator.Flags lineFlags = new CapturingTessellator.Flags(false, false, true, false);
|
||||
final DrawRange[] ranges = useMerged ? compiledLineBuffer.mergedRanges() : compiledLineBuffer.perDrawRanges();
|
||||
for (int i = 0; i < ranges.length; i++) {
|
||||
allRanges.add(new DrawRangeWithBuffer(ranges[i], compiledLineBuffer.vbo(), lineFlags,
|
||||
DrawRangeWithBuffer.DrawSource.IMMEDIATE_MODE_LINES));
|
||||
}
|
||||
}
|
||||
|
||||
// Add tessellator primitive ranges (lines and triangles) - grouped by format
|
||||
if (compiledPrimitiveBuffers != null && !compiledPrimitiveBuffers.isEmpty()) {
|
||||
final CompiledPrimitiveBuffers[] primBufferArray = compiledPrimitiveBuffers.values().toArray(new CompiledPrimitiveBuffers[0]);
|
||||
for (int p = 0; p < primBufferArray.length; p++) {
|
||||
final CompiledPrimitiveBuffers primBuffers = primBufferArray[p];
|
||||
final CapturingTessellator.Flags primFlags = primBuffers.flags();
|
||||
// Tessellator lines
|
||||
if (primBuffers.hasLines()) {
|
||||
final DrawRange[] ranges = useMerged ? primBuffers.lineMergedRanges() : primBuffers.linePerDrawRanges();
|
||||
final VertexBuffer vbo = primBuffers.lineVbo();
|
||||
for (int i = 0; i < ranges.length; i++) {
|
||||
allRanges.add(new DrawRangeWithBuffer(ranges[i], vbo, primFlags,
|
||||
DrawRangeWithBuffer.DrawSource.TESSELLATOR_LINES));
|
||||
}
|
||||
}
|
||||
// Tessellator triangles
|
||||
if (primBuffers.hasTriangles()) {
|
||||
final DrawRange[] ranges = useMerged ? primBuffers.triangleMergedRanges() : primBuffers.trianglePerDrawRanges();
|
||||
final VertexBuffer vbo = primBuffers.triangleVbo();
|
||||
for (int i = 0; i < ranges.length; i++) {
|
||||
allRanges.add(new DrawRangeWithBuffer(ranges[i], vbo, primFlags,
|
||||
DrawRangeWithBuffer.DrawSource.TESSELLATOR_TRIANGLES));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allRanges.sort(Comparator.comparingInt(r -> r.range().commandIndex()));
|
||||
return allRanges;
|
||||
}
|
||||
|
||||
/** Emit draw range with delta transform. Uses writeDrawRangeRestore for immediate mode. */
|
||||
private static void emitDrawRangeToBuffer(
|
||||
DrawRangeWithBuffer drb,
|
||||
BufferTransformOptimizer opt,
|
||||
CommandBuffer out,
|
||||
Object2IntMap<VertexBuffer> vboIndexMap) {
|
||||
|
||||
// Emit transform to reach the draw's expected transform
|
||||
opt.emitTransformTo(out, drb.range().transform());
|
||||
|
||||
// Write the draw range command
|
||||
final int vboIdx = vboIndexMap.getInt(drb.vbo());
|
||||
if (vboIdx >= 0) {
|
||||
final var restoreData = drb.range().restoreData();
|
||||
if (restoreData != null) {
|
||||
// Immediate mode draw with attribute restoration
|
||||
final var flags = drb.flags();
|
||||
out.writeDrawRangeRestore(
|
||||
vboIdx, drb.range().startVertex(), drb.range().vertexCount(),
|
||||
drb.hasBrightness(), flags.hasColor, flags.hasNormals, flags.hasTexture,
|
||||
restoreData.lastColorR(), restoreData.lastColorG(),
|
||||
restoreData.lastColorB(), restoreData.lastColorA(),
|
||||
restoreData.lastNormalX(), restoreData.lastNormalY(), restoreData.lastNormalZ(),
|
||||
restoreData.lastTexCoordS(), restoreData.lastTexCoordT()
|
||||
);
|
||||
} else {
|
||||
// Regular tessellator draw (no restoration needed)
|
||||
out.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
|
||||
}
|
||||
DisplayListManager.trackDrawRangeSource(drb.source().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
|
||||
/**
|
||||
* A compiled VBO with its associated draw ranges.
|
||||
* All ranges reference vertices within this single VBO.
|
||||
* <p>
|
||||
* Contains two range arrays:
|
||||
* <ul>
|
||||
* <li><b>mergedRanges</b>: For optimized path - consecutive same-transform draws merged</li>
|
||||
* <li><b>perDrawRanges</b>: For unoptimized path - 1:1 with original draws</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Desugar
|
||||
public record CompiledFormatBuffer(
|
||||
VertexBuffer vbo,
|
||||
CapturingTessellator.Flags flags,
|
||||
DrawRange[] mergedRanges,
|
||||
DrawRange[] perDrawRanges
|
||||
) {}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
|
||||
/**
|
||||
* A compiled VBO containing all line geometry with associated draw ranges.
|
||||
* All ranges reference vertices within this single VBO.
|
||||
* <p>
|
||||
* Contains two range arrays:
|
||||
* <ul>
|
||||
* <li><b>mergedRanges</b>: For optimized path - consecutive same-transform draws merged</li>
|
||||
* <li><b>perDrawRanges</b>: For unoptimized path - 1:1 with original draws</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Desugar
|
||||
public record CompiledLineBuffer(
|
||||
VertexBuffer vbo,
|
||||
DrawRange[] mergedRanges,
|
||||
DrawRange[] perDrawRanges
|
||||
) {}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
|
||||
/**
|
||||
* Compiled VBOs containing tessellator primitives (lines and triangles) for a single vertex format.
|
||||
* Separate VBOs are used for lines (GL_LINES) and triangles (GL_TRIANGLES) since they have different draw modes.
|
||||
* <p>
|
||||
* Contains range arrays for both optimized (merged) and unoptimized (per-draw) paths.
|
||||
* Similar to CompiledFormatBuffer but handles primitives instead of quads.
|
||||
*/
|
||||
@Desugar
|
||||
public record CompiledPrimitiveBuffers(
|
||||
CapturingTessellator.Flags flags,
|
||||
VertexBuffer lineVbo,
|
||||
DrawRange[] lineMergedRanges,
|
||||
DrawRange[] linePerDrawRanges,
|
||||
VertexBuffer triangleVbo,
|
||||
DrawRange[] triangleMergedRanges,
|
||||
DrawRange[] trianglePerDrawRanges
|
||||
) {
|
||||
public boolean hasLines() {
|
||||
return lineVbo != null;
|
||||
}
|
||||
|
||||
public boolean hasTriangles() {
|
||||
return triangleVbo != null;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,25 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
/**
|
||||
* Represents a vertex range within a shared VBO, along with its transform.
|
||||
* Used during format-based batching to track where each draw's vertices are stored.
|
||||
*
|
||||
* <p>The restoreData field holds last vertex attributes for GL state restoration
|
||||
* after VBO rendering (immediate mode draws only). Null for tessellator draws.
|
||||
*/
|
||||
@Desugar
|
||||
public record DrawRange(
|
||||
int startVertex,
|
||||
int vertexCount,
|
||||
Matrix4f transform,
|
||||
int commandIndex,
|
||||
AccumulatedDraw.RestoreData restoreData
|
||||
) {
|
||||
DrawRange(int startVertex, int vertexCount, Matrix4f transform, int commandIndex) {
|
||||
this(startVertex, vertexCount, transform, commandIndex, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
|
||||
/**
|
||||
* Helper record pairing a DrawRange with its VBO and vertex format flags.
|
||||
* Used for sorting ranges by command index while retaining VBO reference.
|
||||
* Works for both quad ranges (from CompiledFormatBuffer) and line ranges (from CompiledLineBuffer).
|
||||
*/
|
||||
@Desugar
|
||||
record DrawRangeWithBuffer(
|
||||
DrawRange range,
|
||||
VertexBuffer vbo,
|
||||
CapturingTessellator.Flags flags,
|
||||
DrawSource source
|
||||
) {
|
||||
boolean hasBrightness() {
|
||||
return flags != null && flags.hasBrightness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source of a draw command for debug logging.
|
||||
*/
|
||||
enum DrawSource {
|
||||
TESSELLATOR_QUADS("tessellator quads"),
|
||||
TESSELLATOR_LINES("tessellator lines"),
|
||||
TESSELLATOR_TRIANGLES("tessellator triangles"),
|
||||
IMMEDIATE_MODE_LINES("immediate mode lines");
|
||||
|
||||
private final String description;
|
||||
|
||||
DrawSource(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,17 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -19,24 +22,66 @@ public class Feature {
|
||||
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
|
||||
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
|
||||
|
||||
static final Int2ObjectMap<Set<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
|
||||
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
|
||||
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static Set<IStateStack<?>> maskToFeatures(int mask) {
|
||||
static List<IStateStack<?>> maskToFeatures(int mask) {
|
||||
if(maskToFeaturesMap.containsKey(mask)) {
|
||||
return maskToFeaturesMap.get(mask);
|
||||
}
|
||||
|
||||
final Set<IStateStack<?>> features = new HashSet<>();
|
||||
|
||||
for(int attrib : Feature.supportedAttribs) {
|
||||
if((mask & attrib) == attrib) {
|
||||
features.addAll(getFeatures(attrib));
|
||||
}
|
||||
}
|
||||
maskToFeaturesMap.put(mask, features);
|
||||
return features;
|
||||
|
||||
final List<IStateStack<?>> asList = new ArrayList<>(features);
|
||||
|
||||
maskToFeaturesMap.put(mask, asList);
|
||||
return asList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only non-BooleanStateStack instances for the given mask.
|
||||
* These use traditional push/pop without global depth tracking.
|
||||
*/
|
||||
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
|
||||
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final List<IStateStack<?>> all = maskToFeatures(mask);
|
||||
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
|
||||
for (int i = 0; i < all.size(); i++) {
|
||||
final IStateStack<?> stack = all.get(i);
|
||||
if (!(stack instanceof BooleanStateStack)) {
|
||||
nonBooleans.add(stack);
|
||||
}
|
||||
}
|
||||
cached = nonBooleans.toArray(new IStateStack<?>[0]);
|
||||
maskToNonBooleanStacksMap.put(mask, cached);
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
|
||||
* Used by both GL_ENABLE_BIT and GL_TEXTURE_BIT.
|
||||
*/
|
||||
private static void addTextureGenStates(Set<IStateStack<?>> collection) {
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
collection.add(GLStateManager.textures.getTexGenSStates(i));
|
||||
collection.add(GLStateManager.textures.getTexGenTStates(i));
|
||||
collection.add(GLStateManager.textures.getTexGenRStates(i));
|
||||
collection.add(GLStateManager.textures.getTexGenQStates(i));
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
|
||||
GLStateManager.alphaTest // GL_ALPHA_TEST enable bit
|
||||
@@ -45,10 +90,10 @@ public class Feature {
|
||||
, GLStateManager.blendState // Blending source and destination functions
|
||||
// Constant blend color
|
||||
// Blending equation
|
||||
// GL_DITHER enable bit
|
||||
// GL_DRAW_BUFFER setting
|
||||
// GL_COLOR_LOGIC_OP enable bit
|
||||
// GL_INDEX_LOGIC_OP enable bit
|
||||
, 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.colorMask // Color-mode and index-mode writemasks
|
||||
, GLStateManager.clearColor // Color-mode and index-mode clear values
|
||||
@@ -74,14 +119,15 @@ public class Feature {
|
||||
|
||||
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
|
||||
GLStateManager.alphaTest // GL_ALPHA_TEST flag
|
||||
// GL_AUTO_NORMAL flag
|
||||
, GLStateManager.autoNormalState // GL_AUTO_NORMAL flag
|
||||
, GLStateManager.blendMode // GL_BLEND flag
|
||||
// Enable bits for the user-definable clipping planes
|
||||
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP flag
|
||||
, GLStateManager.colorMaterial // GL_COLOR_MATERIAL
|
||||
, GLStateManager.cullState // GL_CULL_FACE flag
|
||||
, GLStateManager.depthTest // GL_DEPTH_TEST flag
|
||||
// GL_DITHER flag
|
||||
, GLStateManager.ditherState // GL_DITHER flag
|
||||
, GLStateManager.fogMode // GL_FOG flag
|
||||
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP flag
|
||||
, GLStateManager.lightStates[0] // GL_LIGHT0
|
||||
, GLStateManager.lightStates[1] // GL_LIGHT1
|
||||
, GLStateManager.lightStates[2] // GL_LIGHT2
|
||||
@@ -91,36 +137,57 @@ public class Feature {
|
||||
, GLStateManager.lightStates[6] // GL_LIGHT6
|
||||
, GLStateManager.lightStates[7] // GL_LIGHT7
|
||||
, GLStateManager.lightingState // GL_LIGHTING flag
|
||||
// GL_LINE_SMOOTH flag
|
||||
// GL_LINE_STIPPLE flag
|
||||
// GL_INDEX_LOGIC_OP flag
|
||||
// GL_COLOR_LOGIC_OP flag
|
||||
// GL_MAP1_x where x is a map type
|
||||
// GL_MAP2_x where x is a map type
|
||||
// GL_MULTISAMPLE flag
|
||||
// GL_NORMALIZE flag
|
||||
// GL_POINT_SMOOTH flag
|
||||
// GL_POLYGON_OFFSET_LINE flag
|
||||
// GL_POLYGON_OFFSET_FILL flag
|
||||
// GL_POLYGON_OFFSET_POINT flag
|
||||
// GL_POLYGON_SMOOTH flag
|
||||
// GL_POLYGON_STIPPLE flag
|
||||
// GL_SAMPLE_ALPHA_TO_COVERAGE flag
|
||||
// GL_SAMPLE_ALPHA_TO_ONE flag
|
||||
// GL_SAMPLE_COVERAGE flag
|
||||
, GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
|
||||
, GLStateManager.lineStippleState // GL_LINE_STIPPLE flag
|
||||
, GLStateManager.map1Color4State // GL_MAP1_COLOR_4
|
||||
, GLStateManager.map1IndexState // GL_MAP1_INDEX
|
||||
, GLStateManager.map1NormalState // GL_MAP1_NORMAL
|
||||
, GLStateManager.map1TextureCoord1State // GL_MAP1_TEXTURE_COORD_1
|
||||
, GLStateManager.map1TextureCoord2State // GL_MAP1_TEXTURE_COORD_2
|
||||
, GLStateManager.map1TextureCoord3State // GL_MAP1_TEXTURE_COORD_3
|
||||
, GLStateManager.map1TextureCoord4State // GL_MAP1_TEXTURE_COORD_4
|
||||
, GLStateManager.map1Vertex3State // GL_MAP1_VERTEX_3
|
||||
, GLStateManager.map1Vertex4State // GL_MAP1_VERTEX_4
|
||||
, GLStateManager.map2Color4State // GL_MAP2_COLOR_4
|
||||
, GLStateManager.map2IndexState // GL_MAP2_INDEX
|
||||
, GLStateManager.map2NormalState // GL_MAP2_NORMAL
|
||||
, GLStateManager.map2TextureCoord1State // GL_MAP2_TEXTURE_COORD_1
|
||||
, GLStateManager.map2TextureCoord2State // GL_MAP2_TEXTURE_COORD_2
|
||||
, GLStateManager.map2TextureCoord3State // GL_MAP2_TEXTURE_COORD_3
|
||||
, GLStateManager.map2TextureCoord4State // GL_MAP2_TEXTURE_COORD_4
|
||||
, GLStateManager.map2Vertex3State // GL_MAP2_VERTEX_3
|
||||
, GLStateManager.map2Vertex4State // GL_MAP2_VERTEX_4
|
||||
, GLStateManager.multisampleState // GL_MULTISAMPLE flag
|
||||
, GLStateManager.normalizeState // GL_NORMALIZE flag
|
||||
, GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
|
||||
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
|
||||
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
|
||||
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
|
||||
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
|
||||
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE flag
|
||||
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
|
||||
, GLStateManager.sampleAlphaToCoverageState // GL_SAMPLE_ALPHA_TO_COVERAGE flag
|
||||
, GLStateManager.sampleAlphaToOneState // GL_SAMPLE_ALPHA_TO_ONE flag
|
||||
, GLStateManager.sampleCoverageState // GL_SAMPLE_COVERAGE flag
|
||||
, GLStateManager.scissorTest // GL_SCISSOR_TEST flag
|
||||
// GL_STENCIL_TEST flag
|
||||
// GL_TEXTURE_1D flag
|
||||
// GL_TEXTURE_2D flag - Below
|
||||
// GL_TEXTURE_3D flag
|
||||
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
, GLStateManager.stencilTest // GL_STENCIL_TEST flag
|
||||
));
|
||||
|
||||
// GL_TEXTURE_2D flag
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
enableBits.add(GLStateManager.textures.getTextureUnitStates(i));
|
||||
// Enable bits for the user-definable clipping planes
|
||||
for(int i = 0; i < GLStateManager.clipPlaneStates.length; i++) {
|
||||
enableBits.add(GLStateManager.clipPlaneStates[i]);
|
||||
}
|
||||
|
||||
// GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
enableBits.add(GLStateManager.textures.getTexture1DStates(i));
|
||||
enableBits.add(GLStateManager.textures.getTextureUnitStates(i));
|
||||
enableBits.add(GLStateManager.textures.getTexture3DStates(i));
|
||||
}
|
||||
|
||||
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
addTextureGenStates(enableBits);
|
||||
|
||||
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
|
||||
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
|
||||
// GL_MAP1_x enable bits, where x is a map type
|
||||
@@ -181,10 +248,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
|
||||
@@ -209,21 +275,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
|
||||
@@ -233,12 +295,8 @@ 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
|
||||
@@ -262,13 +320,16 @@ public class Feature {
|
||||
// textureAttribs.add(GLStateManager.textures.getInfo(i))
|
||||
}
|
||||
|
||||
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
addTextureGenStates(textureAttribs);
|
||||
|
||||
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
|
||||
|
||||
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
|
||||
// Coefficients of the six clipping planes
|
||||
// Enable bits for the user-definable clipping planes
|
||||
GLStateManager.matrixMode
|
||||
// GL_NORMALIZE flag
|
||||
, GLStateManager.normalizeState // GL_NORMALIZE flag
|
||||
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Accumulates geometry for a single vertex format during display list compilation.
|
||||
* Tracks merged ranges (same-transform draws combined) and per-draw ranges (1:1).
|
||||
*/
|
||||
class FormatBuffer {
|
||||
final CapturingTessellator.Flags flags;
|
||||
final List<ModelQuadViewMutable> allQuads = new ArrayList<>();
|
||||
|
||||
// Merged ranges for optimized path (consecutive same-transform draws combined)
|
||||
final List<DrawRange> mergedRanges = new ArrayList<>();
|
||||
|
||||
// Per-draw ranges for unoptimized path (1:1 with input draws)
|
||||
final List<DrawRange> perDrawRanges = new ArrayList<>();
|
||||
|
||||
int currentVertexOffset = 0;
|
||||
|
||||
// Track the current range being built for merging consecutive same-transform draws
|
||||
private int pendingStartVertex = -1;
|
||||
private int pendingVertexCount = 0;
|
||||
private Matrix4f pendingTransform = null;
|
||||
private int pendingCommandIndex = -1;
|
||||
private int pendingGeneration = -1;
|
||||
private int pendingStateGen = -1;
|
||||
// Track last restore data for merged draws (uses the LAST draw's data)
|
||||
private AccumulatedDraw.RestoreData pendingRestoreData = null;
|
||||
|
||||
FormatBuffer(CapturingTessellator.Flags flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
/** Add a draw, tracking both merged and per-draw ranges. */
|
||||
void addDraw(AccumulatedDraw draw) {
|
||||
final int vertexCount = draw.quads.size() * 4; // 4 vertices per quad
|
||||
|
||||
// Always track per-draw range for unoptimized path (includes restoreData)
|
||||
perDrawRanges.add(new DrawRange(
|
||||
currentVertexOffset, vertexCount, draw.transform, draw.commandIndex, draw.restoreData
|
||||
));
|
||||
|
||||
// Merge logic for optimized path:
|
||||
// - same matrixGeneration = same matrix transform context
|
||||
// - same stateGeneration = no state commands (draw barriers) between draws
|
||||
boolean canMerge = pendingGeneration != -1
|
||||
&& pendingGeneration == draw.matrixGeneration
|
||||
&& pendingStateGen == draw.stateGeneration;
|
||||
|
||||
if (canMerge) {
|
||||
// Extend the pending range
|
||||
pendingVertexCount += vertexCount;
|
||||
// Update restoreData to the LAST draw's data (used after merged draw completes)
|
||||
pendingRestoreData = draw.restoreData;
|
||||
} else {
|
||||
// Flush pending range if any
|
||||
flushPendingRange();
|
||||
|
||||
// Start new pending range
|
||||
pendingStartVertex = currentVertexOffset;
|
||||
pendingVertexCount = vertexCount;
|
||||
pendingTransform = new Matrix4f(draw.transform);
|
||||
pendingCommandIndex = draw.commandIndex;
|
||||
pendingGeneration = draw.matrixGeneration;
|
||||
pendingStateGen = draw.stateGeneration;
|
||||
pendingRestoreData = draw.restoreData;
|
||||
}
|
||||
|
||||
allQuads.addAll(draw.quads);
|
||||
currentVertexOffset += vertexCount;
|
||||
}
|
||||
|
||||
private void flushPendingRange() {
|
||||
if (pendingGeneration != -1) {
|
||||
mergedRanges.add(new DrawRange(
|
||||
pendingStartVertex, pendingVertexCount, pendingTransform, pendingCommandIndex, pendingRestoreData
|
||||
));
|
||||
pendingTransform = null;
|
||||
pendingGeneration = -1;
|
||||
pendingStateGen = -1;
|
||||
pendingRestoreData = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Compile accumulated quads into a VBO. */
|
||||
CompiledFormatBuffer finish() {
|
||||
// Flush any remaining pending range
|
||||
flushPendingRange();
|
||||
|
||||
VertexBuffer vbo = DisplayListManager.compileQuads(allQuads, flags);
|
||||
return new CompiledFormatBuffer(
|
||||
vbo,
|
||||
flags,
|
||||
mergedRanges.toArray(new DrawRange[0]),
|
||||
perDrawRanges.toArray(new DrawRange[0])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,11 @@ 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;
|
||||
@@ -99,6 +103,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);
|
||||
@@ -121,6 +128,9 @@ public final class GLDebug {
|
||||
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.");
|
||||
@@ -269,6 +279,304 @@ 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);
|
||||
};
|
||||
}
|
||||
|
||||
private static DebugState debugState;
|
||||
|
||||
private interface DebugState {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,134 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedLineDraw;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Accumulates line geometry during display list compilation.
|
||||
* All line draws are collected here, then compiled into a single VBO.
|
||||
* <p>
|
||||
* Lines all use the same format (POSITION_COLOR), so no format-based grouping needed.
|
||||
* <p>
|
||||
* Tracks two types of ranges:
|
||||
* <ul>
|
||||
* <li><b>mergedRanges</b>: Consecutive same-transform draws merged for optimized path</li>
|
||||
* <li><b>perDrawRanges</b>: One range per input draw for unoptimized path (1:1 with addDraw calls)</li>
|
||||
* </ul>
|
||||
*/
|
||||
class LineBuffer {
|
||||
final List<ImmediateModeRecorder.LineVertex> allVertices = new ArrayList<>();
|
||||
|
||||
// Merged ranges for optimized path (consecutive same-transform draws combined)
|
||||
final List<DrawRange> mergedRanges = new ArrayList<>();
|
||||
|
||||
// Per-draw ranges for unoptimized path (1:1 with input draws)
|
||||
final List<DrawRange> perDrawRanges = new ArrayList<>();
|
||||
|
||||
int currentVertexOffset = 0;
|
||||
|
||||
// Track the current range being built for merging consecutive same-transform draws
|
||||
private int pendingStartVertex = -1;
|
||||
private int pendingVertexCount = 0;
|
||||
private Matrix4f pendingTransform = null;
|
||||
private int pendingCommandIndex = -1;
|
||||
private int pendingStateGen = -1;
|
||||
|
||||
/**
|
||||
* Add a line draw to this buffer.
|
||||
* Tracks both merged (for optimized) and per-draw (for unoptimized) ranges.
|
||||
*/
|
||||
void addDraw(AccumulatedLineDraw draw) {
|
||||
int vertexCount = draw.lines.size(); // Each LineVertex is one vertex, lines come in pairs
|
||||
|
||||
// Always track per-draw range for unoptimized path
|
||||
perDrawRanges.add(new DrawRange(currentVertexOffset, vertexCount, draw.transform, draw.commandIndex));
|
||||
|
||||
// Merge logic for optimized path:
|
||||
// - same transform
|
||||
// - same stateGeneration = no state commands (draw barriers) between draws
|
||||
boolean canMerge = pendingTransform != null
|
||||
&& pendingTransform.equals(draw.transform)
|
||||
&& pendingStateGen == draw.stateGeneration;
|
||||
|
||||
if (canMerge) {
|
||||
// Extend the pending range
|
||||
pendingVertexCount += vertexCount;
|
||||
} else {
|
||||
// Flush pending range if any
|
||||
flushPendingRange();
|
||||
|
||||
// Start new pending range
|
||||
pendingStartVertex = currentVertexOffset;
|
||||
pendingVertexCount = vertexCount;
|
||||
pendingTransform = new Matrix4f(draw.transform);
|
||||
pendingCommandIndex = draw.commandIndex;
|
||||
pendingStateGen = draw.stateGeneration;
|
||||
}
|
||||
|
||||
allVertices.addAll(draw.lines);
|
||||
currentVertexOffset += vertexCount;
|
||||
}
|
||||
|
||||
private void flushPendingRange() {
|
||||
if (pendingTransform != null) {
|
||||
mergedRanges.add(new DrawRange(pendingStartVertex, pendingVertexCount, pendingTransform, pendingCommandIndex));
|
||||
pendingTransform = null;
|
||||
pendingStateGen = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile all accumulated line vertices into a single VBO.
|
||||
* @return The compiled buffer with VBO and both range types, or null if empty
|
||||
*/
|
||||
CompiledLineBuffer finish() {
|
||||
if (allVertices.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Flush any remaining pending range
|
||||
flushPendingRange();
|
||||
|
||||
VertexBuffer vbo = compileLinesToVBO(allVertices);
|
||||
return new CompiledLineBuffer(
|
||||
vbo,
|
||||
mergedRanges.toArray(new DrawRange[0]),
|
||||
perDrawRanges.toArray(new DrawRange[0])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile line vertices into a VertexBuffer (VAO or VBO).
|
||||
* Lines use POSITION_COLOR format (x, y, z, color).
|
||||
*/
|
||||
private static VertexBuffer compileLinesToVBO(List<ImmediateModeRecorder.LineVertex> lines) {
|
||||
final VertexFormat format = DefaultVertexFormat.POSITION_COLOR;
|
||||
final VertexBuffer vao = VAOManager.createVAO(format, GL11.GL_LINES);
|
||||
|
||||
// POSITION_COLOR format: 3 floats (12 bytes) + 4 bytes color = 16 bytes per vertex
|
||||
final int vertexSize = format.getVertexSize();
|
||||
final ByteBuffer buffer = org.lwjgl.BufferUtils.createByteBuffer(vertexSize * lines.size());
|
||||
|
||||
for (ImmediateModeRecorder.LineVertex v : lines) {
|
||||
buffer.putFloat(v.x);
|
||||
buffer.putFloat(v.y);
|
||||
buffer.putFloat(v.z);
|
||||
buffer.putInt(v.color);
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
vao.upload(buffer);
|
||||
|
||||
return vao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
|
||||
/**
|
||||
* Result of building an optimized display list.
|
||||
* Contains both the commands and the shared VBOs that need lifecycle management.
|
||||
*/
|
||||
@Desugar
|
||||
record OptimizedListResult(DisplayListCommand[] commands, VertexBuffer[] ownedVbos) {}
|
||||
@@ -30,7 +30,6 @@ import java.nio.IntBuffer;
|
||||
public class RenderSystem {
|
||||
private static final Logger LOGGER = LogManager.getLogger("RenderSystem");
|
||||
private static DSAAccess dsaState;
|
||||
private static boolean hasMultibind;
|
||||
private static boolean supportsCompute;
|
||||
|
||||
private RenderSystem() {}
|
||||
@@ -41,7 +40,6 @@ public class RenderSystem {
|
||||
dsaState = new DSACore();
|
||||
LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
|
||||
}
|
||||
hasMultibind = GLStateManager.capabilities.OpenGL45;
|
||||
|
||||
} catch (NoSuchFieldError ignored) {}
|
||||
try {
|
||||
@@ -55,10 +53,6 @@ public class RenderSystem {
|
||||
LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
|
||||
}
|
||||
|
||||
try {
|
||||
hasMultibind |= GLStateManager.capabilities.GL_ARB_multi_bind;
|
||||
} catch (NoSuchFieldError ignored) {}
|
||||
|
||||
supportsCompute = supportsCompute();
|
||||
}
|
||||
|
||||
@@ -80,7 +74,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) {
|
||||
@@ -103,6 +97,8 @@ public class RenderSystem {
|
||||
GL20.glUniform3f(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
public static void uniform3i(int location, int v0, int v1, int v2) { GL20.glUniform3i(location, v0, v1, v2); }
|
||||
|
||||
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
|
||||
GL20.glUniform4f(location, v0, v1, v2, v3);
|
||||
}
|
||||
@@ -135,6 +131,22 @@ 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 String getProgramInfoLog(int program) {
|
||||
return GL20.glGetProgramInfoLog(program, GL20.glGetProgrami(program, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
@@ -183,6 +195,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);
|
||||
@@ -241,17 +257,17 @@ public class RenderSystem {
|
||||
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.line.ModelLine;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.tri.ModelTriangle;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedPrimitiveDraw;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Accumulates tessellator primitives (lines, triangles) for a single vertex format during display list compilation.
|
||||
* Similar to FormatBuffer but handles ModelPrimitiveView instances instead of quads.
|
||||
* <p>
|
||||
* Unlike LineBuffer which handles ImmediateModeRecorder.LineVertex (fixed 16-byte: pos+color),
|
||||
* this handles ModelLine/ModelTriangle from the tessellator with format-optimized vertex size.
|
||||
* <p>
|
||||
* Primitives are separated by draw mode (GL_LINES, GL_TRIANGLES) into separate VBOs.
|
||||
*/
|
||||
class TessellatorPrimitiveBuffer {
|
||||
final CapturingTessellator.Flags flags;
|
||||
|
||||
// Accumulated primitives separated by type
|
||||
final List<ModelLine> allLines = new ArrayList<>();
|
||||
final List<ModelTriangle> allTriangles = new ArrayList<>();
|
||||
|
||||
// Ranges for lines
|
||||
final List<DrawRange> lineMergedRanges = new ArrayList<>();
|
||||
final List<DrawRange> linePerDrawRanges = new ArrayList<>();
|
||||
int currentLineVertexOffset = 0;
|
||||
private int pendingLineStartVertex = -1;
|
||||
private int pendingLineVertexCount = 0;
|
||||
private Matrix4f pendingLineTransform = null;
|
||||
private int pendingLineCommandIndex = -1;
|
||||
private int pendingLineStateGen = -1;
|
||||
|
||||
// Ranges for triangles
|
||||
final List<DrawRange> triangleMergedRanges = new ArrayList<>();
|
||||
final List<DrawRange> trianglePerDrawRanges = new ArrayList<>();
|
||||
int currentTriangleVertexOffset = 0;
|
||||
private int pendingTriangleStartVertex = -1;
|
||||
private int pendingTriangleVertexCount = 0;
|
||||
private Matrix4f pendingTriangleTransform = null;
|
||||
private int pendingTriangleCommandIndex = -1;
|
||||
private int pendingTriangleStateGen = -1;
|
||||
|
||||
TessellatorPrimitiveBuffer(CapturingTessellator.Flags flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a primitive draw to this buffer.
|
||||
* Separates lines and triangles, tracking ranges for both merged and per-draw paths.
|
||||
*/
|
||||
void addDraw(AccumulatedPrimitiveDraw draw) {
|
||||
final List<ModelPrimitiveView> primitives = draw.primitives;
|
||||
final int size = primitives.size();
|
||||
|
||||
// Single pass: count and collect primitives by type
|
||||
int lineCount = 0;
|
||||
int triangleCount = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
final ModelPrimitiveView prim = primitives.get(i);
|
||||
if (prim instanceof ModelLine ml) {
|
||||
allLines.add(ml);
|
||||
lineCount++;
|
||||
} else if (prim instanceof ModelTriangle mt) {
|
||||
allTriangles.add(mt);
|
||||
triangleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Process lines
|
||||
if (lineCount > 0) {
|
||||
final int lineVertexCount = lineCount * 2; // 2 vertices per line
|
||||
linePerDrawRanges.add(new DrawRange(currentLineVertexOffset, lineVertexCount, draw.transform, draw.commandIndex));
|
||||
|
||||
// Merge logic for optimized path:
|
||||
// - same transform
|
||||
// - same stateGeneration = no state commands (draw barriers) between draws
|
||||
boolean canMerge = pendingLineTransform != null
|
||||
&& pendingLineTransform.equals(draw.transform)
|
||||
&& pendingLineStateGen == draw.stateGeneration;
|
||||
|
||||
if (canMerge) {
|
||||
pendingLineVertexCount += lineVertexCount;
|
||||
} else {
|
||||
flushPendingLineRange();
|
||||
pendingLineStartVertex = currentLineVertexOffset;
|
||||
pendingLineVertexCount = lineVertexCount;
|
||||
pendingLineTransform = new Matrix4f(draw.transform);
|
||||
pendingLineCommandIndex = draw.commandIndex;
|
||||
pendingLineStateGen = draw.stateGeneration;
|
||||
}
|
||||
currentLineVertexOffset += lineVertexCount;
|
||||
}
|
||||
|
||||
// Process triangles
|
||||
if (triangleCount > 0) {
|
||||
final int triangleVertexCount = triangleCount * 3; // 3 vertices per triangle
|
||||
trianglePerDrawRanges.add(new DrawRange(currentTriangleVertexOffset, triangleVertexCount, draw.transform, draw.commandIndex));
|
||||
|
||||
// Merge logic for optimized path:
|
||||
// - same transform
|
||||
// - same stateGeneration = no state commands (draw barriers) between draws
|
||||
boolean canMerge = pendingTriangleTransform != null
|
||||
&& pendingTriangleTransform.equals(draw.transform)
|
||||
&& pendingTriangleStateGen == draw.stateGeneration;
|
||||
|
||||
if (canMerge) {
|
||||
pendingTriangleVertexCount += triangleVertexCount;
|
||||
} else {
|
||||
flushPendingTriangleRange();
|
||||
pendingTriangleStartVertex = currentTriangleVertexOffset;
|
||||
pendingTriangleVertexCount = triangleVertexCount;
|
||||
pendingTriangleTransform = new Matrix4f(draw.transform);
|
||||
pendingTriangleCommandIndex = draw.commandIndex;
|
||||
pendingTriangleStateGen = draw.stateGeneration;
|
||||
}
|
||||
currentTriangleVertexOffset += triangleVertexCount;
|
||||
}
|
||||
}
|
||||
|
||||
private void flushPendingLineRange() {
|
||||
if (pendingLineTransform != null) {
|
||||
lineMergedRanges.add(new DrawRange(pendingLineStartVertex, pendingLineVertexCount, pendingLineTransform, pendingLineCommandIndex));
|
||||
pendingLineTransform = null;
|
||||
pendingLineStateGen = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void flushPendingTriangleRange() {
|
||||
if (pendingTriangleTransform != null) {
|
||||
triangleMergedRanges.add(new DrawRange(pendingTriangleStartVertex, pendingTriangleVertexCount, pendingTriangleTransform, pendingTriangleCommandIndex));
|
||||
pendingTriangleTransform = null;
|
||||
pendingTriangleStateGen = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile all accumulated primitives into VBOs using optimal vertex format.
|
||||
* @return The compiled buffers with separate VBOs for lines and triangles, or null if empty
|
||||
*/
|
||||
CompiledPrimitiveBuffers finish() {
|
||||
flushPendingLineRange();
|
||||
flushPendingTriangleRange();
|
||||
|
||||
if (allLines.isEmpty() && allTriangles.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use optimal format based on flags (same logic as quads)
|
||||
final VertexFormat format = DisplayListManager.selectOptimalFormat(flags);
|
||||
|
||||
final VertexBuffer lineVbo = allLines.isEmpty() ? null : compileLinesToVBO(format);
|
||||
final VertexBuffer triangleVbo = allTriangles.isEmpty() ? null : compileTrianglesToVBO(format);
|
||||
|
||||
return new CompiledPrimitiveBuffers(
|
||||
flags,
|
||||
lineVbo,
|
||||
lineMergedRanges.toArray(new DrawRange[0]),
|
||||
linePerDrawRanges.toArray(new DrawRange[0]),
|
||||
triangleVbo,
|
||||
triangleMergedRanges.toArray(new DrawRange[0]),
|
||||
trianglePerDrawRanges.toArray(new DrawRange[0])
|
||||
);
|
||||
}
|
||||
|
||||
private VertexBuffer compileLinesToVBO(VertexFormat format) {
|
||||
final int vertexSize = format.getVertexSize();
|
||||
final int size = allLines.size();
|
||||
final ByteBuffer buffer = BufferUtils.createByteBuffer(vertexSize * size * 2);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
writePrimitiveToBuffer(allLines.get(i), buffer);
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
final VertexBuffer vbo = VAOManager.createVAO(format, GL11.GL_LINES);
|
||||
vbo.upload(buffer);
|
||||
return vbo;
|
||||
}
|
||||
|
||||
private VertexBuffer compileTrianglesToVBO(VertexFormat format) {
|
||||
final int vertexSize = format.getVertexSize();
|
||||
final int size = allTriangles.size();
|
||||
final ByteBuffer buffer = BufferUtils.createByteBuffer(vertexSize * size * 3);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
writePrimitiveToBuffer(allTriangles.get(i), buffer);
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
final VertexBuffer vbo = VAOManager.createVAO(format, GL11.GL_TRIANGLES);
|
||||
vbo.upload(buffer);
|
||||
return vbo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a primitive's vertices to the buffer using the format specified by flags.
|
||||
* Only writes attributes that are present in the format.
|
||||
*/
|
||||
private void writePrimitiveToBuffer(ModelPrimitiveView prim, ByteBuffer buffer) {
|
||||
final int vertexCount = prim.getVertexCount();
|
||||
final boolean hasTexture = flags.hasTexture;
|
||||
final boolean hasBrightness = flags.hasBrightness;
|
||||
final boolean hasColor = flags.hasColor;
|
||||
final boolean hasNormals = flags.hasNormals;
|
||||
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
// Position (always present) - 12 bytes
|
||||
buffer.putFloat(prim.getX(i));
|
||||
buffer.putFloat(prim.getY(i));
|
||||
buffer.putFloat(prim.getZ(i));
|
||||
|
||||
// Color (4 bytes, ABGR) - if present
|
||||
if (hasColor) {
|
||||
buffer.putInt(prim.getColor(i));
|
||||
}
|
||||
|
||||
// Texture (2 floats = 8 bytes) - if present
|
||||
if (hasTexture) {
|
||||
buffer.putFloat(prim.getTexU(i));
|
||||
buffer.putFloat(prim.getTexV(i));
|
||||
}
|
||||
|
||||
// Light/Brightness (4 bytes, packed) - if present
|
||||
if (hasBrightness) {
|
||||
buffer.putInt(prim.getLight(i));
|
||||
}
|
||||
|
||||
// Normal (4 bytes, packed) - if present
|
||||
if (hasNormals) {
|
||||
buffer.putInt(prim.getForgeNormal(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,27 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
/// Matches
|
||||
public enum Vendor {
|
||||
AMD("amd"),
|
||||
INTEL("intel"),
|
||||
MESA("mesa"),
|
||||
NVIDIA("nvidia"),
|
||||
UNKNOWN("");
|
||||
|
||||
final String[] names;
|
||||
|
||||
Vendor(String... names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
public static Vendor getVendor(String vendorString) {
|
||||
for (var v : values()) {
|
||||
for (var name : v.names) {
|
||||
if (vendorString.contains(name)) return v;
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't be needed as "" matches everything, but sanity checks good.
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,6 +15,29 @@ 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) {
|
||||
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
|
||||
@@ -48,6 +72,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 +84,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);
|
||||
|
||||
@@ -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,6 +23,8 @@ 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);
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
@@ -20,6 +21,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);
|
||||
@@ -56,9 +90,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);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
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 class AccumulatedDraw {
|
||||
public final List<ModelQuadViewMutable> quads;
|
||||
public final Matrix4f transform;
|
||||
public final CapturingTessellator.Flags flags;
|
||||
public final int commandIndex; // Position in command list for state tracking
|
||||
public final int matrixGeneration; // For batching: same generation = same transform context
|
||||
public final int stateGeneration; // Same value = no state commands between draws, can merge
|
||||
|
||||
/**
|
||||
* Last vertex restoration data for immediate mode draws.
|
||||
* Null for tessellator draws (no restoration needed).
|
||||
*/
|
||||
public final RestoreData restoreData;
|
||||
|
||||
/**
|
||||
* Holds last vertex attribute values for GL state restoration after VBO draw.
|
||||
* Used to sync GLSM cache with actual GL state after VBO rendering.
|
||||
*/
|
||||
@Desugar
|
||||
public record RestoreData(
|
||||
float lastColorR, float lastColorG, float lastColorB, float lastColorA,
|
||||
float lastNormalX, float lastNormalY, float lastNormalZ,
|
||||
float lastTexCoordS, float lastTexCoordT) {}
|
||||
|
||||
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration) {
|
||||
this(quads, transform, flags, commandIndex, matrixGeneration, 0, null);
|
||||
}
|
||||
|
||||
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration, int stateGeneration) {
|
||||
this(quads, transform, flags, commandIndex, matrixGeneration, stateGeneration, null);
|
||||
}
|
||||
|
||||
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration, int stateGeneration, RestoreData restoreData) {
|
||||
// Deep copy quads - pooled quads are reused after callback
|
||||
this.quads = ModelQuadUtil.deepCopyQuads(quads);
|
||||
|
||||
this.transform = new Matrix4f(transform); // Snapshot for runtime application
|
||||
this.flags = flags;
|
||||
this.commandIndex = commandIndex;
|
||||
this.matrixGeneration = matrixGeneration;
|
||||
this.stateGeneration = stateGeneration;
|
||||
this.restoreData = restoreData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("AccumulatedDraw[quads=%d, cmdIdx=%d, matrixGen=%d, stateGen=%d, flags=C%dT%dL%dN%d, restore=%s]",
|
||||
quads.size(), commandIndex, matrixGeneration, stateGeneration, flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0, flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0,
|
||||
restoreData != null ? "yes" : "no");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an accumulated line draw call during display list compilation.
|
||||
* Similar to AccumulatedDraw but for line primitives.
|
||||
* Stores untransformed line vertices + captured matrix.
|
||||
*/
|
||||
public class AccumulatedLineDraw {
|
||||
public final List<ImmediateModeRecorder.LineVertex> lines;
|
||||
public final Matrix4f transform;
|
||||
public final CapturingTessellator.Flags flags;
|
||||
public final int commandIndex; // Position in command list for state tracking
|
||||
public final int stateGeneration; // Same value = no state commands between draws, can merge
|
||||
|
||||
public AccumulatedLineDraw(List<ImmediateModeRecorder.LineVertex> lines, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex) {
|
||||
this(lines, transform, flags, commandIndex, 0);
|
||||
}
|
||||
|
||||
public AccumulatedLineDraw(List<ImmediateModeRecorder.LineVertex> lines, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int stateGeneration) {
|
||||
// Copy lines - the source list may be cleared
|
||||
this.lines = new ArrayList<>(lines);
|
||||
this.transform = new Matrix4f(transform); // Snapshot for runtime application
|
||||
this.flags = flags;
|
||||
this.commandIndex = commandIndex;
|
||||
this.stateGeneration = stateGeneration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("AccumulatedLineDraw[lines=%d, cmdIdx=%d, flags=C%dT%dL%dN%d]",
|
||||
lines.size() / 2, commandIndex, flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0, flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.line.ModelLine;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.tri.ModelTriangle;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an accumulated primitive draw call during display list compilation.
|
||||
* Stores primitives (lines, triangles) from tessellator callback with their transform.
|
||||
* <p>
|
||||
* Unlike AccumulatedLineDraw which stores ImmediateModeRecorder.LineVertex (16-byte: pos+color),
|
||||
* this stores ModelPrimitiveView (32-byte: pos+color+tex+light+normal) from the tessellator.
|
||||
*/
|
||||
public class AccumulatedPrimitiveDraw {
|
||||
public final List<ModelPrimitiveView> primitives;
|
||||
public final Matrix4f transform;
|
||||
public final CapturingTessellator.Flags flags;
|
||||
public final int commandIndex;
|
||||
public final int matrixGeneration; // For batching: same generation = same transform context
|
||||
public final int stateGeneration; // Same value = no state commands between draws, can merge
|
||||
|
||||
public AccumulatedPrimitiveDraw(List<ModelPrimitiveView> primitives, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration) {
|
||||
this(primitives, transform, flags, commandIndex, matrixGeneration, 0);
|
||||
}
|
||||
|
||||
public AccumulatedPrimitiveDraw(List<ModelPrimitiveView> primitives, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration, int stateGeneration) {
|
||||
// Deep copy primitives - pooled primitives are reused after callback
|
||||
this.primitives = deepCopyPrimitives(primitives);
|
||||
this.transform = new Matrix4f(transform);
|
||||
this.flags = flags;
|
||||
this.commandIndex = commandIndex;
|
||||
this.matrixGeneration = matrixGeneration;
|
||||
this.stateGeneration = stateGeneration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates deep copies of all primitives in the list.
|
||||
* Required because CapturingTessellator pools and reuses primitive objects.
|
||||
*/
|
||||
private static List<ModelPrimitiveView> deepCopyPrimitives(List<ModelPrimitiveView> source) {
|
||||
final int size = source.size();
|
||||
final List<ModelPrimitiveView> copies = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
final ModelPrimitiveView prim = source.get(i);
|
||||
if (prim instanceof ModelLine ml) {
|
||||
copies.add(new ModelLine().copyFrom(ml));
|
||||
} else if (prim instanceof ModelTriangle mt) {
|
||||
copies.add(new ModelTriangle().copyFrom(mt));
|
||||
}
|
||||
}
|
||||
return copies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("AccumulatedPrimitiveDraw[primitives=%d, cmdIdx=%d, matrixGen=%d, stateGen=%d, mode=%d, flags=C%dT%dL%dN%d]",
|
||||
primitives.size(), commandIndex, matrixGeneration, stateGeneration, flags.drawMode,
|
||||
flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0,
|
||||
flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,764 @@
|
||||
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
|
||||
|
||||
// Flag bits for DRAW_RANGE_RESTORE command
|
||||
public static final int FLAG_HAS_BRIGHTNESS = 1 << 0;
|
||||
public static final int FLAG_HAS_COLOR = 1 << 1;
|
||||
public static final int FLAG_HAS_NORMALS = 1 << 2;
|
||||
public static final int FLAG_HAS_TEXTURE = 1 << 3;
|
||||
|
||||
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 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 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));
|
||||
}
|
||||
}
|
||||
|
||||
// === Draw commands ===
|
||||
|
||||
public void writeDrawRange(int vboIndex, int startVertex, int vertexCount, boolean hasBrightness) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.DRAW_RANGE);
|
||||
writeInt(vboIndex);
|
||||
writeInt(startVertex);
|
||||
writeInt(vertexCount);
|
||||
writeInt(hasBrightness ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param startVertex First vertex to draw
|
||||
* @param vertexCount Number of vertices to draw
|
||||
* @param hasBrightness True if VBO has brightness/lightmap data
|
||||
* @param hasColor True if color should be restored after draw
|
||||
* @param hasNormals True if normal should be restored after draw
|
||||
* @param hasTexture True if texcoord should be restored after draw
|
||||
* @param lastColorR Last vertex's red color component
|
||||
* @param lastColorG Last vertex's green color component
|
||||
* @param lastColorB Last vertex's blue color component
|
||||
* @param lastColorA Last vertex's alpha color component
|
||||
* @param lastNormalX Last vertex's normal X
|
||||
* @param lastNormalY Last vertex's normal Y
|
||||
* @param lastNormalZ Last vertex's normal Z
|
||||
* @param lastTexS Last vertex's texture S coordinate
|
||||
* @param lastTexT Last vertex's texture T coordinate
|
||||
*/
|
||||
public void writeDrawRangeRestore(
|
||||
int vboIndex, int startVertex, int vertexCount,
|
||||
boolean hasBrightness, boolean hasColor, boolean hasNormals, boolean hasTexture,
|
||||
float lastColorR, float lastColorG, float lastColorB, float lastColorA,
|
||||
float lastNormalX, float lastNormalY, float lastNormalZ,
|
||||
float lastTexS, float lastTexT
|
||||
) {
|
||||
ensureCapacity(56);
|
||||
writeInt(GLCommand.DRAW_RANGE_RESTORE);
|
||||
writeInt(vboIndex);
|
||||
writeInt(startVertex);
|
||||
writeInt(vertexCount);
|
||||
int flags = (hasBrightness ? FLAG_HAS_BRIGHTNESS : 0)
|
||||
| (hasColor ? FLAG_HAS_COLOR : 0)
|
||||
| (hasNormals ? FLAG_HAS_NORMALS : 0)
|
||||
| (hasTexture ? FLAG_HAS_TEXTURE : 0);
|
||||
writeInt(flags);
|
||||
// Last color (4 floats = 16 bytes)
|
||||
writeFloat(lastColorR);
|
||||
writeFloat(lastColorG);
|
||||
writeFloat(lastColorB);
|
||||
writeFloat(lastColorA);
|
||||
// Last normal (3 floats = 12 bytes)
|
||||
writeFloat(lastNormalX);
|
||||
writeFloat(lastNormalY);
|
||||
writeFloat(lastNormalZ);
|
||||
// Last texcoord (2 floats = 8 bytes)
|
||||
writeFloat(lastTexS);
|
||||
writeFloat(lastTexT);
|
||||
}
|
||||
|
||||
public void writeCallList(int listId) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CALL_LIST);
|
||||
writeInt(listId);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,528 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
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 java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
import static com.gtnewhorizons.angelica.glsm.recording.CommandBuffer.*;
|
||||
|
||||
/**
|
||||
* 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 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, VertexBuffer[] 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.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);
|
||||
}
|
||||
|
||||
// === Draw commands ===
|
||||
case GLCommand.DRAW_RANGE -> {
|
||||
final int vboIndex = memGetInt(ptr);
|
||||
final int start = memGetInt(ptr + 4);
|
||||
final int count = memGetInt(ptr + 8);
|
||||
final boolean hasBrightness = memGetInt(ptr + 12) != 0;
|
||||
ptr += 16;
|
||||
final VertexBuffer vbo = ownedVbos[vboIndex];
|
||||
vbo.setupState();
|
||||
vbo.draw(start, count);
|
||||
vbo.cleanupState();
|
||||
}
|
||||
case GLCommand.DRAW_RANGE_RESTORE -> {
|
||||
// Draw VBO range then restore GL current state from last vertex attributes
|
||||
final int vboIndex = memGetInt(ptr);
|
||||
final int start = memGetInt(ptr + 4);
|
||||
final int count = memGetInt(ptr + 8);
|
||||
final int flags = memGetInt(ptr + 12);
|
||||
|
||||
// Draw the VBO
|
||||
final VertexBuffer vbo = ownedVbos[vboIndex];
|
||||
vbo.setupState();
|
||||
vbo.draw(start, count);
|
||||
vbo.cleanupState();
|
||||
|
||||
// Restore attributes based on flags
|
||||
if ((flags & FLAG_HAS_COLOR) != 0) {
|
||||
final float r = memGetFloat(ptr + 16);
|
||||
final float g = memGetFloat(ptr + 20);
|
||||
final float b = memGetFloat(ptr + 24);
|
||||
final float a = memGetFloat(ptr + 28);
|
||||
GLStateManager.glColor4f(r, g, b, a);
|
||||
}
|
||||
if ((flags & FLAG_HAS_NORMALS) != 0) {
|
||||
final float nx = memGetFloat(ptr + 32);
|
||||
final float ny = memGetFloat(ptr + 36);
|
||||
final float nz = memGetFloat(ptr + 40);
|
||||
GLStateManager.glNormal3f(nx, ny, nz);
|
||||
}
|
||||
if ((flags & FLAG_HAS_TEXTURE) != 0) {
|
||||
final float s = memGetFloat(ptr + 44);
|
||||
final float t = memGetFloat(ptr + 48);
|
||||
GLStateManager.glTexCoord2f(s, t);
|
||||
}
|
||||
ptr += 52; // Skip full command size (56 - 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);
|
||||
}
|
||||
|
||||
// === 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,316 @@
|
||||
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() {}
|
||||
|
||||
public static void processCommandForOptimization(int opcode, CommandBuffer raw, BufferTransformOptimizer opt, CommandBuffer out) {
|
||||
switch (opcode) {
|
||||
// === MultMatrix - already collapsed transforms from flushMatrix() ===
|
||||
case GLCommand.MULT_MATRIX -> {
|
||||
raw.readMatrix4f(tempMatrix);
|
||||
opt.multMatrix(tempMatrix); // accumulated = accumulated * tempMatrix
|
||||
out.writeMultMatrix(tempMatrix); // Copy to output
|
||||
opt.markLastEmittedAsAccumulated(); // lastEmitted = accumulated (what GL now has)
|
||||
}
|
||||
|
||||
// === Load commands - flush pending, emit, reset accumulator ===
|
||||
case GLCommand.LOAD_IDENTITY -> {
|
||||
opt.emitPendingTransform(out); // Emit any pending transforms first
|
||||
out.writeLoadIdentity(); // Always emit LoadIdentity
|
||||
opt.loadIdentity(); // Reset accumulated to identity
|
||||
opt.resetLastEmitted(); // Reset lastEmitted to identity
|
||||
opt.checkAndClearAbsoluteMatrix(); // Clear the flag if set
|
||||
}
|
||||
case GLCommand.LOAD_MATRIX -> {
|
||||
raw.readMatrix4f(tempMatrix);
|
||||
opt.emitPendingTransform(out);
|
||||
out.writeLoadMatrix(tempMatrix);
|
||||
opt.loadIdentity();
|
||||
opt.resetLastEmitted(); // Reset lastEmitted to identity
|
||||
opt.markAbsoluteMatrix();
|
||||
}
|
||||
|
||||
// === Stack operations - flush/update optimizer state and copy ===
|
||||
case GLCommand.PUSH_MATRIX -> {
|
||||
opt.emitPendingTransform(out);
|
||||
opt.push();
|
||||
// Reset accumulated/lastEmitted to identity after push.
|
||||
opt.loadIdentity();
|
||||
opt.resetLastEmitted();
|
||||
out.writePushMatrix();
|
||||
}
|
||||
case GLCommand.POP_MATRIX -> {
|
||||
opt.pop();
|
||||
out.writePopMatrix();
|
||||
}
|
||||
|
||||
// === Barriers - emit pending transform before copying ===
|
||||
case GLCommand.MATRIX_MODE -> {
|
||||
final int mode = raw.readInt();
|
||||
opt.emitPendingTransform(out); // Pending transforms apply to OLD mode
|
||||
out.writeMatrixMode(mode);
|
||||
opt.loadIdentity(); // Reset accumulated for new mode
|
||||
opt.resetLastEmitted(); // Reset lastEmitted for new mode
|
||||
}
|
||||
case GLCommand.CALL_LIST -> {
|
||||
final int listId = raw.readInt();
|
||||
opt.emitPendingTransform(out);
|
||||
out.writeCallList(listId);
|
||||
}
|
||||
case GLCommand.PUSH_ATTRIB -> {
|
||||
final int mask = raw.readInt();
|
||||
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
|
||||
opt.emitPendingTransform(out);
|
||||
}
|
||||
out.writePushAttrib(mask);
|
||||
}
|
||||
|
||||
// === All other commands - just copy ===
|
||||
default -> copyCommandData(opcode, raw, out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
|
||||
// Draw/call commands
|
||||
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt() != 0);
|
||||
case GLCommand.CALL_LIST -> out.writeCallList(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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight transform optimizer for buffer-to-buffer processing.
|
||||
* Tracks accumulated transform (mode-agnostic) and emits MultMatrix commands when needed.
|
||||
*
|
||||
* <p>This optimizer processes raw transform commands (TRANSLATE, ROTATE, SCALE) and
|
||||
* collapses them into single MultMatrix commands at barriers. It must stay synchronized
|
||||
* with the relativeTransform tracking in DisplayListManager - both systems must reset
|
||||
* at the same barriers (MatrixMode, LoadIdentity, LoadMatrix, Push/Pop).</p>
|
||||
*/
|
||||
public static class BufferTransformOptimizer {
|
||||
private final Matrix4f accumulated = new Matrix4f();
|
||||
private final Matrix4f lastEmitted = new Matrix4f();
|
||||
private final Matrix4f delta = new Matrix4f(); // Reused for delta computation
|
||||
private final Deque<Matrix4f> stack = new ArrayDeque<>();
|
||||
private boolean absoluteMatrixFlag = false;
|
||||
|
||||
public BufferTransformOptimizer() {
|
||||
accumulated.identity();
|
||||
lastEmitted.identity();
|
||||
}
|
||||
|
||||
public void multMatrix(Matrix4f m) {
|
||||
accumulated.mul(m);
|
||||
}
|
||||
|
||||
public void loadIdentity() {
|
||||
accumulated.identity();
|
||||
}
|
||||
|
||||
public void push() {
|
||||
stack.push(new Matrix4f(accumulated));
|
||||
}
|
||||
|
||||
public void pop() {
|
||||
if (!stack.isEmpty()) {
|
||||
accumulated.set(stack.pop());
|
||||
lastEmitted.set(accumulated);
|
||||
} else {
|
||||
accumulated.identity();
|
||||
lastEmitted.identity();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isIdentity() {
|
||||
return DisplayListManager.isIdentity(accumulated);
|
||||
}
|
||||
|
||||
public void markAbsoluteMatrix() {
|
||||
absoluteMatrixFlag = true;
|
||||
}
|
||||
|
||||
public boolean checkAndClearAbsoluteMatrix() {
|
||||
final boolean was = absoluteMatrixFlag;
|
||||
absoluteMatrixFlag = false;
|
||||
return was;
|
||||
}
|
||||
|
||||
public void resetLastEmitted() {
|
||||
lastEmitted.identity();
|
||||
}
|
||||
|
||||
public void setLastEmitted(Matrix4f m) {
|
||||
lastEmitted.set(m);
|
||||
}
|
||||
|
||||
public void markLastEmittedAsAccumulated() {
|
||||
lastEmitted.set(accumulated);
|
||||
}
|
||||
|
||||
public void emitPendingTransform(CommandBuffer out) {
|
||||
if (!accumulated.equals(lastEmitted)) {
|
||||
emitTransformTo(out, accumulated);
|
||||
}
|
||||
}
|
||||
|
||||
public void emitTransformTo(CommandBuffer out, Matrix4f target) {
|
||||
if (target.equals(lastEmitted)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DisplayListManager.isIdentity(lastEmitted)) {
|
||||
out.writeMultMatrix(target);
|
||||
} else {
|
||||
delta.set(lastEmitted).invert().mul(target);
|
||||
out.writeMultMatrix(delta);
|
||||
}
|
||||
lastEmitted.set(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
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 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 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 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 recordComplexCommand(DisplayListCommand cmd) {
|
||||
buffer.writeComplexRef(cmd);
|
||||
commandCount++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
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 VertexBuffer[] ownedVbos; // GPU resources referenced by index
|
||||
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, VertexBuffer[] 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) {
|
||||
for (VertexBuffer vbo : ownedVbos) {
|
||||
if (vbo != null) {
|
||||
vbo.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get owned VBOs for inlining into another display list.
|
||||
*/
|
||||
public VertexBuffer[] 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 += 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 += getCommandSize(cmd, ptr);
|
||||
}
|
||||
return opcodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get size of a command in bytes (including the opcode).
|
||||
*/
|
||||
private static int getCommandSize(int cmd, long ptr) {
|
||||
return switch (cmd) {
|
||||
// Opcode-only commands (4 bytes)
|
||||
case GLCommand.LOAD_IDENTITY, GLCommand.PUSH_MATRIX, GLCommand.POP_MATRIX -> 4;
|
||||
|
||||
// Single int commands (8 bytes)
|
||||
case GLCommand.ENABLE, GLCommand.DISABLE, GLCommand.CLEAR, GLCommand.CLEAR_STENCIL,
|
||||
GLCommand.CULL_FACE, GLCommand.DEPTH_FUNC, GLCommand.SHADE_MODEL, GLCommand.LOGIC_OP,
|
||||
GLCommand.MATRIX_MODE, GLCommand.ACTIVE_TEXTURE, GLCommand.USE_PROGRAM,
|
||||
GLCommand.PUSH_ATTRIB, GLCommand.POP_ATTRIB, GLCommand.STENCIL_MASK,
|
||||
GLCommand.DEPTH_MASK, GLCommand.FRONT_FACE, GLCommand.POINT_SIZE, GLCommand.LINE_WIDTH,
|
||||
GLCommand.CALL_LIST, GLCommand.COMPLEX_REF -> 8;
|
||||
|
||||
// Two int commands (12 bytes)
|
||||
case GLCommand.BIND_TEXTURE, GLCommand.POLYGON_MODE, GLCommand.COLOR_MATERIAL,
|
||||
GLCommand.LINE_STIPPLE, GLCommand.STENCIL_MASK_SEPARATE, GLCommand.FOGI,
|
||||
GLCommand.HINT, GLCommand.POLYGON_OFFSET, GLCommand.ALPHA_FUNC, GLCommand.FOGF,
|
||||
GLCommand.LIGHT_MODELF, GLCommand.LIGHT_MODELI -> 12;
|
||||
|
||||
// Three int commands (16 bytes)
|
||||
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
|
||||
GLCommand.LIGHTF, GLCommand.LIGHTI,
|
||||
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
|
||||
|
||||
// Four int commands (20 bytes)
|
||||
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
|
||||
GLCommand.STENCIL_FUNC_SEPARATE, GLCommand.STENCIL_OP_SEPARATE,
|
||||
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR,
|
||||
GLCommand.DRAW_RANGE -> 20;
|
||||
|
||||
// DRAW_RANGE_RESTORE: 56 bytes
|
||||
// [cmd:4][vboIndex:4][start:4][count:4][flags:4][color:16f][normal:12f][texcoord:8f]
|
||||
case GLCommand.DRAW_RANGE_RESTORE -> 56;
|
||||
|
||||
// DRAW_BUFFER: 8 bytes [cmd:4][mode:4]
|
||||
case GLCommand.DRAW_BUFFER -> 8;
|
||||
|
||||
// DRAW_BUFFERS: 40 bytes [cmd:4][count:4][bufs:4*8]
|
||||
case GLCommand.DRAW_BUFFERS -> 40;
|
||||
|
||||
// Double commands
|
||||
case GLCommand.TRANSLATE, GLCommand.SCALE -> 28; // cmd + 3 doubles
|
||||
case GLCommand.CLEAR_DEPTH -> 12; // cmd + 1 double
|
||||
case GLCommand.ROTATE -> 36; // cmd + 4 doubles
|
||||
case GLCommand.ORTHO, GLCommand.FRUSTUM -> 52; // cmd + 6 doubles
|
||||
|
||||
// Matrix commands
|
||||
case GLCommand.MULT_MATRIX, GLCommand.LOAD_MATRIX -> 68; // cmd + 16 floats
|
||||
|
||||
// Buffer commands (variable, read count)
|
||||
case GLCommand.FOG, GLCommand.LIGHT_MODEL -> {
|
||||
final int count = memGetInt(ptr + 8); // pname at +4, count at +8
|
||||
yield 12 + count * 4;
|
||||
}
|
||||
case GLCommand.LIGHT, GLCommand.MATERIAL -> {
|
||||
final int count = memGetInt(ptr + 12); // light/face at +4, pname at +8, count at +12
|
||||
yield 16 + count * 4;
|
||||
}
|
||||
|
||||
default -> throw new IllegalStateException("Unknown command: " + cmd);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
/**
|
||||
* Command opcodes for ByteBuffer-based display list serialization.
|
||||
* Each command has a fixed layout described in comments.
|
||||
* All values are 4-byte aligned.
|
||||
*/
|
||||
public final class GLCommand {
|
||||
private GLCommand() {}
|
||||
|
||||
// === Single int commands (8 bytes: opcode + param) ===
|
||||
public static final int ENABLE = 1; // [cmd:4][cap:4]
|
||||
public static final int DISABLE = 2; // [cmd:4][cap:4]
|
||||
public static final int CLEAR = 3; // [cmd:4][mask:4]
|
||||
public static final int CLEAR_STENCIL = 4; // [cmd:4][s:4]
|
||||
public static final int CULL_FACE = 5; // [cmd:4][mode:4]
|
||||
public static final int DEPTH_FUNC = 6; // [cmd:4][func:4]
|
||||
public static final int SHADE_MODEL = 7; // [cmd:4][mode:4]
|
||||
public static final int LOGIC_OP = 8; // [cmd:4][opcode:4]
|
||||
public static final int MATRIX_MODE = 9; // [cmd:4][mode:4]
|
||||
public static final int ACTIVE_TEXTURE = 10; // [cmd:4][texture:4]
|
||||
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
|
||||
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
|
||||
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
|
||||
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
|
||||
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
|
||||
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
|
||||
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
|
||||
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
|
||||
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
|
||||
|
||||
// === Two int commands (12 bytes) ===
|
||||
public static final int BIND_TEXTURE = 20; // [cmd:4][target:4][texture:4]
|
||||
public static final int POLYGON_MODE = 21; // [cmd:4][face:4][mode:4]
|
||||
public static final int COLOR_MATERIAL = 22; // [cmd:4][face:4][mode:4]
|
||||
public static final int LINE_STIPPLE = 23; // [cmd:4][factor:4][pattern:4]
|
||||
public static final int STENCIL_MASK_SEPARATE = 24; // [cmd:4][face:4][mask:4]
|
||||
public static final int FOGI = 25; // [cmd:4][pname:4][param:4]
|
||||
public static final int HINT = 26; // [cmd:4][target:4][mode:4]
|
||||
|
||||
// === Three int commands (16 bytes) ===
|
||||
public static final int STENCIL_FUNC = 30; // [cmd:4][func:4][ref:4][mask:4]
|
||||
public static final int STENCIL_OP = 31; // [cmd:4][fail:4][zfail:4][zpass:4]
|
||||
public static final int TEX_PARAMETERI = 32; // [cmd:4][target:4][pname:4][param:4]
|
||||
|
||||
// === Four int commands (20 bytes) ===
|
||||
public static final int VIEWPORT = 40; // [cmd:4][x:4][y:4][width:4][height:4]
|
||||
public static final int BLEND_FUNC = 41; // [cmd:4][srcRgb:4][dstRgb:4][srcAlpha:4][dstAlpha:4]
|
||||
public static final int COLOR_MASK = 42; // [cmd:4][r:4][g:4][b:4][a:4] (0 or 1 each)
|
||||
public static final int STENCIL_FUNC_SEPARATE = 43; // [cmd:4][face:4][func:4][ref:4][mask:4]
|
||||
public static final int STENCIL_OP_SEPARATE = 44; // [cmd:4][face:4][sfail:4][dpfail:4][dppass:4]
|
||||
|
||||
// === Float commands ===
|
||||
public static final int POINT_SIZE = 50; // [cmd:4][size:4f]
|
||||
public static final int LINE_WIDTH = 51; // [cmd:4][width:4f]
|
||||
public static final int POLYGON_OFFSET = 52; // [cmd:4][factor:4f][units:4f]
|
||||
public static final int NORMAL = 53; // [cmd:4][x:4f][y:4f][z:4f]
|
||||
public static final int COLOR = 54; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
|
||||
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
|
||||
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
|
||||
|
||||
// === Mixed int+float commands ===
|
||||
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
|
||||
public static final int FOGF = 61; // [cmd:4][pname:4][param:4f]
|
||||
public static final int LIGHTF = 62; // [cmd:4][light:4][pname:4][param:4f]
|
||||
public static final int LIGHT_MODELF = 63; // [cmd:4][pname:4][param:4f]
|
||||
public static final int LIGHTI = 64; // [cmd:4][light:4][pname:4][param:4]
|
||||
public static final int LIGHT_MODELI = 65; // [cmd:4][pname:4][param:4]
|
||||
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
|
||||
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
|
||||
|
||||
// === Double commands ===
|
||||
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
|
||||
public static final int ROTATE = 71; // [cmd:4][mode:4][angle:8d][x:8d][y:8d][z:8d] = 40 bytes
|
||||
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
|
||||
public static final int ORTHO = 73; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
|
||||
public static final int FRUSTUM = 74; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
|
||||
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
|
||||
|
||||
// === Matrix commands (72 bytes) ===
|
||||
public static final int MULT_MATRIX = 80; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
|
||||
public static final int LOAD_MATRIX = 81; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
|
||||
|
||||
// === Buffer commands (inline 4 floats max) ===
|
||||
public static final int FOG = 90; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
|
||||
public static final int LIGHT = 91; // [cmd:4][light:4][pname:4][count:4][params:16f] = 32 bytes
|
||||
public static final int LIGHT_MODEL = 92; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
|
||||
public static final int MATERIAL = 93; // [cmd:4][face:4][pname:4][count:4][params:16f] = 32 bytes
|
||||
|
||||
// === Draw commands ===
|
||||
public static final int DRAW_RANGE = 100; // [cmd:4][vboIndex:4][start:4][count:4][hasBrightness:4] = 20 bytes
|
||||
public static final int CALL_LIST = 101; // [cmd:4][listId:4] = 8 bytes
|
||||
public static final int DRAW_BUFFER = 102; // [cmd:4][mode:4] = 8 bytes
|
||||
public static final int DRAW_BUFFERS = 103; // [cmd:4][count:4][bufs:4*8] = 40 bytes (up to 8 buffers)
|
||||
/**
|
||||
* Draw VBO range with attribute restoration after draw.
|
||||
* Used for immediate mode VBOs to restore GL current state (color, normal, texcoord).
|
||||
*/
|
||||
public static final int DRAW_RANGE_RESTORE = 104; // [cmd:4][vboIndex:4][start:4][count:4][flags:4]
|
||||
// [lastColor:16f][lastNormal:12f][lastTexCoord:8f] = 56 bytes
|
||||
|
||||
// === Complex object reference ===
|
||||
public static final int COMPLEX_REF = 255; // [cmd:4][index:4] = 8 bytes
|
||||
|
||||
/**
|
||||
* Get a human-readable name for an opcode (for debugging).
|
||||
*/
|
||||
public static String getName(int opcode) {
|
||||
return switch (opcode) {
|
||||
case ENABLE -> "ENABLE";
|
||||
case DISABLE -> "DISABLE";
|
||||
case CLEAR -> "CLEAR";
|
||||
case CLEAR_STENCIL -> "CLEAR_STENCIL";
|
||||
case CULL_FACE -> "CULL_FACE";
|
||||
case DEPTH_FUNC -> "DEPTH_FUNC";
|
||||
case SHADE_MODEL -> "SHADE_MODEL";
|
||||
case LOGIC_OP -> "LOGIC_OP";
|
||||
case MATRIX_MODE -> "MATRIX_MODE";
|
||||
case ACTIVE_TEXTURE -> "ACTIVE_TEXTURE";
|
||||
case USE_PROGRAM -> "USE_PROGRAM";
|
||||
case PUSH_ATTRIB -> "PUSH_ATTRIB";
|
||||
case POP_ATTRIB -> "POP_ATTRIB";
|
||||
case LOAD_IDENTITY -> "LOAD_IDENTITY";
|
||||
case PUSH_MATRIX -> "PUSH_MATRIX";
|
||||
case POP_MATRIX -> "POP_MATRIX";
|
||||
case STENCIL_MASK -> "STENCIL_MASK";
|
||||
case DEPTH_MASK -> "DEPTH_MASK";
|
||||
case FRONT_FACE -> "FRONT_FACE";
|
||||
case BIND_TEXTURE -> "BIND_TEXTURE";
|
||||
case POLYGON_MODE -> "POLYGON_MODE";
|
||||
case COLOR_MATERIAL -> "COLOR_MATERIAL";
|
||||
case LINE_STIPPLE -> "LINE_STIPPLE";
|
||||
case STENCIL_MASK_SEPARATE -> "STENCIL_MASK_SEPARATE";
|
||||
case FOGI -> "FOGI";
|
||||
case HINT -> "HINT";
|
||||
case STENCIL_FUNC -> "STENCIL_FUNC";
|
||||
case STENCIL_OP -> "STENCIL_OP";
|
||||
case TEX_PARAMETERI -> "TEX_PARAMETERI";
|
||||
case VIEWPORT -> "VIEWPORT";
|
||||
case BLEND_FUNC -> "BLEND_FUNC";
|
||||
case COLOR_MASK -> "COLOR_MASK";
|
||||
case STENCIL_FUNC_SEPARATE -> "STENCIL_FUNC_SEPARATE";
|
||||
case STENCIL_OP_SEPARATE -> "STENCIL_OP_SEPARATE";
|
||||
case POINT_SIZE -> "POINT_SIZE";
|
||||
case LINE_WIDTH -> "LINE_WIDTH";
|
||||
case POLYGON_OFFSET -> "POLYGON_OFFSET";
|
||||
case COLOR -> "COLOR";
|
||||
case CLEAR_COLOR -> "CLEAR_COLOR";
|
||||
case BLEND_COLOR -> "BLEND_COLOR";
|
||||
case ALPHA_FUNC -> "ALPHA_FUNC";
|
||||
case FOGF -> "FOGF";
|
||||
case LIGHTF -> "LIGHTF";
|
||||
case LIGHT_MODELF -> "LIGHT_MODELF";
|
||||
case LIGHTI -> "LIGHTI";
|
||||
case LIGHT_MODELI -> "LIGHT_MODELI";
|
||||
case MATERIALF -> "MATERIALF";
|
||||
case TEX_PARAMETERF -> "TEX_PARAMETERF";
|
||||
case TRANSLATE -> "TRANSLATE";
|
||||
case ROTATE -> "ROTATE";
|
||||
case SCALE -> "SCALE";
|
||||
case ORTHO -> "ORTHO";
|
||||
case FRUSTUM -> "FRUSTUM";
|
||||
case CLEAR_DEPTH -> "CLEAR_DEPTH";
|
||||
case MULT_MATRIX -> "MULT_MATRIX";
|
||||
case LOAD_MATRIX -> "LOAD_MATRIX";
|
||||
case FOG -> "FOG";
|
||||
case LIGHT -> "LIGHT";
|
||||
case LIGHT_MODEL -> "LIGHT_MODEL";
|
||||
case MATERIAL -> "MATERIAL";
|
||||
case DRAW_RANGE -> "DRAW_RANGE";
|
||||
case CALL_LIST -> "CALL_LIST";
|
||||
case DRAW_BUFFER -> "DRAW_BUFFER";
|
||||
case DRAW_BUFFERS -> "DRAW_BUFFERS";
|
||||
case DRAW_RANGE_RESTORE -> "DRAW_RANGE_RESTORE";
|
||||
case COMPLEX_REF -> "COMPLEX_REF";
|
||||
default -> "UNKNOWN(" + opcode + ")";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,750 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuad;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Records immediate mode GL calls (glBegin/glEnd/glVertex) during display list compilation.
|
||||
* Converts immediate mode geometry to ModelQuad format for VBO compilation.
|
||||
*
|
||||
* <p>Tracks current immediate mode state:
|
||||
* <ul>
|
||||
* <li>TexCoord: Set by glTexCoord* calls</li>
|
||||
* <li>Normal: Set by glNormal* calls</li>
|
||||
* <li>Color: Read from GLStateManager.getColor() at vertex emission time</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Supported primitive types:
|
||||
* <ul>
|
||||
* <li>GL_QUADS: 4 vertices per quad (native support)</li>
|
||||
* <li>GL_TRIANGLES: 3 vertices per triangle (converted to degenerate quad)</li>
|
||||
* <li>GL_TRIANGLE_FAN: Fan of triangles (converted to individual quads)</li>
|
||||
* <li>GL_TRIANGLE_STRIP: Strip of triangles (converted to individual quads)</li>
|
||||
* <li>GL_QUAD_STRIP: Strip of quads (converted to individual quads)</li>
|
||||
* <li>GL_POLYGON: Polygon (treated as triangle fan)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Unsupported primitive types (logged warning, geometry discarded):
|
||||
* <ul>
|
||||
* <li>GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP: Line primitives</li>
|
||||
* <li>GL_POINTS: Point primitives</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ImmediateModeRecorder {
|
||||
private static final Logger LOGGER = LogManager.getLogger("ImmediateModeRecorder");
|
||||
|
||||
// Accumulated quads from completed primitives
|
||||
private final List<ModelQuadViewMutable> quads = new ArrayList<>();
|
||||
|
||||
// Accumulated line vertices (pairs of vertices for GL_LINES)
|
||||
private final List<LineVertex> lineVertices = new ArrayList<>();
|
||||
|
||||
// Current primitive state
|
||||
private int currentPrimitiveType = -1;
|
||||
private boolean inPrimitive = false;
|
||||
|
||||
// Vertices for current primitive (cleared on glEnd)
|
||||
private final List<ImmediateVertex> currentVertices = new ArrayList<>();
|
||||
|
||||
// Current immediate mode state (set by glTexCoord*, glNormal*)
|
||||
private float texCoordS = 0.0f;
|
||||
private float texCoordT = 0.0f;
|
||||
private float normalX = 0.0f;
|
||||
private float normalY = 0.0f;
|
||||
private float normalZ = 1.0f; // Default normal points +Z
|
||||
|
||||
// Track which attributes have been set (for flags)
|
||||
private boolean hasTexCoord = false;
|
||||
private boolean hasNormal = false;
|
||||
private boolean hasColor = false;
|
||||
|
||||
/**
|
||||
* Represents a single vertex captured during immediate mode.
|
||||
*/
|
||||
private static class ImmediateVertex {
|
||||
float x, y, z; // Position
|
||||
int color; // Packed ABGR color
|
||||
float texU, texV; // Texture coordinates
|
||||
int normal; // Packed normal
|
||||
|
||||
ImmediateVertex(float x, float y, float z, int color, float texU, float texV, int normal) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.color = color;
|
||||
this.texU = texU;
|
||||
this.texV = texV;
|
||||
this.normal = normal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a vertex for line primitives.
|
||||
* Simpler than ImmediateVertex - just position and color.
|
||||
*/
|
||||
public static class LineVertex {
|
||||
public final float x, y, z;
|
||||
public final int color; // Packed ABGR color
|
||||
|
||||
public LineVertex(float x, float y, float z, int color) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public ImmediateModeRecorder() {
|
||||
// Recorder is ready to capture geometry
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current texture coordinate.
|
||||
* Called by GLStateManager.glTexCoord*.
|
||||
*/
|
||||
public void setTexCoord(float s, float t) {
|
||||
this.texCoordS = s;
|
||||
this.texCoordT = t;
|
||||
this.hasTexCoord = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current normal vector.
|
||||
* Called by GLStateManager.glNormal*.
|
||||
*/
|
||||
public void setNormal(float x, float y, float z) {
|
||||
this.normalX = x;
|
||||
this.normalY = y;
|
||||
this.normalZ = z;
|
||||
this.hasNormal = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently in a primitive (between glBegin and glEnd).
|
||||
*/
|
||||
public boolean isInPrimitive() {
|
||||
return inPrimitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording a new primitive.
|
||||
*/
|
||||
public void begin(int primitiveType) {
|
||||
if (inPrimitive) {
|
||||
throw new IllegalStateException("glBegin called while already in primitive");
|
||||
}
|
||||
this.currentPrimitiveType = primitiveType;
|
||||
this.currentVertices.clear();
|
||||
this.inPrimitive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* End the current primitive, convert to quads/lines, and return them immediately.
|
||||
* This allows the caller to create AccumulatedDraw at the correct command position.
|
||||
*
|
||||
* @return Result containing quads, lines, and flags, or null if no geometry was produced
|
||||
*/
|
||||
public Result end() {
|
||||
if (!inPrimitive) {
|
||||
throw new IllegalStateException("glEnd called without glBegin");
|
||||
}
|
||||
|
||||
// Capture last vertex attributes BEFORE clearing currentVertices
|
||||
// These will be used to restore GL current state after VBO playback
|
||||
float lastColorR = 1.0f, lastColorG = 1.0f, lastColorB = 1.0f, lastColorA = 1.0f;
|
||||
float lastNormalX = 0.0f, lastNormalY = 0.0f, lastNormalZ = 1.0f;
|
||||
float lastTexS = 0.0f, lastTexT = 0.0f;
|
||||
|
||||
if (!currentVertices.isEmpty()) {
|
||||
ImmediateVertex last = currentVertices.get(currentVertices.size() - 1);
|
||||
|
||||
// Unpack color from ABGR format
|
||||
lastColorR = (last.color & 0xFF) / 255.0f;
|
||||
lastColorG = ((last.color >> 8) & 0xFF) / 255.0f;
|
||||
lastColorB = ((last.color >> 16) & 0xFF) / 255.0f;
|
||||
lastColorA = ((last.color >> 24) & 0xFF) / 255.0f;
|
||||
|
||||
// Unpack normal from NormI8 format (signed bytes packed into int)
|
||||
lastNormalX = NormI8.unpackX(last.normal);
|
||||
lastNormalY = NormI8.unpackY(last.normal);
|
||||
lastNormalZ = NormI8.unpackZ(last.normal);
|
||||
|
||||
// Texture coords are stored directly as floats
|
||||
lastTexS = last.texU;
|
||||
lastTexT = last.texV;
|
||||
}
|
||||
|
||||
// Convert this primitive's vertices to quads or lines
|
||||
convertPrimitiveToQuads();
|
||||
this.currentVertices.clear();
|
||||
this.inPrimitive = false;
|
||||
|
||||
// Return geometry from this primitive immediately (don't accumulate)
|
||||
if (quads.isEmpty() && lineVertices.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Copy geometry and create flags
|
||||
List<ModelQuadViewMutable> resultQuads = new ArrayList<>(quads);
|
||||
List<LineVertex> resultLines = new ArrayList<>(lineVertices);
|
||||
CapturingTessellator.Flags flags = new CapturingTessellator.Flags(
|
||||
hasTexCoord, // hasTexture
|
||||
false, // hasBrightness - immediate mode doesn't typically use lightmap
|
||||
hasColor, // hasColor
|
||||
hasNormal // hasNormals
|
||||
);
|
||||
|
||||
// Clear for next primitive (but keep attribute tracking state)
|
||||
quads.clear();
|
||||
lineVertices.clear();
|
||||
|
||||
return new Result(resultQuads, resultLines, flags,
|
||||
lastColorR, lastColorG, lastColorB, lastColorA,
|
||||
lastNormalX, lastNormalY, lastNormalZ,
|
||||
lastTexS, lastTexT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a vertex with current attributes.
|
||||
* Color is read from GLStateManager at call time.
|
||||
*/
|
||||
public void vertex(float x, float y, float z) {
|
||||
if (!inPrimitive) {
|
||||
throw new IllegalStateException("glVertex called outside glBegin/glEnd");
|
||||
}
|
||||
|
||||
// Get current color from GLStateManager
|
||||
int packedColor = packColor(
|
||||
GLStateManager.getColor().getRed(),
|
||||
GLStateManager.getColor().getGreen(),
|
||||
GLStateManager.getColor().getBlue(),
|
||||
GLStateManager.getColor().getAlpha()
|
||||
);
|
||||
if (packedColor != 0xFFFFFFFF) {
|
||||
hasColor = true;
|
||||
}
|
||||
|
||||
// Pack normal
|
||||
int packedNormal = NormI8.pack(normalX, normalY, normalZ);
|
||||
|
||||
currentVertices.add(new ImmediateVertex(
|
||||
x, y, z,
|
||||
packedColor,
|
||||
texCoordS, texCoordT,
|
||||
packedNormal
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack RGBA floats (0-1) into ABGR int format used by ModelQuad.
|
||||
*/
|
||||
private static int packColor(float r, float g, float b, float a) {
|
||||
int ri = (int) (r * 255.0f) & 0xFF;
|
||||
int gi = (int) (g * 255.0f) & 0xFF;
|
||||
int bi = (int) (b * 255.0f) & 0xFF;
|
||||
int ai = (int) (a * 255.0f) & 0xFF;
|
||||
// ABGR format (little-endian: stored as RGBA bytes)
|
||||
return (ai << 24) | (bi << 16) | (gi << 8) | ri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the current primitive vertices to quads.
|
||||
* Handles different primitive types.
|
||||
*/
|
||||
private void convertPrimitiveToQuads() {
|
||||
int vertexCount = currentVertices.size();
|
||||
if (vertexCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (currentPrimitiveType) {
|
||||
case GL11.GL_QUADS -> convertQuads();
|
||||
case GL11.GL_TRIANGLES -> convertTriangles();
|
||||
case GL11.GL_TRIANGLE_FAN, GL11.GL_POLYGON -> convertTriangleFan();
|
||||
case GL11.GL_TRIANGLE_STRIP -> convertTriangleStrip();
|
||||
case GL11.GL_QUAD_STRIP -> convertQuadStrip();
|
||||
case GL11.GL_LINES -> convertLines();
|
||||
case GL11.GL_LINE_STRIP -> convertLineStripToLines();
|
||||
case GL11.GL_LINE_LOOP -> convertLineLoopToLines();
|
||||
case GL11.GL_POINTS -> {
|
||||
LOGGER.warn("[ImmediateModeRecorder] Point primitives not supported, discarding {} vertices", vertexCount);
|
||||
}
|
||||
default -> {
|
||||
LOGGER.warn("[ImmediateModeRecorder] Unknown primitive type {}, discarding {} vertices",
|
||||
currentPrimitiveType, vertexCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GL_QUADS vertices to ModelQuads (4 vertices per quad).
|
||||
*/
|
||||
private void convertQuads() {
|
||||
int vertexCount = currentVertices.size();
|
||||
int fullQuads = vertexCount / 4;
|
||||
|
||||
for (int i = 0; i < fullQuads; i++) {
|
||||
int base = i * 4;
|
||||
addQuad(
|
||||
currentVertices.get(base),
|
||||
currentVertices.get(base + 1),
|
||||
currentVertices.get(base + 2),
|
||||
currentVertices.get(base + 3)
|
||||
);
|
||||
}
|
||||
|
||||
int remaining = vertexCount % 4;
|
||||
if (remaining > 0) {
|
||||
LOGGER.warn("[ImmediateModeRecorder] GL_QUADS: Incomplete quad, discarding {} vertices", remaining);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GL_TRIANGLES vertices to degenerate quads (3 vertices per triangle, duplicate last).
|
||||
*/
|
||||
private void convertTriangles() {
|
||||
int vertexCount = currentVertices.size();
|
||||
int fullTriangles = vertexCount / 3;
|
||||
|
||||
for (int i = 0; i < fullTriangles; i++) {
|
||||
int base = i * 3;
|
||||
ImmediateVertex v0 = currentVertices.get(base);
|
||||
ImmediateVertex v1 = currentVertices.get(base + 1);
|
||||
ImmediateVertex v2 = currentVertices.get(base + 2);
|
||||
// Degenerate quad: duplicate third vertex
|
||||
addQuad(v0, v1, v2, v2);
|
||||
}
|
||||
|
||||
int remaining = vertexCount % 3;
|
||||
if (remaining > 0) {
|
||||
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLES: Incomplete triangle, discarding {} vertices", remaining);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GL_TRIANGLE_FAN vertices to triangles then to degenerate quads.
|
||||
* First vertex is center, subsequent vertices form fan.
|
||||
*/
|
||||
private void convertTriangleFan() {
|
||||
int vertexCount = currentVertices.size();
|
||||
if (vertexCount < 3) {
|
||||
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLE_FAN: Need at least 3 vertices, got {}", vertexCount);
|
||||
return;
|
||||
}
|
||||
|
||||
ImmediateVertex center = currentVertices.get(0);
|
||||
for (int i = 1; i < vertexCount - 1; i++) {
|
||||
ImmediateVertex v1 = currentVertices.get(i);
|
||||
ImmediateVertex v2 = currentVertices.get(i + 1);
|
||||
// Triangle: center, v1, v2 -> degenerate quad
|
||||
addQuad(center, v1, v2, v2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GL_TRIANGLE_STRIP vertices to triangles then to degenerate quads.
|
||||
*/
|
||||
private void convertTriangleStrip() {
|
||||
int vertexCount = currentVertices.size();
|
||||
if (vertexCount < 3) {
|
||||
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLE_STRIP: Need at least 3 vertices, got {}", vertexCount);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertexCount - 2; i++) {
|
||||
ImmediateVertex v0, v1, v2;
|
||||
if ((i & 1) == 0) {
|
||||
// Even triangle: v[i], v[i+1], v[i+2]
|
||||
v0 = currentVertices.get(i);
|
||||
v1 = currentVertices.get(i + 1);
|
||||
v2 = currentVertices.get(i + 2);
|
||||
} else {
|
||||
// Odd triangle: v[i+1], v[i], v[i+2] (reverse winding)
|
||||
v0 = currentVertices.get(i + 1);
|
||||
v1 = currentVertices.get(i);
|
||||
v2 = currentVertices.get(i + 2);
|
||||
}
|
||||
addQuad(v0, v1, v2, v2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GL_QUAD_STRIP vertices to quads.
|
||||
* Vertices come in pairs: (v0,v1), (v2,v3), (v4,v5)...
|
||||
* Each quad uses 4 consecutive vertices in pattern: v[2i], v[2i+1], v[2i+3], v[2i+2]
|
||||
*/
|
||||
private void convertQuadStrip() {
|
||||
int vertexCount = currentVertices.size();
|
||||
if (vertexCount < 4) {
|
||||
LOGGER.warn("[ImmediateModeRecorder] GL_QUAD_STRIP: Need at least 4 vertices, got {}", vertexCount);
|
||||
return;
|
||||
}
|
||||
|
||||
int numQuads = (vertexCount - 2) / 2;
|
||||
for (int i = 0; i < numQuads; i++) {
|
||||
int base = i * 2;
|
||||
// Quad strip winding: v[0], v[1], v[3], v[2]
|
||||
addQuad(
|
||||
currentVertices.get(base),
|
||||
currentVertices.get(base + 1),
|
||||
currentVertices.get(base + 3),
|
||||
currentVertices.get(base + 2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GL_LINES vertices to line segments (pairs of vertices).
|
||||
*/
|
||||
private void convertLines() {
|
||||
int vertexCount = currentVertices.size();
|
||||
int fullLines = vertexCount / 2;
|
||||
|
||||
for (int i = 0; i < fullLines; i++) {
|
||||
int base = i * 2;
|
||||
ImmediateVertex v0 = currentVertices.get(base);
|
||||
ImmediateVertex v1 = currentVertices.get(base + 1);
|
||||
lineVertices.add(new LineVertex(v0.x, v0.y, v0.z, v0.color));
|
||||
lineVertices.add(new LineVertex(v1.x, v1.y, v1.z, v1.color));
|
||||
}
|
||||
|
||||
int remaining = vertexCount % 2;
|
||||
if (remaining > 0) {
|
||||
LOGGER.warn("[ImmediateModeRecorder] GL_LINES: Incomplete line, discarding {} vertices", remaining);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GL_LINE_STRIP to individual line segments.
|
||||
* Vertices 0-1-2-3 become lines: 0-1, 1-2, 2-3
|
||||
*/
|
||||
private void convertLineStripToLines() {
|
||||
int vertexCount = currentVertices.size();
|
||||
if (vertexCount < 2) {
|
||||
LOGGER.warn("[ImmediateModeRecorder] GL_LINE_STRIP: Need at least 2 vertices, got {}", vertexCount);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertexCount - 1; i++) {
|
||||
ImmediateVertex v0 = currentVertices.get(i);
|
||||
ImmediateVertex v1 = currentVertices.get(i + 1);
|
||||
lineVertices.add(new LineVertex(v0.x, v0.y, v0.z, v0.color));
|
||||
lineVertices.add(new LineVertex(v1.x, v1.y, v1.z, v1.color));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GL_LINE_LOOP to individual line segments.
|
||||
* Like LINE_STRIP but also closes the loop from last to first vertex.
|
||||
*/
|
||||
private void convertLineLoopToLines() {
|
||||
int vertexCount = currentVertices.size();
|
||||
if (vertexCount < 2) {
|
||||
LOGGER.warn("[ImmediateModeRecorder] GL_LINE_LOOP: Need at least 2 vertices, got {}", vertexCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert as strip first
|
||||
convertLineStripToLines();
|
||||
|
||||
// Close the loop: add last-to-first segment
|
||||
ImmediateVertex vLast = currentVertices.get(vertexCount - 1);
|
||||
ImmediateVertex vFirst = currentVertices.get(0);
|
||||
lineVertices.add(new LineVertex(vLast.x, vLast.y, vLast.z, vLast.color));
|
||||
lineVertices.add(new LineVertex(vFirst.x, vFirst.y, vFirst.z, vFirst.color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ModelQuad from 4 ImmediateVertex objects.
|
||||
*/
|
||||
private void addQuad(ImmediateVertex v0, ImmediateVertex v1, ImmediateVertex v2, ImmediateVertex v3) {
|
||||
ModelQuad quad = new ModelQuad();
|
||||
|
||||
setVertex(quad, 0, v0);
|
||||
setVertex(quad, 1, v1);
|
||||
setVertex(quad, 2, v2);
|
||||
setVertex(quad, 3, v3);
|
||||
|
||||
// Set light face based on computed normal
|
||||
quad.setLightFace(ModelQuadUtil.findLightFace(quad.getComputedFaceNormal()));
|
||||
|
||||
quads.add(quad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single vertex in a ModelQuad.
|
||||
*/
|
||||
private void setVertex(ModelQuad quad, int idx, ImmediateVertex v) {
|
||||
quad.setX(idx, v.x);
|
||||
quad.setY(idx, v.y);
|
||||
quad.setZ(idx, v.z);
|
||||
quad.setColor(idx, v.color);
|
||||
quad.setTexU(idx, v.texU);
|
||||
quad.setTexV(idx, v.texV);
|
||||
quad.setForgeNormal(idx, v.normal);
|
||||
// Light defaults to full brightness (no lightmap for immediate mode typically)
|
||||
quad.setLight(idx, ModelQuadUtil.DEFAULT_LIGHTMAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any immediate mode geometry has been captured.
|
||||
*
|
||||
* <p>Note: With immediate flush mode, this will typically return false as geometry
|
||||
* is returned from end() immediately. This method is kept for edge cases
|
||||
* where primitives might fail to convert (e.g., incomplete quads).
|
||||
*/
|
||||
public boolean hasGeometry() {
|
||||
return !quads.isEmpty() || !lineVertices.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any remaining accumulated geometry and clear the recorder.
|
||||
*
|
||||
* <p>Note: With immediate flush mode, this will typically return null as geometry
|
||||
* is returned from end() immediately. This method is kept for cleanup and
|
||||
* edge cases where geometry might remain (e.g., if end() was never called).
|
||||
*
|
||||
* @return Result containing remaining quads, lines, and flags, or null if no geometry
|
||||
*/
|
||||
public Result getQuadsAndClear() {
|
||||
if (inPrimitive) {
|
||||
throw new IllegalStateException("Cannot get geometry while in primitive (missing glEnd)");
|
||||
}
|
||||
|
||||
if (quads.isEmpty() && lineVertices.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Copy geometry and create flags
|
||||
List<ModelQuadViewMutable> resultQuads = new ArrayList<>(quads);
|
||||
List<LineVertex> resultLines = new ArrayList<>(lineVertices);
|
||||
CapturingTessellator.Flags flags = new CapturingTessellator.Flags(
|
||||
hasTexCoord, // hasTexture
|
||||
false, // hasBrightness - immediate mode doesn't typically use lightmap
|
||||
hasColor, // hasColor
|
||||
hasNormal // hasNormals
|
||||
);
|
||||
|
||||
// Clear state for reuse
|
||||
quads.clear();
|
||||
lineVertices.clear();
|
||||
hasColor = false;
|
||||
hasTexCoord = false;
|
||||
hasNormal = false;
|
||||
// Note: current texcoord/normal values are NOT reset - they persist per OpenGL spec
|
||||
|
||||
// For getQuadsAndClear, we don't have last vertex data (it was cleared in end())
|
||||
// Use default values - this method is for edge cases/cleanup anyway
|
||||
return new Result(resultQuads, resultLines, flags,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, // Default white color
|
||||
0.0f, 0.0f, 1.0f, // Default +Z normal
|
||||
0.0f, 0.0f); // Default (0,0) texcoord
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of immediate mode recording: quads, lines, attribute flags, and last vertex state.
|
||||
* The last vertex state is used to restore GL_CURRENT_COLOR etc. after VBO playback.
|
||||
*/
|
||||
@com.github.bsideup.jabel.Desugar
|
||||
public record Result(
|
||||
List<ModelQuadViewMutable> quads,
|
||||
List<LineVertex> lines,
|
||||
CapturingTessellator.Flags flags,
|
||||
// Last vertex attributes for restoration after VBO draw
|
||||
float lastColorR, float lastColorG, float lastColorB, float lastColorA,
|
||||
float lastNormalX, float lastNormalY, float lastNormalZ,
|
||||
float lastTexCoordS, float lastTexCoordT
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Reset all state including current texcoord/normal.
|
||||
* Called when starting a new display list.
|
||||
*/
|
||||
public void reset() {
|
||||
quads.clear();
|
||||
lineVertices.clear();
|
||||
currentVertices.clear();
|
||||
inPrimitive = false;
|
||||
currentPrimitiveType = -1;
|
||||
texCoordS = 0.0f;
|
||||
texCoordT = 0.0f;
|
||||
normalX = 0.0f;
|
||||
normalY = 0.0f;
|
||||
normalZ = 1.0f;
|
||||
hasColor = false;
|
||||
hasTexCoord = false;
|
||||
hasNormal = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a glDrawArrays call during display list recording.
|
||||
* Reads vertex data from client arrays (tracked globally in GLStateManager) and converts to immediate mode geometry.
|
||||
*
|
||||
* @param mode Primitive type (GL_QUADS, GL_LINES, etc.)
|
||||
* @param first Starting index in arrays
|
||||
* @param count Number of vertices
|
||||
* @return Result containing converted geometry, or null if no geometry produced
|
||||
*/
|
||||
public Result processDrawArrays(int mode, int first, int count) {
|
||||
final var cas = GLStateManager.getClientArrayState();
|
||||
final var vertexPointer = cas.isVertexArrayEnabled() ? cas.getVertexPointer() : null;
|
||||
final var colorPointer = cas.isColorArrayEnabled() ? cas.getColorPointer() : null;
|
||||
|
||||
return convertClientArrays(
|
||||
mode, first, count,
|
||||
vertexPointer,
|
||||
cas.getVertexPointerType(), cas.getVertexPointerSize(), cas.getVertexPointerStride(),
|
||||
colorPointer,
|
||||
cas.getColorPointerType(), cas.getColorPointerSize(), cas.getColorPointerStride()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert client array data to immediate mode.
|
||||
* Called by glDrawArrays during display list recording.
|
||||
* Like Mesa's save_DrawArrays, reads vertex data from arrays and emits as immediate mode.
|
||||
*
|
||||
* @param mode Primitive type (GL_QUADS, GL_LINES, etc.)
|
||||
* @param first Starting index in arrays
|
||||
* @param count Number of vertices
|
||||
* @param vertexPointer Vertex position buffer (or null if not enabled)
|
||||
* @param vertexType GL type (GL_FLOAT, GL_DOUBLE, etc.)
|
||||
* @param vertexSize Size of vertex (2, 3, or 4)
|
||||
* @param vertexStride Stride in bytes (0 for tightly packed)
|
||||
* @param colorPointer Color buffer (or null if not enabled)
|
||||
* @param colorType GL type (GL_FLOAT, GL_UNSIGNED_BYTE, etc.)
|
||||
* @param colorSize Size of color (3 or 4)
|
||||
* @param colorStride Stride in bytes (0 for tightly packed)
|
||||
* @return Result containing converted geometry, or null if no geometry produced
|
||||
*/
|
||||
public Result convertClientArrays(
|
||||
int mode, int first, int count,
|
||||
java.nio.Buffer vertexPointer, int vertexType, int vertexSize, int vertexStride,
|
||||
java.nio.Buffer colorPointer, int colorType, int colorSize, int colorStride
|
||||
) {
|
||||
begin(mode);
|
||||
|
||||
// Hoist type checks and stride calculations outside the loop for performance.
|
||||
// Mesa's algorithm: offset_bytes = index * effective_stride
|
||||
// effective_stride = (stride == 0) ? (numComponents * sizeof(element)) : stride
|
||||
|
||||
// Determine color reader (hoisted out of loop)
|
||||
final ColorReader colorReader;
|
||||
final int colorEffectiveStride;
|
||||
if (colorPointer == null) {
|
||||
colorReader = null;
|
||||
colorEffectiveStride = 0;
|
||||
} else if (colorType == GL11.GL_FLOAT && colorPointer instanceof FloatBuffer fb) {
|
||||
colorEffectiveStride = (colorStride == 0) ? colorSize * 4 : colorStride;
|
||||
final int strideInFloats = colorEffectiveStride / 4;
|
||||
colorReader = (idx) -> {
|
||||
final int offset = idx * strideInFloats;
|
||||
final float r = fb.get(offset);
|
||||
final float g = fb.get(offset + 1);
|
||||
final float b = fb.get(offset + 2);
|
||||
final float a = (colorSize == 4) ? fb.get(offset + 3) : 1.0f;
|
||||
GLStateManager.glColor4f(r, g, b, a);
|
||||
};
|
||||
} else if ((colorType == GL11.GL_UNSIGNED_BYTE || colorType == GL11.GL_BYTE) && colorPointer instanceof ByteBuffer bb) {
|
||||
colorEffectiveStride = (colorStride == 0) ? colorSize : colorStride;
|
||||
colorReader = (idx) -> {
|
||||
final int offset = idx * colorEffectiveStride;
|
||||
final float r = (bb.get(offset) & 0xFF) / 255.0f;
|
||||
final float g = (bb.get(offset + 1) & 0xFF) / 255.0f;
|
||||
final float b = (bb.get(offset + 2) & 0xFF) / 255.0f;
|
||||
final float a = (colorSize == 4) ? (bb.get(offset + 3) & 0xFF) / 255.0f : 1.0f;
|
||||
GLStateManager.glColor4f(r, g, b, a);
|
||||
};
|
||||
} else {
|
||||
// Unsupported color type - use white
|
||||
colorEffectiveStride = 0;
|
||||
colorReader = (idx) -> GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
// Determine vertex reader (hoisted out of loop)
|
||||
final VertexReader vertexReader;
|
||||
if (vertexPointer == null) {
|
||||
vertexReader = null;
|
||||
} else if (vertexType == GL11.GL_FLOAT && vertexPointer instanceof FloatBuffer fb) {
|
||||
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
|
||||
final int strideInFloats = effectiveStride / 4;
|
||||
vertexReader = (idx) -> {
|
||||
final int offset = idx * strideInFloats;
|
||||
final float x = fb.get(offset);
|
||||
final float y = fb.get(offset + 1);
|
||||
final float z = (vertexSize >= 3) ? fb.get(offset + 2) : 0.0f;
|
||||
vertex(x, y, z);
|
||||
};
|
||||
} else if (vertexType == GL11.GL_DOUBLE && vertexPointer instanceof DoubleBuffer db) {
|
||||
final int effectiveStride = (vertexStride == 0) ? vertexSize * 8 : vertexStride;
|
||||
final int strideInDoubles = effectiveStride / 8;
|
||||
vertexReader = (idx) -> {
|
||||
final int offset = idx * strideInDoubles;
|
||||
final float x = (float) db.get(offset);
|
||||
final float y = (float) db.get(offset + 1);
|
||||
final float z = (vertexSize >= 3) ? (float) db.get(offset + 2) : 0.0f;
|
||||
vertex(x, y, z);
|
||||
};
|
||||
} else if (vertexType == GL11.GL_INT && vertexPointer instanceof IntBuffer ib) {
|
||||
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
|
||||
final int strideInInts = effectiveStride / 4;
|
||||
vertexReader = (idx) -> {
|
||||
final int offset = idx * strideInInts;
|
||||
final float x = ib.get(offset);
|
||||
final float y = ib.get(offset + 1);
|
||||
final float z = (vertexSize >= 3) ? ib.get(offset + 2) : 0.0f;
|
||||
vertex(x, y, z);
|
||||
};
|
||||
} else if (vertexType == GL11.GL_SHORT && vertexPointer instanceof ShortBuffer sb) {
|
||||
final int effectiveStride = (vertexStride == 0) ? vertexSize * 2 : vertexStride;
|
||||
final int strideInShorts = effectiveStride / 2;
|
||||
vertexReader = (idx) -> {
|
||||
final int offset = idx * strideInShorts;
|
||||
final float x = sb.get(offset);
|
||||
final float y = sb.get(offset + 1);
|
||||
final float z = (vertexSize >= 3) ? sb.get(offset + 2) : 0.0f;
|
||||
vertex(x, y, z);
|
||||
};
|
||||
} else {
|
||||
// Unsupported vertex type
|
||||
vertexReader = null;
|
||||
}
|
||||
|
||||
// Main loop - no type checks, just function calls
|
||||
for (int i = first; i < first + count; i++) {
|
||||
if (colorReader != null) {
|
||||
colorReader.read(i);
|
||||
}
|
||||
if (vertexReader != null) {
|
||||
vertexReader.read(i);
|
||||
}
|
||||
}
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ColorReader {
|
||||
void read(int index);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface VertexReader {
|
||||
void read(int index);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
/**
|
||||
* A single command recorded during display list compilation.
|
||||
* Commands are executed in sequence during glCallList() to replay the display list.
|
||||
*/
|
||||
public interface DisplayListCommand {
|
||||
/**
|
||||
* Execute this command by calling the appropriate GL function.
|
||||
*/
|
||||
void execute();
|
||||
|
||||
/**
|
||||
* Release any resources held by this command (e.g., VBOs).
|
||||
* Called when the display list is deleted.
|
||||
*/
|
||||
default void delete() {
|
||||
// Most commands don't hold resources
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle this command during display list optimization.
|
||||
*
|
||||
* <p>Commands override this to implement their optimization behavior:
|
||||
* <ul>
|
||||
* <li>Matrix transforms: accumulate into the optimizer, return false</li>
|
||||
* <li>Push/Pop: update stack, return true to emit</li>
|
||||
* <li>Barriers: emit pending transforms, return true to emit</li>
|
||||
* <li>State changes: return true to emit</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param ctx The optimization context providing access to transform state
|
||||
* @return true if this command should be added to output, false if handled internally
|
||||
*/
|
||||
default boolean handleOptimization(OptimizationContext ctx) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
|
||||
/**
|
||||
* Represents a single draw call within a compiled display list.
|
||||
* Each DrawCommand corresponds to one glBegin/glEnd block.
|
||||
*/
|
||||
@Desugar
|
||||
public record DrawCommand(
|
||||
int firstVertex,
|
||||
int vertexCount,
|
||||
int primitiveType
|
||||
) {}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Command: Draw a range of vertices from a shared VBO.
|
||||
* <p>
|
||||
* DrawRangeCmd references a shared VBO owned by CompiledDisplayList.ownedVbos.
|
||||
* Multiple DrawRangeCmds can reference different ranges within the same VBO,
|
||||
* enabling format-based batching where all draws with the same vertex format
|
||||
* share a single VBO.
|
||||
* <p>
|
||||
* Transform is NOT stored here - handled by TransformOptimizer emitting
|
||||
* MultMatrixCmd before this command during optimization.
|
||||
*/
|
||||
@Desugar
|
||||
public record DrawRangeCmd(
|
||||
VertexBuffer sharedVbo,
|
||||
int startVertex,
|
||||
int vertexCount,
|
||||
boolean hasBrightness
|
||||
) implements DisplayListCommand {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
sharedVbo.setupState();
|
||||
sharedVbo.draw(startVertex, vertexCount);
|
||||
sharedVbo.cleanupState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
// No-op: VBO lifetime is managed by CompiledDisplayList.ownedVbos
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return "DrawRange(start=" + startVertex + ", count=" + vertexCount + ", hasBrightness=" + hasBrightness + ")";
|
||||
}
|
||||
}
|
||||
Loaded 100 of 829 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user