mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 09:15:06 +00:00
Compare commits
307
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2e491ed47 | ||
|
|
bccc5b00ca | ||
|
|
5ed26f3af7 | ||
|
|
9e7d19b883 | ||
|
|
5f61e387fb | ||
|
|
70cfa2ae90 | ||
|
|
37717bd4db | ||
|
|
b179340a0f | ||
|
|
465e36148c | ||
|
|
a3f0efd629 | ||
|
|
2386d630c5 | ||
|
|
6a784d8d3b | ||
|
|
fe9028d300 | ||
|
|
4c5e5df290 | ||
|
|
39ee364418 | ||
|
|
4c9cae5e0b | ||
|
|
11b53c9f8f | ||
|
|
c769a08a4e | ||
|
|
90dac9f39d | ||
|
|
c5c4b290b1 | ||
|
|
4b46de4566 | ||
|
|
9f2a71be4e | ||
|
|
79fe08417c | ||
|
|
5de64abdef | ||
|
|
2e07465166 | ||
|
|
2b610b9aea | ||
|
|
ab192ddd2a | ||
|
|
cb5e2ed7d4 | ||
|
|
e914ef7563 | ||
|
|
748dbd55a1 | ||
|
|
140c0c2285 | ||
|
|
af26442b98 | ||
|
|
ef07ee9f77 | ||
|
|
1b8087e8a3 | ||
|
|
9107ea9ae5 | ||
|
|
938a6bda90 | ||
|
|
e9f6f72741 | ||
|
|
379330f997 | ||
|
|
2fecb3a05b | ||
|
|
c085af35bd | ||
|
|
a36aec36a2 | ||
|
|
bdd6259c3f | ||
|
|
d7d2228a6c | ||
|
|
cf9993fce4 | ||
|
|
3ae57ca594 | ||
|
|
9816cb2148 | ||
|
|
b1e496f56d | ||
|
|
c8e50227fb | ||
|
|
066381a7c0 | ||
|
|
9a0ea1ad65 | ||
|
|
8e26a0179b | ||
|
|
1d96af8e3a | ||
|
|
f7eb86cdb6 | ||
|
|
b38fafa831 | ||
|
|
7dc2625f8c | ||
|
|
3d97977050 | ||
|
|
5f41c60bc2 | ||
|
|
0325a96bc2 | ||
|
|
ad123d0bd5 | ||
|
|
4b89997985 | ||
|
|
f5c9fa3301 | ||
|
|
d6a6d3b525 | ||
|
|
f079bed977 | ||
|
|
c9ec40e94b | ||
|
|
8380f3853d | ||
|
|
534ae6b05c | ||
|
|
68d523d3df | ||
|
|
2f22a7b3ad | ||
|
|
7b79b90dd6 | ||
|
|
417f23f826 | ||
|
|
ca3c46c873 | ||
|
|
82fa9ade2b | ||
|
|
aaab8fd0bc | ||
|
|
bd56ccc4b8 | ||
|
|
e17f60308d | ||
|
|
1dd98978ad | ||
|
|
dae986eb0b | ||
|
|
5fdf84edd0 | ||
|
|
b19d023c11 | ||
|
|
41eafbd151 | ||
|
|
dd970b4c6a | ||
|
|
f79ad7f22a | ||
|
|
6947ffa3e2 | ||
|
|
969d2c3710 | ||
|
|
c5988d29c3 | ||
|
|
dc68c2c265 | ||
|
|
51533a81ec | ||
|
|
90eabb5d40 | ||
|
|
74843cb1f9 | ||
|
|
5e0344a529 | ||
|
|
3cdd934987 | ||
|
|
9d90ff137c | ||
|
|
bee9541797 | ||
|
|
b75a5d8f7d | ||
|
|
8d3ab41c53 | ||
|
|
48d032e654 | ||
|
|
1968914582 | ||
|
|
a70bd76ff3 | ||
|
|
db919b5f37 | ||
|
|
cde47ea423 | ||
|
|
5ef6cb11c0 | ||
|
|
b7015bf10e | ||
|
|
46602ab921 | ||
|
|
63e9fa0df1 | ||
|
|
dcea970a77 | ||
|
|
b29240d6da | ||
|
|
9d5a02ed21 | ||
|
|
b454483aae | ||
|
|
72b13543c6 | ||
|
|
36d98fcb22 | ||
|
|
44c5b9617a | ||
|
|
f3f1677db8 | ||
|
|
d396b611ad | ||
|
|
08f4c0f9db | ||
|
|
50b0d22dc2 | ||
|
|
7fed54264e | ||
|
|
be87401be6 | ||
|
|
94254006df | ||
|
|
f79774df65 | ||
|
|
2f2fc22df3 | ||
|
|
26300bbb1b | ||
|
|
88e4d2fd6c | ||
|
|
bb52476d27 | ||
|
|
6d60477b93 | ||
|
|
362724610c | ||
|
|
ea4a3cdf0b | ||
|
|
65959268d0 | ||
|
|
58d820cd7e | ||
|
|
685d68bd9c | ||
|
|
ad63d2d62c | ||
|
|
d2730ccca8 | ||
|
|
e9257b8739 | ||
|
|
3984225faa | ||
|
|
52db6b56d6 | ||
|
|
34bfe513f1 | ||
|
|
42d7ed58aa | ||
|
|
9a4f0f9b99 | ||
|
|
be90a01725 | ||
|
|
e5e8250d44 | ||
|
|
062a03ffc2 | ||
|
|
9f5afaaec3 | ||
|
|
b05f17a477 | ||
|
|
57caf08212 | ||
|
|
085ec79c21 | ||
|
|
38641268ae | ||
|
|
fe8e723638 | ||
|
|
16debfd518 | ||
|
|
8cfc3e1f4a | ||
|
|
2c808fae43 | ||
|
|
1649c3c0c6 | ||
|
|
b84286ba3b | ||
|
|
ee6c79802a | ||
|
|
d3366ad31a | ||
|
|
92b6dcc010 | ||
|
|
89132f3ef9 | ||
|
|
f0d45b0d62 | ||
|
|
173d986a5b | ||
|
|
473a690054 | ||
|
|
2515b3520f | ||
|
|
a32f9ca244 | ||
|
|
941fc88484 | ||
|
|
4106dd3865 | ||
|
|
61f6aa19a8 | ||
|
|
77875f7f91 | ||
|
|
241c32def1 | ||
|
|
a8107f97e8 | ||
|
|
8bec433699 | ||
|
|
2d39fb2397 | ||
|
|
cfd8eb1411 | ||
|
|
59c270a5b9 | ||
|
|
87ac03281c | ||
|
|
36b82fa2ce | ||
|
|
2fc2e2e38d | ||
|
|
33de1de9c4 | ||
|
|
55811b9d7b | ||
|
|
a10d92f52e | ||
|
|
7d2fe12ce9 | ||
|
|
006ded801e | ||
|
|
03afd6939e | ||
|
|
adc5421e00 | ||
|
|
dfe60e4b3e | ||
|
|
72c6cdfa60 | ||
|
|
c5daea86f8 | ||
|
|
f8958ee990 | ||
|
|
073260060b | ||
|
|
771fee124d | ||
|
|
680285bf54 | ||
|
|
df8784536e | ||
|
|
c8991d810c | ||
|
|
e3b02642ac | ||
|
|
2759dd6689 | ||
|
|
996dbaef2c | ||
|
|
72ca675c93 | ||
|
|
e6e230e270 | ||
|
|
43d7282ba5 | ||
|
|
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
|
||||
|
||||
+2
-3
@@ -4,7 +4,7 @@
|
||||
/.vs/
|
||||
/.vscode/
|
||||
/run/
|
||||
/build/
|
||||
build/
|
||||
/eclipse/
|
||||
.classpath
|
||||
.project
|
||||
@@ -36,5 +36,4 @@ addon.local.gradle
|
||||
addon.local.gradle.kts
|
||||
addon.late.local.gradle
|
||||
addon.late.local.gradle.kts
|
||||
/*.csv
|
||||
!gl_functions.csv
|
||||
local/
|
||||
@@ -7,40 +7,49 @@
|
||||
* 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
|
||||
|
||||
## Core Profile
|
||||
Targets GL 3.3+ core profile
|
||||
* FFP emulation via generated GLSL shaders
|
||||
* Display list emulation
|
||||
* Software matrix stacks and state management through GLSM
|
||||
* GL_QUADS → GL_TRIANGLES conversion
|
||||
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
|
||||
|
||||
|
||||
# Required Dependencies
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.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
|
||||
@@ -55,16 +64,16 @@
|
||||
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
|
||||
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
|
||||
* Backported code under the LGPL
|
||||
* Sodium
|
||||
* Sodium / Celeritas
|
||||
* JellySquid & the CaffeineMC team, for making Sodium in the first place
|
||||
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
|
||||
* Embeddedt for Celeritas used under the LGPL 3.0
|
||||
* Reese's Sodium Options
|
||||
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
|
||||
* Neodymium
|
||||
* 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 +91,10 @@
|
||||
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
|
||||
* FalseTweaks
|
||||
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
|
||||
* Beddium
|
||||
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
|
||||
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
+65
-2
@@ -1,9 +1,18 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
|
||||
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
|
||||
// extraRunJvmArguments.add("-Dangelica.useVulkan=true")
|
||||
// extraRunJvmArguments.add("-Dangelica.vulkanDebug=true")
|
||||
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
@@ -11,17 +20,23 @@ 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 +46,51 @@ tasks.register<Copy>("copyDependencies") {
|
||||
from(configurations.default)
|
||||
into("testDependencies")
|
||||
}
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
val shadowImplementation: Configuration by configurations
|
||||
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
}
|
||||
|
||||
val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
|
||||
dependsOn(shadowImplementation)
|
||||
from(shadowImplementation.map(::zipTree))
|
||||
exclude("module-info.class")
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
archiveClassifier = "shadow-stripped"
|
||||
}
|
||||
|
||||
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = files(stripModuleInfoFromShadow.map { it.archiveFile })
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
dependsOn(downgradeEmbedOnlyForTest, downgradeShadowImplForTest)
|
||||
classpath = classpath
|
||||
.minus(embedOnly)
|
||||
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
|
||||
.minus(shadowImplementation)
|
||||
.plus(files({ fileTree(downgradeShadowImplForTest.get().temporaryDir) }))
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
dependsOn(embedOnly)
|
||||
from(embedOnly.map(::zipTree))
|
||||
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
mergeServiceFiles()
|
||||
|
||||
minimize {
|
||||
exclude(project(":glsm"))
|
||||
exclude(project(":lwjgl3-backend"))
|
||||
}
|
||||
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
|
||||
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
|
||||
}
|
||||
|
||||
+63
-32
@@ -46,45 +46,68 @@ configurations {
|
||||
// Can be enabled for testing if desired
|
||||
// runtimeClasspath.extendsFrom(transformedMod)
|
||||
// testRuntimeClasspath.extendsFrom(transformedMod)
|
||||
|
||||
testRuntimeClasspath.exclude group: "com.github.GTNewHorizons", module: "lwjgl3ify"
|
||||
|
||||
// Embed in JAR without relocation (compile only, shadowed into JAR)
|
||||
embedOnly
|
||||
embedOnly.canBeConsumed = false
|
||||
embedOnly.canBeResolved = true
|
||||
compileClasspath.extendsFrom(embedOnly)
|
||||
testCompileClasspath.extendsFrom(embedOnly)
|
||||
testRuntimeClasspath.extendsFrom(embedOnly)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
|
||||
configurations.all {
|
||||
exclude group: "com.github.GTNewHorizons", module: "Angelica"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.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 }
|
||||
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
|
||||
|
||||
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.36:dev")
|
||||
|
||||
shadowImplementation(project(":glsm")) { transitive = false }
|
||||
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
|
||||
shadowImplementation("net.minecraftforge:eventbus:7.0-beta.15")
|
||||
|
||||
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
|
||||
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-4.g6b42bca") {
|
||||
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
|
||||
exclude module: "antlr4" // we only want to shadow the runtime module
|
||||
}
|
||||
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
|
||||
compileOnly "org.apache.ant:ant:1.8.2"
|
||||
|
||||
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
|
||||
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
|
||||
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.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,29 +117,37 @@ dependencies {
|
||||
|
||||
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.6:dev") // Mixin Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
|
||||
|
||||
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5593_H261"))
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
|
||||
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
|
||||
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:HoloInventory:2.4.13-GTNH:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
|
||||
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
|
||||
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
|
||||
|
||||
// Hodgepodge
|
||||
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
|
||||
|
||||
// Better Crashes
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.1-GTNH:dev")
|
||||
|
||||
// Tests
|
||||
testImplementation(platform('org.junit:junit-bom:5.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")
|
||||
|
||||
devOnlyNonPublishable("com.github.javaparser:javaparser-symbol-solver-core:3.26.2") {
|
||||
exclude module: "guava"
|
||||
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")
|
||||
}
|
||||
-2084
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,192 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
apply(plugin = "xyz.wagyourtail.jvmdowngrader")
|
||||
|
||||
version = rootProject.version
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
// RFG variant attributes: the root Angelica project uses RetroFuturaGradle which expects
|
||||
// these attributes on dependencies. Without them, Gradle can't resolve the subproject variant.
|
||||
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
|
||||
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
|
||||
|
||||
configurations {
|
||||
listOf("apiElements", "runtimeElements").forEach { name ->
|
||||
named(name) {
|
||||
attributes {
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compile stubs: MC class signatures needed to resolve GTNHLib's type hierarchy.
|
||||
// Not included in the published jar — real MC classes are on the classpath at runtime.
|
||||
sourceSets {
|
||||
create("stubs") {
|
||||
java.srcDir("src/stubs/java")
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude stubs from all output jars
|
||||
tasks.withType<Jar>().configureEach {
|
||||
exclude("net/minecraft/**")
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Mojang"
|
||||
url = uri("https://libraries.minecraft.net")
|
||||
}
|
||||
maven {
|
||||
name = "GTNH Maven"
|
||||
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
|
||||
}
|
||||
maven {
|
||||
name = "Forge"
|
||||
url = uri("https://maven.minecraftforge.net/")
|
||||
}
|
||||
maven {
|
||||
name = "taumc"
|
||||
url = uri("https://maven.taumc.org/releases")
|
||||
}
|
||||
maven {
|
||||
name = "WagYourTail"
|
||||
url = uri("https://maven.wagyourtail.xyz/releases")
|
||||
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// MC compile stubs
|
||||
compileOnly(sourceSets["stubs"].output)
|
||||
|
||||
// LWJGL
|
||||
compileOnly("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
|
||||
compileOnly("org.lwjgl.lwjgl:lwjgl_util:${property("lwjglVersion")}")
|
||||
|
||||
compileOnly("org.ow2.asm:asm-tree:9.7")
|
||||
|
||||
// Our Deps
|
||||
api("com.github.GTNewHorizons:GTNHLib:${property("gtnhlibVersion")}:dev")
|
||||
api("net.minecraftforge:eventbus:${property("eventbusVersion")}")
|
||||
|
||||
compileOnly("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
|
||||
implementation("org.taumc:glsl-transformation-lib:${property("glslTransformLibVersion")}") { exclude(module = "antlr4") }
|
||||
implementation("org.antlr:antlr4-runtime:${property("antlr4RuntimeVersion")}")
|
||||
implementation("org.anarres:jcpp:${property("jcppVersion")}")
|
||||
|
||||
compileOnly("org.projectlombok:lombok:${property("lombokVersion")}") { isTransitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:${property("lombokVersion")}")
|
||||
compileOnly("org.jetbrains:annotations:${property("jetbrainsAnnotationsVersion")}")
|
||||
compileOnly("org.apache.logging.log4j:log4j-api:${property("log4jVersion")}")
|
||||
|
||||
// Deps (normally from MC) - compile only, but needed at test runtime
|
||||
compileOnly("com.google.guava:guava:${property("guavaVersion")}")
|
||||
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}")
|
||||
|
||||
// Test
|
||||
testImplementation(sourceSets["stubs"].output)
|
||||
testImplementation(platform("org.junit:junit-bom:${property("junitBomVersion")}"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core:${property("log4jVersion")}")
|
||||
testRuntimeOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:${property("celeritasVersion")}") { isTransitive = false }
|
||||
testRuntimeOnly("xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:${property("jvmDowngraderVersion")}:downgraded-8")
|
||||
testRuntimeOnly("org.apache.commons:commons-lang3:${property("commonsLang3Version")}")
|
||||
}
|
||||
|
||||
val depsToDowngrade by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
depsToDowngrade("net.minecraftforge:eventbus:${property("eventbusVersion")}")
|
||||
depsToDowngrade("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
}
|
||||
|
||||
val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = depsToDowngrade
|
||||
}
|
||||
|
||||
val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = sourceSets["main"].output.classesDirs.plus(sourceSets["stubs"].output.classesDirs)
|
||||
classpath = sourceSets["main"].compileClasspath
|
||||
dependsOn(tasks.named("classes"), tasks.named("stubsClasses"))
|
||||
}
|
||||
|
||||
val downgradeTestClasses by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = sourceSets["test"].output.classesDirs
|
||||
classpath = sourceSets["test"].compileClasspath
|
||||
dependsOn(tasks.named("testClasses"))
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
}
|
||||
|
||||
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeDepsForTest)
|
||||
|
||||
val mainClassesDirs = sourceSets["main"].output.classesDirs
|
||||
.plus(sourceSets["stubs"].output.classesDirs)
|
||||
val testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
|
||||
val downgradedTest = files(downgradeTestClasses.map { it.outputCollection })
|
||||
val downgradedMain = files(downgradeMainClasses.map { it.outputCollection })
|
||||
val downgradedDeps = files(downgradeDepsForTest.map { it.outputCollection })
|
||||
|
||||
setTestClassesDirs(downgradedTest)
|
||||
classpath = downgradedTest
|
||||
.plus(downgradedMain)
|
||||
.plus(downgradedDeps)
|
||||
.plus(classpath.minus(mainClassesDirs).minus(testClassesDirs).minus(depsToDowngrade))
|
||||
|
||||
val extractNatives = rootProject.tasks.named("extractNatives2")
|
||||
dependsOn(extractNatives)
|
||||
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
groupId = "com.gtnewhorizons.angelica"
|
||||
artifactId = "glsm"
|
||||
version = rootProject.version.toString()
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
if (System.getenv("MAVEN_USER") != null) {
|
||||
maven {
|
||||
name = "GTNHMaven"
|
||||
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
|
||||
credentials {
|
||||
username = System.getenv("MAVEN_USER")
|
||||
password = System.getenv("MAVEN_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
lwjglVersion = 2.9.4-nightly-20150209
|
||||
eventbusVersion = 7.0-beta.15
|
||||
celeritasVersion = 2.5.3-GTNH
|
||||
lombokVersion = 1.18.42
|
||||
log4jVersion = 2.0-beta9
|
||||
guavaVersion = 18.0
|
||||
gtnhlibVersion = 0.9.36
|
||||
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
|
||||
antlr4RuntimeVersion = 4.13.2
|
||||
jcppVersion = 1.4.14
|
||||
jetbrainsAnnotationsVersion = 26.0.2
|
||||
junitBomVersion = 5.14.1
|
||||
jvmDowngraderVersion = 1.3.5
|
||||
commonsLang3Version = 3.17.0
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Builds final command buffers from raw command buffers and compiled VBO data.
|
||||
* Bridges the gap between VBO compilation (glsm types) and buffer processing (recording types).
|
||||
*/
|
||||
public final class CommandBufferBuilder {
|
||||
|
||||
private CommandBufferBuilder() {}
|
||||
|
||||
/**
|
||||
* Build commands with transform collapsing optimization.
|
||||
* Accumulates MODELVIEW transforms and emits MultMatrix at barriers (draws, CallList).
|
||||
* Uses mergedRanges where consecutive same-transform draws are combined.
|
||||
*/
|
||||
public static void buildFromRawBuffer(
|
||||
CommandBuffer rawBuffer,
|
||||
List<AccumulatedDraw> accumulatedDraws,
|
||||
CommandBuffer finalBuffer) {
|
||||
|
||||
rawBuffer.resetRead();
|
||||
int cmdIndex = 0;
|
||||
int rangeIndex = 0;
|
||||
|
||||
AccumulatedDraw draw;
|
||||
while (rawBuffer.hasRemaining()) {
|
||||
// Read and process the command
|
||||
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
|
||||
|
||||
// Emit draw ranges at this command position
|
||||
while (rangeIndex < accumulatedDraws.size() && (draw = accumulatedDraws.get(rangeIndex)).commandIndex == cmdIndex) {
|
||||
emitDrawRangeToBuffer(draw, finalBuffer, rangeIndex);
|
||||
rangeIndex++;
|
||||
}
|
||||
|
||||
cmdIndex++;
|
||||
}
|
||||
|
||||
while (rangeIndex < accumulatedDraws.size()) {
|
||||
emitDrawRangeToBuffer(accumulatedDraws.get(rangeIndex), finalBuffer, rangeIndex);
|
||||
rangeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a draw range to the output buffer, with transform if needed.
|
||||
*/
|
||||
private static void emitDrawRangeToBuffer(
|
||||
AccumulatedDraw draw,
|
||||
CommandBuffer out,
|
||||
int vboIdx) {
|
||||
|
||||
if (draw.restoreData != null) {
|
||||
out.writeDrawRangeRestore(vboIdx, draw.restoreData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the draw range command
|
||||
out.writeDrawRange(vboIdx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import org.taumc.glsl.ShaderParser;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Compat transformation for mod shaders running under core profile.
|
||||
*
|
||||
* <p>Handles:
|
||||
* <ul>
|
||||
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
|
||||
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
|
||||
* <li>gl_TexCoord[N] varying array → per-index in/out declarations</li>
|
||||
* <li>Texture function renames (texture2D → texture, etc.)</li>
|
||||
* <li>Fragment output handling (gl_FragColor → layout-qualified out declarations)</li>
|
||||
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
|
||||
* <li>gl_FrontColor → local variable in vertex shaders</li>
|
||||
* <li>shadow2D/shadow2DLod → texture/textureLod with vec4 wrapping</li>
|
||||
* <li>Version upgrade to 330 core minimum</li>
|
||||
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CompatShaderTransformer {
|
||||
|
||||
private static final Pattern VERSION_PATTERN = Pattern.compile("#version[ \\t]+(\\d+)(?:[ \\t]+(\\w+))?");
|
||||
|
||||
private static final Pattern DEFINE_PATTERN = Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
|
||||
|
||||
/** Compat builtins that trigger AST transformation. */
|
||||
private static final Set<String> COMPAT_BUILTINS = Set.of(
|
||||
"gl_ModelView", "gl_Projection", "gl_NormalMatrix", "gl_TextureMatrix",
|
||||
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
|
||||
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
|
||||
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
|
||||
"shadow2D", "gl_FrontLightModelProduct",
|
||||
"gl_LightSource", "gl_FrontMaterial"
|
||||
);
|
||||
|
||||
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
|
||||
String.join("|", COMPAT_BUILTINS) + "|\\b(?:attribute|varying)\\b"
|
||||
);
|
||||
|
||||
private static final Map<String, String> MATRIX_RENAMES = Map.of(
|
||||
"gl_ModelViewMatrix", "angelica_ModelViewMatrix",
|
||||
"gl_ModelViewMatrixInverse", "angelica_ModelViewMatrixInverse",
|
||||
"gl_ProjectionMatrix", "angelica_ProjectionMatrix",
|
||||
"gl_ProjectionMatrixInverse", "angelica_ProjectionMatrixInverse",
|
||||
"gl_NormalMatrix", "angelica_NormalMatrix"
|
||||
);
|
||||
|
||||
private static final Path DUMP_DIR;
|
||||
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
|
||||
|
||||
static {
|
||||
DUMP_DIR = Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false")) ? Paths.get("compat_shaders") : null;
|
||||
}
|
||||
|
||||
private static final int CACHE_SIZE = 32;
|
||||
private record CacheKey(String source, boolean isFragment) {}
|
||||
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
|
||||
new LinkedHashMap<>(32, 0.75f, true) {
|
||||
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
|
||||
return size() > CACHE_SIZE;
|
||||
}
|
||||
});
|
||||
|
||||
public static void clearCache() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a mod shader source for core profile compatibility.
|
||||
*/
|
||||
public static String transform(String source, boolean isFragment) {
|
||||
final boolean needsTransform = needsTransformation(source);
|
||||
String result;
|
||||
if (!needsTransform) {
|
||||
result = fixupVersion(source);
|
||||
} else {
|
||||
final CacheKey key = new CacheKey(source, isFragment);
|
||||
final String cached = cache.get(key);
|
||||
if (cached != null) {
|
||||
dumpShader(source, cached, isFragment, needsTransform);
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
result = transformInternal(source, isFragment);
|
||||
cache.put(key, result);
|
||||
} catch (Exception e) {
|
||||
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
|
||||
result = fixupVersion(source);
|
||||
}
|
||||
}
|
||||
|
||||
dumpShader(source, result, isFragment, needsTransform);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean needsTransformation(String source) {
|
||||
// Shaders already at 330+ core don't need compat transformation
|
||||
final Matcher vm = VERSION_PATTERN.matcher(source);
|
||||
if (vm.find()) {
|
||||
final int version = Integer.parseInt(vm.group(1));
|
||||
if (version >= 330 && "core".equals(vm.group(2))) return false;
|
||||
}
|
||||
|
||||
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
|
||||
}
|
||||
|
||||
private static String transformInternal(String source, boolean isFragment) {
|
||||
final Matcher versionMatcher = VERSION_PATTERN.matcher(source);
|
||||
int declaredVersion = 110;
|
||||
if (versionMatcher.find()) {
|
||||
declaredVersion = Integer.parseInt(versionMatcher.group(1));
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(declaredVersion, RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330);
|
||||
|
||||
// Pre-parse reserved word renaming — prevents ANTLR parse failures
|
||||
source = GlslTransformUtils.replaceTexture(source);
|
||||
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
|
||||
|
||||
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source);
|
||||
final Transformer transformer = new Transformer(parsedShader.full());
|
||||
|
||||
injectMatrixUniforms(transformer);
|
||||
|
||||
transformFog(transformer, isFragment, source);
|
||||
|
||||
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform
|
||||
if (source.contains("gl_FrontLightModelProduct")) {
|
||||
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
|
||||
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
|
||||
}
|
||||
|
||||
// gl_LightSource[i] → struct + uniform array (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_LightSource")) {
|
||||
transformer.injectFunction("struct angelica_LightSourceParameters {"
|
||||
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
|
||||
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
|
||||
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
|
||||
transformer.rename("gl_LightSource", "angelica_LightSource");
|
||||
}
|
||||
|
||||
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_FrontMaterial")) {
|
||||
transformer.injectFunction("struct angelica_MaterialParameters {"
|
||||
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
|
||||
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
|
||||
}
|
||||
|
||||
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
|
||||
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
|
||||
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_FrontColor", "angelica_FrontColor");
|
||||
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
|
||||
|
||||
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
|
||||
transformVertexAttributes(transformer, source);
|
||||
} else {
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("in vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_Color", "angelica_FrontColor");
|
||||
}
|
||||
}
|
||||
|
||||
// gl_TexCoord[N] varying array → per-index in/out declarations
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
|
||||
for (Integer i : texCoordIndices) {
|
||||
final String qualifier = isFragment ? "in" : "out";
|
||||
transformer.injectVariable(qualifier + " vec4 angelica_TexCoord" + i + ";");
|
||||
}
|
||||
|
||||
// Fragment output handling + alpha test discard
|
||||
if (isFragment) {
|
||||
transformFragmentOutputs(transformer);
|
||||
}
|
||||
|
||||
// texture-as-variable collision handling
|
||||
if (transformer.containsCall("texture") && transformer.hasVariable("texture")) {
|
||||
transformer.rename("texture", "gtexture");
|
||||
}
|
||||
if (transformer.hasVariable("angelica_renamed_texture")) {
|
||||
transformer.rename("angelica_renamed_texture", "gtexture");
|
||||
}
|
||||
|
||||
transformer.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
|
||||
|
||||
transformer.renameAndWrapShadow("shadow2D", "texture");
|
||||
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
|
||||
|
||||
final String versionDirective = "#version " + targetVersion + " core\n";
|
||||
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
||||
|
||||
// Preserve #define directives
|
||||
final StringBuilder defines = new StringBuilder();
|
||||
final Matcher defineMatcher = DEFINE_PATTERN.matcher(source);
|
||||
while (defineMatcher.find()) {
|
||||
defines.append(defineMatcher.group().trim()).append('\n');
|
||||
}
|
||||
|
||||
final String header = versionDirective + (extensions.isEmpty() ? "" : "\n" + extensions) + (defines.isEmpty() ? "" : "\n" + defines);
|
||||
final StringBuilder result = new StringBuilder();
|
||||
transformer.mutateTree(tree -> result.append(GlslTransformUtils.getFormattedShader(tree, header)));
|
||||
|
||||
// Restore pre-parse renames
|
||||
String output = GlslTransformUtils.restoreReservedWords(result.toString());
|
||||
|
||||
// Core profile: attribute → in, varying → out (vertex) / in (fragment)
|
||||
output = fixupQualifiers(output, isFragment);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject matrix uniforms and rename compat builtins.
|
||||
*/
|
||||
private static void injectMatrixUniforms(Transformer transformer) {
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat3 angelica_NormalMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_LightmapTextureMatrix;");
|
||||
|
||||
transformer.rename(MATRIX_RENAMES);
|
||||
|
||||
// Expression replacements
|
||||
transformer.replaceExpression("gl_ModelViewProjectionMatrix", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[0]", "mat4(1.0)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[1]", "angelica_LightmapTextureMatrix");
|
||||
transformer.replaceExpression(
|
||||
"gl_TextureMatrix",
|
||||
"mat4[8](mat4(1.0), angelica_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform fragment outputs for core profile.
|
||||
*/
|
||||
private static void transformFragmentOutputs(Transformer transformer) {
|
||||
if (transformer.containsCall("gl_FragColor")) {
|
||||
transformer.replaceExpression("gl_FragColor", "gl_FragData[0]");
|
||||
}
|
||||
|
||||
final Set<Integer> found = new HashSet<>();
|
||||
transformer.renameArray("gl_FragData", "angelica_FragData", found);
|
||||
|
||||
for (Integer i : found) {
|
||||
transformer.injectVariable("layout (location = " + i + ") out vec4 angelica_FragData" + i + ";");
|
||||
}
|
||||
|
||||
// Core profile: GL_ALPHA_TEST is removed - inject runtime discard using GLSM-tracked alpha reference (uploaded by CompatUniformManager).
|
||||
if (found.contains(0)) {
|
||||
transformer.injectVariable("uniform float angelica_currentAlphaTest;");
|
||||
transformer.appendMain("if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;");
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
|
||||
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
|
||||
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out float angelica_FogFragCoord;");
|
||||
transformer.prependMain("angelica_FogFragCoord = 0.0;");
|
||||
} else {
|
||||
if (source.contains("gl_FogFragCoord")) {
|
||||
transformer.injectVariable("in float angelica_FogFragCoord;");
|
||||
}
|
||||
}
|
||||
|
||||
transformer.rename("gl_Fog", "angelica_Fog");
|
||||
transformer.injectVariable("uniform float angelica_FogDensity;");
|
||||
transformer.injectVariable("uniform float angelica_FogStart;");
|
||||
transformer.injectVariable("uniform float angelica_FogEnd;");
|
||||
transformer.injectVariable("uniform vec4 angelica_FogColor;");
|
||||
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
|
||||
transformer.injectFunction("angelica_FogParameters angelica_Fog = angelica_FogParameters("
|
||||
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
|
||||
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
|
||||
*/
|
||||
private static void transformVertexAttributes(Transformer transformer, String source) {
|
||||
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
|
||||
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
|
||||
transformer.rename("gl_Vertex", "angelica_Vertex");
|
||||
}
|
||||
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
|
||||
transformer.rename("gl_Color", "angelica_Color");
|
||||
}
|
||||
if (source.contains("gl_MultiTexCoord0")) {
|
||||
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
|
||||
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
|
||||
}
|
||||
if (source.contains("gl_MultiTexCoord1")) {
|
||||
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
|
||||
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
|
||||
}
|
||||
if (source.contains("gl_Normal")) {
|
||||
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
|
||||
transformer.rename("gl_Normal", "angelica_Normal");
|
||||
}
|
||||
if (source.contains("ftransform")) {
|
||||
transformer.replaceExpression("ftransform()", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix * angelica_Vertex)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
|
||||
if (DUMP_DIR == null) return;
|
||||
final int id = dumpCounter.getAndIncrement();
|
||||
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
|
||||
try {
|
||||
Files.createDirectories(DUMP_DIR);
|
||||
// Capture caller info for identification
|
||||
final String caller = identifyCaller();
|
||||
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
|
||||
+ "\n// Transformed: " + wasTransformed
|
||||
+ "\n// Caller: " + caller + "\n\n";
|
||||
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
|
||||
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String identifyCaller() {
|
||||
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
|
||||
final String cls = frame.getClassName();
|
||||
if (!cls.startsWith("com.gtnewhorizons.angelica.glsm.")
|
||||
&& !cls.startsWith("java.")
|
||||
&& !cls.equals("org.lwjgl.opengl.GL20")) {
|
||||
return cls + "." + frame.getMethodName() + ":" + frame.getLineNumber();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\battribute\\b");
|
||||
private static final Pattern VARYING_PATTERN = Pattern.compile("\\bvarying\\b");
|
||||
|
||||
/** Replace legacy storage qualifiers removed in core profile. Safe on AST-serialized output (no comments). */
|
||||
public static String fixupQualifiers(String source, boolean isFragment) {
|
||||
source = ATTRIBUTE_PATTERN.matcher(source).replaceAll("in");
|
||||
source = VARYING_PATTERN.matcher(source).replaceAll(isFragment ? "in" : "out");
|
||||
return source;
|
||||
}
|
||||
|
||||
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
|
||||
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
|
||||
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
|
||||
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
|
||||
|
||||
/**
|
||||
* Generate a passthrough vertex shader for a fragment-only program.
|
||||
*
|
||||
* @param fragmentSource the transformed fragment shader source
|
||||
* @return a complete vertex shader source string
|
||||
*/
|
||||
public static String generatePassthroughVertexShader(String fragmentSource) {
|
||||
final StringBuilder sb = new StringBuilder(512);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
// Always have position
|
||||
sb.append("layout(location = 0) in vec4 a_Position;\n");
|
||||
|
||||
// Matrix uniforms for position transform
|
||||
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
|
||||
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
|
||||
|
||||
final StringBuilder varyings = new StringBuilder();
|
||||
final StringBuilder assignments = new StringBuilder();
|
||||
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
|
||||
sb.append("layout(location = 1) in vec4 a_Color;\n");
|
||||
varyings.append("out vec4 angelica_FrontColor;\n");
|
||||
assignments.append(" angelica_FrontColor = a_Color;\n");
|
||||
}
|
||||
|
||||
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
while (texCoordMatcher.find()) {
|
||||
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
|
||||
}
|
||||
for (int i : texCoordIndices) {
|
||||
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
|
||||
final int location = i == 0 ? 2 : 3;
|
||||
sb.append("layout(location = ").append(location).append(") in vec4 a_TexCoord").append(i).append(";\n");
|
||||
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
|
||||
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
|
||||
}
|
||||
|
||||
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
|
||||
varyings.append("out float angelica_FogFragCoord;\n");
|
||||
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
|
||||
}
|
||||
|
||||
sb.append(varyings);
|
||||
sb.append("\nvoid main() {\n");
|
||||
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
|
||||
sb.append(assignments);
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
|
||||
private static String fixupVersion(String source) {
|
||||
final Matcher m = VERSION_PATTERN.matcher(source);
|
||||
if (!m.find()) {
|
||||
return "#version 330 core\n" + source;
|
||||
}
|
||||
|
||||
final int version = Integer.parseInt(m.group(1));
|
||||
final String profile = m.group(2);
|
||||
|
||||
if (version >= 330 && !"compatibility".equals(profile)) {
|
||||
if ("core".equals(profile)) return source;
|
||||
return m.replaceFirst("#version " + version + " core");
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(version, 330);
|
||||
return m.replaceFirst("#version " + targetVersion + " core");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
/**
|
||||
* Per-program compat uniform location cache and upload manager.
|
||||
*
|
||||
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader
|
||||
* programs.
|
||||
*/
|
||||
public class CompatUniformManager {
|
||||
|
||||
// Uniform location indices within the cached array (package-private for tests)
|
||||
static final int LOC_MODELVIEW = 0;
|
||||
static final int LOC_MODELVIEW_INVERSE = 1;
|
||||
static final int LOC_PROJECTION = 2;
|
||||
static final int LOC_PROJECTION_INVERSE = 3;
|
||||
static final int LOC_NORMAL = 4;
|
||||
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
|
||||
static final int LOC_FOG_DENSITY = 6;
|
||||
static final int LOC_FOG_START = 7;
|
||||
static final int LOC_FOG_END = 8;
|
||||
static final int LOC_FOG_COLOR = 9;
|
||||
static final int LOC_IRIS_MODELVIEW = 10;
|
||||
static final int LOC_IRIS_PROJECTION = 11;
|
||||
static final int LOC_IRIS_NORMAL = 12;
|
||||
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
|
||||
static final int LOC_ALPHA_TEST_REF = 14;
|
||||
static final int LOC_SCENE_COLOR = 15;
|
||||
static final int LOC_CLIP_PLANES = 16;
|
||||
static final int LOC_CLIP_PLANES_ENABLED = 17;
|
||||
|
||||
// Light source fields: 12 per light × 2 lights = 24 locations
|
||||
static final int LIGHT_FIELDS = 12;
|
||||
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
|
||||
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
|
||||
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
|
||||
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
|
||||
static final int LOC_LIGHT_BASE = 18;
|
||||
|
||||
// Material fields: 5 locations
|
||||
static final int MF_EMISSION = 0, MF_AMBIENT = 1, MF_DIFFUSE = 2, MF_SPECULAR = 3, MF_SHININESS = 4;
|
||||
static final int MAT_FIELDS = 5;
|
||||
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 42
|
||||
|
||||
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 47
|
||||
|
||||
private static final String[] LIGHT_FIELD_NAMES = {
|
||||
"ambient", "diffuse", "specular", "position", "halfVector",
|
||||
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
|
||||
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
|
||||
};
|
||||
private static final String[] MAT_FIELD_NAMES = {
|
||||
"emission", "ambient", "diffuse", "specular", "shininess",
|
||||
};
|
||||
|
||||
private static final String[] UNIFORM_NAMES;
|
||||
|
||||
static {
|
||||
UNIFORM_NAMES = new String[LOC_COUNT];
|
||||
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
|
||||
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
|
||||
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
|
||||
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
|
||||
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
|
||||
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES_ENABLED] = "angelica_ClipPlanesEnabled";
|
||||
for (int li = 0; li < 2; li++) {
|
||||
for (int fi = 0; fi < LIGHT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] = "angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
for (int fi = 0; fi < MAT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
|
||||
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// Reusable NIO buffers for upload
|
||||
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
|
||||
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
|
||||
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
|
||||
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
|
||||
private static final Matrix3f normalMatrix = new Matrix3f();
|
||||
private static final Matrix4f scratchMatrix = new Matrix4f();
|
||||
|
||||
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
|
||||
private static final FloatBuffer lightmapMatrixBuf;
|
||||
|
||||
static {
|
||||
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
|
||||
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// Dirty tracking: skip uploads when state hasn't changed and program is the same
|
||||
private static int lastProgram = -1;
|
||||
private static int lastMvGen = -1;
|
||||
private static int lastProjGen = -1;
|
||||
private static int lastTexMatGen = -1;
|
||||
private static int lastFragmentGen = -1;
|
||||
private static int lastLightingGen = -1;
|
||||
private static int lastClipPlaneGen = -1;
|
||||
|
||||
private CompatUniformManager() {}
|
||||
|
||||
public static void onLinkProgram(int program) {
|
||||
int[] locs = new int[LOC_COUNT];
|
||||
boolean hasAny = false;
|
||||
|
||||
for (int i = 0; i < LOC_COUNT; i++) {
|
||||
locs[i] = RENDER_BACKEND.getUniformLocation(program, UNIFORM_NAMES[i]);
|
||||
if (locs[i] != -1) hasAny = true;
|
||||
}
|
||||
|
||||
if (hasAny) {
|
||||
programLocations.put(program, locs);
|
||||
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onUseProgram(int program) {
|
||||
if (program == 0) return;
|
||||
|
||||
int[] locs = programLocations.get(program);
|
||||
if (locs == null) return;
|
||||
|
||||
final boolean programChanged = program != lastProgram;
|
||||
lastProgram = program;
|
||||
|
||||
// Matrix uniforms — skip if generation unchanged and same program
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
lastFragmentGen = fragGen;
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
// Lighting-derived uniforms (scene color, light sources, material)
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
lastLightingGen = litGen;
|
||||
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
|
||||
uploadLightSources(locs);
|
||||
uploadMaterial(locs);
|
||||
}
|
||||
|
||||
// Clip plane equations + enabled bool — uploaded when enable state or equations change
|
||||
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
if (mvChanged) {
|
||||
// ModelView
|
||||
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
mv.get(mat4Buf);
|
||||
if (locs[LOC_MODELVIEW] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// ModelView Inverse
|
||||
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
|
||||
mv.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
|
||||
}
|
||||
|
||||
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
|
||||
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
normalMatrix.get(mat3Buf);
|
||||
if (locs[LOC_NORMAL] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_NORMAL] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projChanged) {
|
||||
// Projection
|
||||
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
proj.get(mat4Buf);
|
||||
if (locs[LOC_PROJECTION] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Projection Inverse
|
||||
if (locs[LOC_PROJECTION_INVERSE] != -1) {
|
||||
proj.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (texMatChanged) {
|
||||
// Iris lightmap texture matrix (constant)
|
||||
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
|
||||
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadFragmentUniforms(int[] locs) {
|
||||
// Fog uniforms
|
||||
FogStateStack fog = GLStateManager.getFogState();
|
||||
if (locs[LOC_FOG_DENSITY] != -1) {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
|
||||
}
|
||||
if (locs[LOC_FOG_START] != -1) {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_FOG_START], fog.getStart());
|
||||
}
|
||||
if (locs[LOC_FOG_END] != -1) {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_FOG_END], fog.getEnd());
|
||||
}
|
||||
if (locs[LOC_FOG_COLOR] != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y).put((float) fog.getFogColor().z).put(fog.getFogAlpha());
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(locs[LOC_FOG_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
|
||||
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
|
||||
if (locs[LOC_ALPHA_TEST_REF] != -1) {
|
||||
final float ref;
|
||||
if (!GLStateManager.getAlphaTest().isEnabled() || GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
|
||||
ref = -1.0f;
|
||||
} else {
|
||||
ref = GLStateManager.getAlphaState().getReference();
|
||||
}
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_ALPHA_TEST_REF], ref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a Same computation as {@code Uniforms.uploadLightingPreComputed()}.
|
||||
*/
|
||||
private static void uploadSceneColor(int[] locs) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
final LightModelState lm = GLStateManager.getLightModel();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
|
||||
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
|
||||
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
|
||||
.put(mat.diffuse.w);
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(locs[LOC_SCENE_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
private static void uploadLightSources(int[] locs) {
|
||||
// Early exit: if the first light field isn't present, no lighting uniforms exist
|
||||
if (locs[LOC_LIGHT_BASE] == -1) return;
|
||||
final LightStateStack[] lights = GLStateManager.getLightDataStates();
|
||||
for (int li = 0; li < 2; li++) {
|
||||
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
|
||||
final LightState light = lights[li];
|
||||
|
||||
if (locs[base + LF_AMBIENT] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
|
||||
if (locs[base + LF_DIFFUSE] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
|
||||
if (locs[base + LF_SPECULAR] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
|
||||
if (locs[base + LF_POSITION] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
|
||||
if (locs[base + LF_HALF_VECTOR] != -1) {
|
||||
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
|
||||
float hx, hy, hz;
|
||||
if (light.position.w == 0.0f) {
|
||||
final float len = (float) Math.sqrt(light.position.x * light.position.x
|
||||
+ light.position.y * light.position.y
|
||||
+ light.position.z * light.position.z);
|
||||
if (len > 0) {
|
||||
hx = light.position.x / len;
|
||||
hy = light.position.y / len;
|
||||
hz = light.position.z / len + 1.0f;
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
final float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
|
||||
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
|
||||
}
|
||||
if (locs[base + LF_SPOT_DIRECTION] != -1) RENDER_BACKEND.uniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
|
||||
if (locs[base + LF_SPOT_EXPONENT] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
|
||||
if (locs[base + LF_SPOT_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
|
||||
if (locs[base + LF_SPOT_COS_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
|
||||
if (locs[base + LF_CONSTANT_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
|
||||
if (locs[base + LF_LINEAR_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
|
||||
if (locs[base + LF_QUADRATIC_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMaterial(int[] locs) {
|
||||
// Early exit: if the first material field isn't present, no material uniforms exist
|
||||
if (locs[LOC_MAT_BASE] == -1) return;
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
|
||||
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
|
||||
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
|
||||
}
|
||||
|
||||
private static void uploadClipPlanes(int[] locs) {
|
||||
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
|
||||
}
|
||||
if (locs[LOC_CLIP_PLANES] != -1) {
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
clipPlaneBuf.flip();
|
||||
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onDeleteProgram(int program) {
|
||||
programLocations.remove(program);
|
||||
}
|
||||
|
||||
public static boolean hasProgram(int program) {
|
||||
return programLocations.containsKey(program);
|
||||
}
|
||||
|
||||
public static int[] getLocations(int program) {
|
||||
return programLocations.get(program);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
/*
|
||||
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
|
||||
*
|
||||
* Original copyright and license:
|
||||
*
|
||||
* Copyright 2017 Valve Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sub license, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the
|
||||
* next paragraph) shall be included in all copies or substantial portions
|
||||
* of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Original author: Samuel Pitoiset <[email protected]>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bitmap ID allocator for display list IDs.
|
||||
*
|
||||
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
|
||||
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
|
||||
*/
|
||||
public class DisplayListIDAllocator {
|
||||
|
||||
private int[] data;
|
||||
private int numElements; // number of uint32 words allocated
|
||||
private int numSetElements; // highest word index with any bit set + 1
|
||||
private int lowestFreeIdx; // optimization: skip fully-allocated words
|
||||
|
||||
public DisplayListIDAllocator() {
|
||||
this(32); // 1024 IDs initially
|
||||
}
|
||||
|
||||
public DisplayListIDAllocator(int initialWords) {
|
||||
if (initialWords < 1) initialWords = 1;
|
||||
this.data = new int[initialWords];
|
||||
this.numElements = initialWords;
|
||||
this.numSetElements = 0;
|
||||
this.lowestFreeIdx = 0;
|
||||
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
|
||||
data[0] = 1;
|
||||
numSetElements = 1;
|
||||
}
|
||||
|
||||
public int allocRange(int count) {
|
||||
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
|
||||
if (count == 1) return allocSingle();
|
||||
return allocMultiple(count);
|
||||
}
|
||||
|
||||
private int allocSingle() {
|
||||
for (int i = lowestFreeIdx; i < numElements; i++) {
|
||||
if (data[i] == 0xFFFFFFFF) continue;
|
||||
|
||||
int bit = Integer.numberOfTrailingZeros(~data[i]);
|
||||
data[i] |= 1 << bit;
|
||||
lowestFreeIdx = i;
|
||||
numSetElements = Math.max(numSetElements, i + 1);
|
||||
return i * 32 + bit;
|
||||
}
|
||||
|
||||
// No slots available, grow. Record old end, then mark first bit there.
|
||||
int oldNumElements = numElements;
|
||||
resize(Math.max(numElements, 1) * 2);
|
||||
lowestFreeIdx = oldNumElements;
|
||||
data[oldNumElements] |= 1;
|
||||
numSetElements = Math.max(numSetElements, oldNumElements + 1);
|
||||
return oldNumElements * 32;
|
||||
}
|
||||
|
||||
private int allocMultiple(int num) {
|
||||
int numWords = divRoundUp(num, 32);
|
||||
int base = findFreeBlock(lowestFreeIdx);
|
||||
boolean found = false;
|
||||
|
||||
while (!found) {
|
||||
int i;
|
||||
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
|
||||
;
|
||||
|
||||
if (i - base == numWords) {
|
||||
found = true;
|
||||
} else if (i == numElements) {
|
||||
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
|
||||
resize(numElements * 2 + numWords);
|
||||
found = true;
|
||||
} else {
|
||||
// Non-zero word at i, skip past it
|
||||
base = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark bits as used
|
||||
int fullWords = num / 32;
|
||||
int remainder = num % 32;
|
||||
for (int i = base; i < base + fullWords; i++) {
|
||||
data[i] = 0xFFFFFFFF;
|
||||
}
|
||||
if (remainder != 0) {
|
||||
data[base + fullWords] |= (1 << remainder) - 1;
|
||||
}
|
||||
|
||||
if (lowestFreeIdx == base) {
|
||||
lowestFreeIdx = base + num / 32;
|
||||
}
|
||||
numSetElements = Math.max(numSetElements, base + numWords);
|
||||
|
||||
return base * 32;
|
||||
}
|
||||
|
||||
private int findFreeBlock(int start) {
|
||||
for (int i = start; i < numElements; i++) {
|
||||
if (data[i] == 0) return i;
|
||||
}
|
||||
return numElements;
|
||||
}
|
||||
|
||||
public void free(int id) {
|
||||
if (id <= 0) return; // ID 0 is reserved
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) return;
|
||||
|
||||
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
|
||||
if (numSetElements == idx + 1) {
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void freeRange(int base, int count) {
|
||||
if (count <= 0 || base <= 0) return;
|
||||
|
||||
int startIdx = base / 32;
|
||||
if (startIdx >= numElements) return;
|
||||
|
||||
int end = base + count;
|
||||
for (int id = base; id < end; id++) {
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) break;
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
}
|
||||
|
||||
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAllocated(int id) {
|
||||
if (id <= 0) return false;
|
||||
int idx = id / 32;
|
||||
if (idx >= numSetElements) return false;
|
||||
return (data[idx] & (1 << (id % 32))) != 0;
|
||||
}
|
||||
|
||||
private void resize(int newNumElements) {
|
||||
if (newNumElements <= numElements) return;
|
||||
int[] newData = new int[newNumElements];
|
||||
System.arraycopy(data, 0, newData, 0, numElements);
|
||||
data = newData;
|
||||
numElements = newNumElements;
|
||||
}
|
||||
|
||||
private static int divRoundUp(int a, int b) {
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+128
-68
@@ -2,14 +2,18 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -19,45 +23,85 @@ 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
|
||||
, GLStateManager.alphaState // Alpha test function and reference value
|
||||
, GLStateManager.blendMode // GL_BLEND enable bit
|
||||
, GLStateManager.blendState // Blending source and destination functions
|
||||
// Constant blend color
|
||||
// Blending equation
|
||||
// GL_DITHER enable bit
|
||||
// GL_DRAW_BUFFER setting
|
||||
// GL_COLOR_LOGIC_OP enable bit
|
||||
// GL_INDEX_LOGIC_OP enable bit
|
||||
// Logic op function
|
||||
, GLStateManager.blendState // Blending source/destination functions, equation, blend color
|
||||
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
|
||||
, GLStateManager.ditherState // GL_DITHER enable bit
|
||||
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
|
||||
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
|
||||
, GLStateManager.logicOpMode // Logic op function
|
||||
, GLStateManager.colorMask // Color-mode and index-mode writemasks
|
||||
, GLStateManager.clearColor // Color-mode and index-mode clear values
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
|
||||
GLStateManager.color // Current RGBA color
|
||||
, ShaderManager.getNormalStack() // Current normal vector
|
||||
, ShaderManager.getTexCoordStack() // Current texture coordinates
|
||||
// Current color index
|
||||
// Current normal vector
|
||||
// Current texture coordinates
|
||||
// Current raster position
|
||||
// GL_CURRENT_RASTER_POSITION_VALID flag
|
||||
// RGBA color associated with current raster position
|
||||
@@ -74,14 +118,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 +136,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 +247,9 @@ public class Feature {
|
||||
, GLStateManager.shadeModelState // GL_SHADE_MODEL setting
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
|
||||
// GL_LINE_SMOOTH flag
|
||||
// GL_LINE_STIPPLE enable bit
|
||||
// Line stipple pattern and repeat counter
|
||||
// Line width
|
||||
GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
|
||||
, GLStateManager.lineStippleState // GL_LINE_STIPPLE enable bit
|
||||
, GLStateManager.lineState // Line stipple pattern, repeat counter, and width
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
|
||||
// GL_LIST_BASE setting
|
||||
@@ -209,21 +274,17 @@ public class Feature {
|
||||
// GL_READ_BUFFER setting
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
|
||||
// GL_POINT_SMOOTH flag
|
||||
// Point size
|
||||
GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
|
||||
, GLStateManager.pointState // Point size
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
|
||||
GLStateManager.cullState // GL_CULL_FACE enable bit
|
||||
// GL_CULL_FACE_MODE value
|
||||
// GL_FRONT_FACE indicator
|
||||
// GL_POLYGON_MODE setting
|
||||
// GL_POLYGON_SMOOTH flag
|
||||
// GL_POLYGON_STIPPLE enable bit
|
||||
// GL_POLYGON_OFFSET_FILL flag
|
||||
// GL_POLYGON_OFFSET_LINE flag
|
||||
// GL_POLYGON_OFFSET_POINT flag
|
||||
// GL_POLYGON_OFFSET_FACTOR
|
||||
// GL_POLYGON_OFFSET_UNITS
|
||||
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
|
||||
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE enable bit
|
||||
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
|
||||
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
|
||||
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
|
||||
, GLStateManager.polygonState // GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_OFFSET_FACTOR/UNITS
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
|
||||
// Polygon stipple pattern
|
||||
@@ -233,15 +294,12 @@ public class Feature {
|
||||
// Scissor box
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
|
||||
// GL_STENCIL_TEST enable bit
|
||||
// Stencil function and reference value
|
||||
// Stencil value mask
|
||||
// Stencil fail, pass, and depth buffer pass actions
|
||||
// Stencil buffer clear value
|
||||
// Stencil buffer writemask
|
||||
GLStateManager.stencilTest // GL_STENCIL_TEST enable bit
|
||||
, GLStateManager.stencilState // Stencil function, ref, mask, ops, writemask, clear value
|
||||
));
|
||||
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
|
||||
GLStateManager.activeTextureUnit // Active texture unit
|
||||
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
|
||||
// Enable bits for the four texture coordinates
|
||||
|
||||
// Border color for each texture image
|
||||
@@ -249,26 +307,28 @@ public class Feature {
|
||||
// Magnification function for each texture image
|
||||
// Texture coordinates and wrap mode for each texture image
|
||||
|
||||
// Color and mode for each texture environment
|
||||
// Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
|
||||
// GL_TEXTURE_GEN_MODE setting for S, T, R, and Q
|
||||
// glTexGen plane equations for S, T, R, and Q
|
||||
// Current texture bindings (for example, GL_TEXTURE_BINDING_2D) - Below
|
||||
));
|
||||
|
||||
// Current Texture Bindings - GL_TEXTURE_BINDING_2D
|
||||
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i));
|
||||
// textureAttribs.add(GLStateManager.textures.getInfo(i))
|
||||
textureAttribs.add(GLStateManager.textures.getTexEnvState(i));
|
||||
}
|
||||
|
||||
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
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,360 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Software emulation of GL feedback mode
|
||||
*/
|
||||
public class FeedbackManager {
|
||||
|
||||
private static final int FB_3D = 0x01;
|
||||
private static final int FB_4D = 0x02;
|
||||
private static final int FB_COLOR = 0x04;
|
||||
private static final int FB_TEXTURE = 0x08;
|
||||
|
||||
private static int renderMode = GL11.GL_RENDER;
|
||||
private static FloatBuffer feedbackBuffer;
|
||||
private static int feedbackBufferSize;
|
||||
private static int feedbackMask;
|
||||
private static int feedbackCount;
|
||||
|
||||
private static final Matrix4f mvpMatrix = new Matrix4f();
|
||||
private static final Vector4f clipVec = new Vector4f();
|
||||
|
||||
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
|
||||
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
|
||||
|
||||
@FunctionalInterface
|
||||
private interface IndexReader {
|
||||
|
||||
int read(int i);
|
||||
}
|
||||
|
||||
public static boolean isFeedbackMode() {
|
||||
return renderMode == GL11.GL_FEEDBACK;
|
||||
}
|
||||
|
||||
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
|
||||
return;
|
||||
}
|
||||
feedbackBuffer = buffer;
|
||||
feedbackBufferSize = buffer.remaining();
|
||||
feedbackMask = computeMask(type);
|
||||
feedbackCount = 0;
|
||||
}
|
||||
|
||||
public static int glRenderMode(int mode) {
|
||||
int result = switch (renderMode) {
|
||||
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
|
||||
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
|
||||
default -> 0;
|
||||
};
|
||||
|
||||
if (mode == GL11.GL_SELECT) {
|
||||
GLStateManager.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
|
||||
}
|
||||
|
||||
feedbackCount = 0;
|
||||
renderMode = mode;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void glPassThrough(float token) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
feedbackToken(GL11.GL_PASS_THROUGH_TOKEN);
|
||||
feedbackToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
private static void feedbackToken(float token) {
|
||||
if (feedbackCount < feedbackBufferSize) {
|
||||
feedbackBuffer.put(feedbackBuffer.position() + feedbackCount, token);
|
||||
}
|
||||
feedbackCount++;
|
||||
}
|
||||
|
||||
private static void feedbackVertex(float winX, float winY, float winZ, float winW) {
|
||||
feedbackToken(winX);
|
||||
feedbackToken(winY);
|
||||
if ((feedbackMask & FB_3D) != 0) feedbackToken(winZ);
|
||||
if ((feedbackMask & FB_4D) != 0) feedbackToken(winW);
|
||||
if ((feedbackMask & FB_COLOR) != 0) {
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
}
|
||||
if ((feedbackMask & FB_TEXTURE) != 0) {
|
||||
feedbackToken(0f);
|
||||
feedbackToken(0f);
|
||||
feedbackToken(0f);
|
||||
feedbackToken(1f);
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformAndEmitVertex(float x, float y, float z) {
|
||||
clipVec.set(x, y, z, 1.0f);
|
||||
mvpMatrix.transform(clipVec);
|
||||
|
||||
final float w = clipVec.w;
|
||||
final float ndcX = clipVec.x / w;
|
||||
final float ndcY = clipVec.y / w;
|
||||
final float ndcZ = clipVec.z / w;
|
||||
|
||||
final ViewportState vp = GLStateManager.getViewportState();
|
||||
final float winX = vp.x + vp.width * (ndcX + 1.0f) * 0.5f;
|
||||
final float winY = vp.y + vp.height * (ndcY + 1.0f) * 0.5f;
|
||||
final float winZ = (float) ((vp.depthRangeFar - vp.depthRangeNear) * ndcZ + (vp.depthRangeFar + vp.depthRangeNear)) * 0.5f;
|
||||
final float winW = 1.0f / w;
|
||||
|
||||
feedbackVertex(winX, winY, winZ, winW);
|
||||
}
|
||||
|
||||
private static int positionStride() {
|
||||
final int s = VertexAttribState.get(0).effectiveStride();
|
||||
return (s != 0) ? s : 12;
|
||||
}
|
||||
|
||||
private static long positionOffset() {
|
||||
return VertexAttribState.get(0).offset;
|
||||
}
|
||||
|
||||
public static void processDrawArrays(int mode, int first, int count) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
final long posOffset = positionOffset();
|
||||
final int byteCount = count * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
|
||||
|
||||
emitAllPrimitives(mode, count, i -> i, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
|
||||
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
|
||||
final int indexByteCount = indexCount * elementSize;
|
||||
ensureIndexReadbackCapacity(indexByteCount);
|
||||
indexReadbackBuf.clear().limit(indexByteCount);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
|
||||
final ByteBuffer indexBuf = indexReadbackBuf;
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < indexCount; i++) {
|
||||
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
|
||||
if (idx < minIdx) minIdx = idx;
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
final long posOffset = positionOffset();
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
|
||||
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, ByteBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFF);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, ShortBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFFFF);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, IntBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i));
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int count, int type, ByteBuffer indices) {
|
||||
final int pos = indices.position();
|
||||
final IndexReader reader = switch (type) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> i -> indices.get(pos + i) & 0xFF;
|
||||
case GL11.GL_UNSIGNED_SHORT -> i -> indices.getShort(pos + i * 2) & 0xFFFF;
|
||||
case GL11.GL_UNSIGNED_INT -> i -> indices.getInt(pos + i * 4);
|
||||
default -> i -> 0;
|
||||
};
|
||||
processDrawElementsImpl(mode, count, reader);
|
||||
}
|
||||
|
||||
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
|
||||
computeMVP();
|
||||
final int stride = positionStride();
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int idx = reader.read(i);
|
||||
if (idx < minIdx) minIdx = idx;
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
readVerticesFromVBO(minIdx, maxIdx, stride);
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
|
||||
}
|
||||
|
||||
private static void computeMVP() {
|
||||
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
|
||||
}
|
||||
|
||||
private static int computeMask(int type) {
|
||||
return switch (type) {
|
||||
case GL11.GL_2D -> 0;
|
||||
case GL11.GL_3D -> FB_3D;
|
||||
case GL11.GL_3D_COLOR -> FB_3D | FB_COLOR;
|
||||
case GL11.GL_3D_COLOR_TEXTURE -> FB_3D | FB_COLOR | FB_TEXTURE;
|
||||
case GL11.GL_4D_COLOR_TEXTURE -> FB_3D | FB_4D | FB_COLOR | FB_TEXTURE;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static void ensureReadbackCapacity(int bytes) {
|
||||
if (readbackBuf.capacity() < bytes) {
|
||||
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureIndexReadbackCapacity(int bytes) {
|
||||
if (indexReadbackBuf.capacity() < bytes) {
|
||||
indexReadbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
|
||||
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
|
||||
final long posOffset = positionOffset();
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
|
||||
}
|
||||
|
||||
private static void emitVertex(int vertexIndex, int stride) {
|
||||
final float x = readbackBuf.getFloat(vertexIndex * stride);
|
||||
final float y = readbackBuf.getFloat(vertexIndex * stride + 4);
|
||||
final float z = readbackBuf.getFloat(vertexIndex * stride + 8);
|
||||
transformAndEmitVertex(x, y, z);
|
||||
}
|
||||
|
||||
private static void emitAllPrimitives(int mode, int count, IndexReader reader, int stride) {
|
||||
switch (mode) {
|
||||
case GL11.GL_POINTS:
|
||||
for (int i = 0; i < count; i++) {
|
||||
feedbackToken(GL11.GL_POINT_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINES:
|
||||
for (int i = 0; i + 1 < count; i += 2) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINE_STRIP:
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINE_LOOP:
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
if (count > 2) {
|
||||
feedbackToken(GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(count - 1), stride);
|
||||
emitVertex(reader.read(0), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLES:
|
||||
for (int i = 0; i + 2 < count; i += 3) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLE_STRIP:
|
||||
for (int i = 0; i + 2 < count; i++) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
if ((i & 1) == 0) {
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
} else {
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLE_FAN:
|
||||
for (int i = 1; i + 1 < count; i++) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
emitVertex(reader.read(0), stride);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_QUADS:
|
||||
for (int i = 0; i + 3 < count; i += 4) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(4);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
emitVertex(reader.read(i + 3), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
|
||||
}
|
||||
}
|
||||
|
||||
static void reset() {
|
||||
renderMode = GL11.GL_RENDER;
|
||||
feedbackBuffer = null;
|
||||
feedbackBufferSize = 0;
|
||||
feedbackMask = 0;
|
||||
feedbackCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
/*
|
||||
* Copyright LWJGL. All rights reserved. Modified by IMS for use in Iris (net.coderbot.iris.gl).
|
||||
* License terms: https://www.lwjgl.org/license
|
||||
*/
|
||||
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import 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;
|
||||
|
||||
public final class GLDebug {
|
||||
|
||||
/**
|
||||
* Sets up debug callbacks
|
||||
*
|
||||
* @return 0 for failure, 1 for success, 2 for restart required.
|
||||
*/
|
||||
public static int setupDebugMessageCallback() {
|
||||
if (Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
GLStateManager.LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
|
||||
return 0;
|
||||
}
|
||||
return setupDebugMessageCallbackImpl();
|
||||
}
|
||||
|
||||
public static Throwable filterStackTrace(Throwable throwable, int offset) {
|
||||
final StackTraceElement[] elems = throwable.getStackTrace();
|
||||
final StackTraceElement[] filtered = new StackTraceElement[elems.length];
|
||||
int j = 0;
|
||||
for (int i = offset; i < elems.length; i++) {
|
||||
String className = elems[i].getClassName();
|
||||
if (className == null) {
|
||||
className = "";
|
||||
}
|
||||
filtered[j++] = elems[i];
|
||||
}
|
||||
final StackTraceElement[] newElems = new StackTraceElement[j];
|
||||
System.arraycopy(filtered, 0, newElems, 0, j);
|
||||
throwable.setStackTrace(newElems);
|
||||
return throwable;
|
||||
}
|
||||
|
||||
private static String buildStackTrace() {
|
||||
final StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (StackTraceElement elem : elems) {
|
||||
sb.append("\n\t").append(elem.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void logDebugMessage(int id, String source, String type, String severity, String message) {
|
||||
final String fullMessage = String.format("[GL] %s %s (0x%X) from %s: %s%s", severity, type, id, source, message, buildStackTrace());
|
||||
|
||||
if ("HIGH".equals(severity)) {
|
||||
GLStateManager.LOGGER.error(fullMessage);
|
||||
} else if ("MEDIUM".equals(severity)) {
|
||||
GLStateManager.LOGGER.warn(fullMessage);
|
||||
} else {
|
||||
GLStateManager.LOGGER.info(fullMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static int setupDebugMessageCallbackImpl() {
|
||||
return BackendManager.RENDER_BACKEND.setupDebugOutput(
|
||||
(source, type, id, severity, message) ->
|
||||
logDebugMessage(id, getDebugSource(source), getDebugType(type), getDebugSeverity(severity), message)
|
||||
);
|
||||
}
|
||||
|
||||
public static int disableDebugMessages() {
|
||||
return BackendManager.RENDER_BACKEND.disableDebugOutput();
|
||||
}
|
||||
|
||||
private static String getDebugSource(int source) {
|
||||
return switch (source) {
|
||||
case GL43.GL_DEBUG_SOURCE_API -> "API";
|
||||
case GL43.GL_DEBUG_SOURCE_WINDOW_SYSTEM -> "WINDOW SYSTEM";
|
||||
case GL43.GL_DEBUG_SOURCE_SHADER_COMPILER -> "SHADER COMPILER";
|
||||
case GL43.GL_DEBUG_SOURCE_THIRD_PARTY -> "THIRD PARTY";
|
||||
case GL43.GL_DEBUG_SOURCE_APPLICATION -> "APPLICATION";
|
||||
case GL43.GL_DEBUG_SOURCE_OTHER -> "OTHER";
|
||||
default -> String.format("Unknown [0x%X]", source);
|
||||
};
|
||||
}
|
||||
|
||||
private static String getDebugType(int type) {
|
||||
return switch (type) {
|
||||
case GL43.GL_DEBUG_TYPE_ERROR -> "ERROR";
|
||||
case GL43.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR -> "DEPRECATED BEHAVIOR";
|
||||
case GL43.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR -> "UNDEFINED BEHAVIOR";
|
||||
case GL43.GL_DEBUG_TYPE_PORTABILITY -> "PORTABILITY";
|
||||
case GL43.GL_DEBUG_TYPE_PERFORMANCE -> "PERFORMANCE";
|
||||
case GL43.GL_DEBUG_TYPE_OTHER -> "OTHER";
|
||||
case GL43.GL_DEBUG_TYPE_MARKER -> "MARKER";
|
||||
default -> String.format("Unknown [0x%X]", type);
|
||||
};
|
||||
}
|
||||
|
||||
private static String getDebugSeverity(int severity) {
|
||||
return switch (severity) {
|
||||
case GL43.GL_DEBUG_SEVERITY_NOTIFICATION -> "NOTIFICATION";
|
||||
case GL43.GL_DEBUG_SEVERITY_HIGH -> "HIGH";
|
||||
case GL43.GL_DEBUG_SEVERITY_MEDIUM -> "MEDIUM";
|
||||
case GL43.GL_DEBUG_SEVERITY_LOW -> "LOW";
|
||||
default -> String.format("Unknown [0x%X]", severity);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getMatrixModeName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_MODELVIEW -> "MODELVIEW";
|
||||
case GL11.GL_PROJECTION -> "PROJECTION";
|
||||
case GL11.GL_TEXTURE -> "TEXTURE";
|
||||
case GL11.GL_COLOR -> "COLOR";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getCapabilityName(int cap) {
|
||||
return switch (cap) {
|
||||
case GL11.GL_ALPHA_TEST -> "ALPHA_TEST";
|
||||
case GL11.GL_BLEND -> "BLEND";
|
||||
case GL11.GL_COLOR_MATERIAL -> "COLOR_MATERIAL";
|
||||
case GL11.GL_CULL_FACE -> "CULL_FACE";
|
||||
case GL11.GL_DEPTH_TEST -> "DEPTH_TEST";
|
||||
case GL11.GL_FOG -> "FOG";
|
||||
case GL11.GL_LIGHTING -> "LIGHTING";
|
||||
case GL11.GL_LINE_SMOOTH -> "LINE_SMOOTH";
|
||||
case GL11.GL_NORMALIZE -> "NORMALIZE";
|
||||
case GL11.GL_POINT_SMOOTH -> "POINT_SMOOTH";
|
||||
case GL11.GL_POLYGON_OFFSET_FILL -> "POLYGON_OFFSET_FILL";
|
||||
case GL11.GL_POLYGON_OFFSET_LINE -> "POLYGON_OFFSET_LINE";
|
||||
case GL11.GL_POLYGON_SMOOTH -> "POLYGON_SMOOTH";
|
||||
case GL12.GL_RESCALE_NORMAL -> "RESCALE_NORMAL";
|
||||
case GL11.GL_SCISSOR_TEST -> "SCISSOR_TEST";
|
||||
case GL11.GL_STENCIL_TEST -> "STENCIL_TEST";
|
||||
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
|
||||
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
|
||||
case GL11.GL_LIGHT0 -> "LIGHT0";
|
||||
case GL11.GL_LIGHT1 -> "LIGHT1";
|
||||
case GL11.GL_LIGHT2 -> "LIGHT2";
|
||||
case GL11.GL_LIGHT3 -> "LIGHT3";
|
||||
case GL11.GL_LIGHT4 -> "LIGHT4";
|
||||
case GL11.GL_LIGHT5 -> "LIGHT5";
|
||||
case GL11.GL_LIGHT6 -> "LIGHT6";
|
||||
case GL11.GL_LIGHT7 -> "LIGHT7";
|
||||
default -> String.format("0x%X", cap);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getComparisonFuncName(int func) {
|
||||
return switch (func) {
|
||||
case GL11.GL_NEVER -> "NEVER";
|
||||
case GL11.GL_LESS -> "LESS";
|
||||
case GL11.GL_EQUAL -> "EQUAL";
|
||||
case GL11.GL_LEQUAL -> "LEQUAL";
|
||||
case GL11.GL_GREATER -> "GREATER";
|
||||
case GL11.GL_NOTEQUAL -> "NOTEQUAL";
|
||||
case GL11.GL_GEQUAL -> "GEQUAL";
|
||||
case GL11.GL_ALWAYS -> "ALWAYS";
|
||||
default -> String.format("0x%X", func);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getBlendFactorName(int factor) {
|
||||
return switch (factor) {
|
||||
case GL11.GL_ZERO -> "ZERO";
|
||||
case GL11.GL_ONE -> "ONE";
|
||||
case GL11.GL_SRC_COLOR -> "SRC_COLOR";
|
||||
case GL11.GL_ONE_MINUS_SRC_COLOR -> "ONE_MINUS_SRC_COLOR";
|
||||
case GL11.GL_DST_COLOR -> "DST_COLOR";
|
||||
case GL11.GL_ONE_MINUS_DST_COLOR -> "ONE_MINUS_DST_COLOR";
|
||||
case GL11.GL_SRC_ALPHA -> "SRC_ALPHA";
|
||||
case GL11.GL_ONE_MINUS_SRC_ALPHA -> "ONE_MINUS_SRC_ALPHA";
|
||||
case GL11.GL_DST_ALPHA -> "DST_ALPHA";
|
||||
case GL11.GL_ONE_MINUS_DST_ALPHA -> "ONE_MINUS_DST_ALPHA";
|
||||
case EXTBlendColor.GL_CONSTANT_COLOR_EXT -> "CONSTANT_COLOR";
|
||||
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_COLOR_EXT -> "ONE_MINUS_CONSTANT_COLOR";
|
||||
case EXTBlendColor.GL_CONSTANT_ALPHA_EXT -> "CONSTANT_ALPHA";
|
||||
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_ALPHA_EXT -> "ONE_MINUS_CONSTANT_ALPHA";
|
||||
case GL11.GL_SRC_ALPHA_SATURATE -> "SRC_ALPHA_SATURATE";
|
||||
default -> String.format("0x%X", factor);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getShadeModelName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_FLAT -> "FLAT";
|
||||
case GL11.GL_SMOOTH -> "SMOOTH";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTextureTargetName(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
|
||||
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
|
||||
case GL12.GL_TEXTURE_3D -> "TEXTURE_3D";
|
||||
case GL13.GL_TEXTURE_CUBE_MAP -> "TEXTURE_CUBE_MAP";
|
||||
default -> String.format("0x%X", target);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTexturePnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_MIN_FILTER -> "TEXTURE_MIN_FILTER";
|
||||
case GL11.GL_TEXTURE_MAG_FILTER -> "TEXTURE_MAG_FILTER";
|
||||
case GL11.GL_TEXTURE_WRAP_S -> "TEXTURE_WRAP_S";
|
||||
case GL11.GL_TEXTURE_WRAP_T -> "TEXTURE_WRAP_T";
|
||||
case GL12.GL_TEXTURE_WRAP_R -> "TEXTURE_WRAP_R";
|
||||
case GL12.GL_TEXTURE_MIN_LOD -> "TEXTURE_MIN_LOD";
|
||||
case GL12.GL_TEXTURE_MAX_LOD -> "TEXTURE_MAX_LOD";
|
||||
case GL12.GL_TEXTURE_BASE_LEVEL -> "TEXTURE_BASE_LEVEL";
|
||||
case GL12.GL_TEXTURE_MAX_LEVEL -> "TEXTURE_MAX_LEVEL";
|
||||
case GL11.GL_TEXTURE_BORDER_COLOR -> "TEXTURE_BORDER_COLOR";
|
||||
case GL11.GL_TEXTURE_PRIORITY -> "TEXTURE_PRIORITY";
|
||||
case GL14.GL_TEXTURE_LOD_BIAS -> "TEXTURE_LOD_BIAS";
|
||||
case GL14.GL_GENERATE_MIPMAP -> "GENERATE_MIPMAP";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTextureFormatName(int format) {
|
||||
return switch (format) {
|
||||
case GL11.GL_RED -> "RED";
|
||||
case GL11.GL_GREEN -> "GREEN";
|
||||
case GL11.GL_BLUE -> "BLUE";
|
||||
case GL11.GL_ALPHA -> "ALPHA";
|
||||
case GL11.GL_RGB -> "RGB";
|
||||
case GL11.GL_RGBA -> "RGBA";
|
||||
case GL11.GL_LUMINANCE -> "LUMINANCE";
|
||||
case GL11.GL_LUMINANCE_ALPHA -> "LUMINANCE_ALPHA";
|
||||
case GL12.GL_BGR -> "BGR";
|
||||
case GL12.GL_BGRA -> "BGRA";
|
||||
case GL11.GL_DEPTH_COMPONENT -> "DEPTH_COMPONENT";
|
||||
case GL11.GL_STENCIL_INDEX -> "STENCIL_INDEX";
|
||||
case GL30.GL_DEPTH_STENCIL -> "DEPTH_STENCIL";
|
||||
// Internal formats
|
||||
case GL11.GL_RGB8 -> "RGB8";
|
||||
case GL11.GL_RGBA8 -> "RGBA8";
|
||||
case GL11.GL_ALPHA8 -> "ALPHA8";
|
||||
case GL11.GL_LUMINANCE8 -> "LUMINANCE8";
|
||||
case GL11.GL_LUMINANCE8_ALPHA8 -> "LUMINANCE8_ALPHA8";
|
||||
default -> String.format("0x%X", format);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getDataTypeName(int type) {
|
||||
return switch (type) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
|
||||
case GL11.GL_BYTE -> "BYTE";
|
||||
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
|
||||
case GL11.GL_SHORT -> "SHORT";
|
||||
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
|
||||
case GL11.GL_INT -> "INT";
|
||||
case GL11.GL_FLOAT -> "FLOAT";
|
||||
case GL11.GL_DOUBLE -> "DOUBLE";
|
||||
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
|
||||
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
|
||||
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
|
||||
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
|
||||
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
|
||||
default -> String.format("0x%X", type);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightName(int light) {
|
||||
return switch (light) {
|
||||
case GL11.GL_LIGHT0 -> "LIGHT0";
|
||||
case GL11.GL_LIGHT1 -> "LIGHT1";
|
||||
case GL11.GL_LIGHT2 -> "LIGHT2";
|
||||
case GL11.GL_LIGHT3 -> "LIGHT3";
|
||||
case GL11.GL_LIGHT4 -> "LIGHT4";
|
||||
case GL11.GL_LIGHT5 -> "LIGHT5";
|
||||
case GL11.GL_LIGHT6 -> "LIGHT6";
|
||||
case GL11.GL_LIGHT7 -> "LIGHT7";
|
||||
default -> String.format("0x%X", light);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_POSITION -> "POSITION";
|
||||
case GL11.GL_SPOT_DIRECTION -> "SPOT_DIRECTION";
|
||||
case GL11.GL_SPOT_EXPONENT -> "SPOT_EXPONENT";
|
||||
case GL11.GL_SPOT_CUTOFF -> "SPOT_CUTOFF";
|
||||
case GL11.GL_CONSTANT_ATTENUATION -> "CONSTANT_ATTENUATION";
|
||||
case GL11.GL_LINEAR_ATTENUATION -> "LINEAR_ATTENUATION";
|
||||
case GL11.GL_QUADRATIC_ATTENUATION -> "QUADRATIC_ATTENUATION";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getMaterialPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_EMISSION -> "EMISSION";
|
||||
case GL11.GL_SHININESS -> "SHININESS";
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
|
||||
case GL11.GL_COLOR_INDEXES -> "COLOR_INDEXES";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFaceName(int face) {
|
||||
return switch (face) {
|
||||
case GL11.GL_FRONT -> "FRONT";
|
||||
case GL11.GL_BACK -> "BACK";
|
||||
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
|
||||
default -> String.format("0x%X", face);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getColorMaterialModeName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_EMISSION -> "EMISSION";
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFogPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_FOG_MODE -> "FOG_MODE";
|
||||
case GL11.GL_FOG_DENSITY -> "FOG_DENSITY";
|
||||
case GL11.GL_FOG_START -> "FOG_START";
|
||||
case GL11.GL_FOG_END -> "FOG_END";
|
||||
case GL11.GL_FOG_INDEX -> "FOG_INDEX";
|
||||
case GL11.GL_FOG_COLOR -> "FOG_COLOR";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightModelPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_LIGHT_MODEL_AMBIENT -> "LIGHT_MODEL_AMBIENT";
|
||||
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> "LIGHT_MODEL_LOCAL_VIEWER";
|
||||
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> "LIGHT_MODEL_TWO_SIDE";
|
||||
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> "LIGHT_MODEL_COLOR_CONTROL";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getClearMaskString(int mask) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
sb.append("COLOR");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("DEPTH");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("STENCIL");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_ACCUM_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("ACCUM");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getCullFaceName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_FRONT -> "FRONT";
|
||||
case GL11.GL_BACK -> "BACK";
|
||||
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLogicOpName(int opcode) {
|
||||
return switch (opcode) {
|
||||
case GL11.GL_CLEAR -> "CLEAR";
|
||||
case GL11.GL_AND -> "AND";
|
||||
case GL11.GL_AND_REVERSE -> "AND_REVERSE";
|
||||
case GL11.GL_COPY -> "COPY";
|
||||
case GL11.GL_AND_INVERTED -> "AND_INVERTED";
|
||||
case GL11.GL_NOOP -> "NOOP";
|
||||
case GL11.GL_XOR -> "XOR";
|
||||
case GL11.GL_OR -> "OR";
|
||||
case GL11.GL_NOR -> "NOR";
|
||||
case GL11.GL_EQUIV -> "EQUIV";
|
||||
case GL11.GL_INVERT -> "INVERT";
|
||||
case GL11.GL_OR_REVERSE -> "OR_REVERSE";
|
||||
case GL11.GL_COPY_INVERTED -> "COPY_INVERTED";
|
||||
case GL11.GL_OR_INVERTED -> "OR_INVERTED";
|
||||
case GL11.GL_NAND -> "NAND";
|
||||
case GL11.GL_SET -> "SET";
|
||||
default -> String.format("0x%X", opcode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFramebufferStatusName(int status) {
|
||||
return switch (status) {
|
||||
case GL30.GL_FRAMEBUFFER_COMPLETE -> "COMPLETE";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT -> "INCOMPLETE_ATTACHMENT";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT -> "INCOMPLETE_MISSING_ATTACHMENT";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER -> "INCOMPLETE_DRAW_BUFFER";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER -> "INCOMPLETE_READ_BUFFER";
|
||||
case GL30.GL_FRAMEBUFFER_UNSUPPORTED -> "UNSUPPORTED";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE -> "INCOMPLETE_MULTISAMPLE";
|
||||
case 0 -> "NO_FRAMEBUFFER_BOUND";
|
||||
default -> String.format("UNKNOWN(0x%X)", status);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getInternalFormatName(int format) {
|
||||
return switch (format) {
|
||||
// Basic
|
||||
case GL11.GL_RGBA -> "RGBA";
|
||||
case GL11.GL_RGB -> "RGB";
|
||||
// 8-bit normalized
|
||||
case GL30.GL_R8 -> "R8";
|
||||
case GL30.GL_RG8 -> "RG8";
|
||||
case GL11.GL_RGB8 -> "RGB8";
|
||||
case GL11.GL_RGBA8 -> "RGBA8";
|
||||
// 8-bit signed normalized
|
||||
case 0x8F94 -> "R8_SNORM";
|
||||
case 0x8F95 -> "RG8_SNORM";
|
||||
case 0x8F96 -> "RGB8_SNORM";
|
||||
case 0x8F97 -> "RGBA8_SNORM";
|
||||
// 16-bit normalized
|
||||
case GL30.GL_R16 -> "R16";
|
||||
case GL30.GL_RG16 -> "RG16";
|
||||
case GL11.GL_RGB16 -> "RGB16";
|
||||
case GL11.GL_RGBA16 -> "RGBA16";
|
||||
// 16-bit float
|
||||
case GL30.GL_R16F -> "R16F";
|
||||
case GL30.GL_RG16F -> "RG16F";
|
||||
case GL30.GL_RGB16F -> "RGB16F";
|
||||
case GL30.GL_RGBA16F -> "RGBA16F";
|
||||
// 32-bit float
|
||||
case GL30.GL_R32F -> "R32F";
|
||||
case GL30.GL_RG32F -> "RG32F";
|
||||
case GL30.GL_RGB32F -> "RGB32F";
|
||||
case GL30.GL_RGBA32F -> "RGBA32F";
|
||||
// Packed
|
||||
case GL30.GL_R11F_G11F_B10F -> "R11F_G11F_B10F";
|
||||
case GL11.GL_RGB10_A2 -> "RGB10_A2";
|
||||
default -> String.format("0x%X", format);
|
||||
};
|
||||
}
|
||||
|
||||
private static DebugState debugState;
|
||||
|
||||
private interface DebugState {
|
||||
|
||||
void nameObject(int id, int object, String name);
|
||||
|
||||
void pushGroup(String name);
|
||||
|
||||
void popGroup();
|
||||
|
||||
void debugMessage(String name);
|
||||
|
||||
String getObjectLabel(int glProgram, int program);
|
||||
}
|
||||
|
||||
private static class KHRDebugState implements DebugState {
|
||||
|
||||
private static final int ID = 0;
|
||||
private int depth = 0;
|
||||
private static final int maxDepth = BackendManager.RENDER_BACKEND.getInteger(GL43.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
|
||||
private final String[] groupStack = new String[maxDepth + 1];
|
||||
|
||||
@Override
|
||||
public void nameObject(int id, int object, String name) {
|
||||
BackendManager.RENDER_BACKEND.objectLabel(id, object, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushGroup(String name) {
|
||||
if (depth + 1 > maxDepth) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Debug group stack overflow (maxDepth=").append(maxDepth).append("), current stack:\n");
|
||||
for (int i = 0; i < depth; i++) {
|
||||
sb.append(" [").append(i).append("] ").append(groupStack[i]).append('\n');
|
||||
}
|
||||
sb.append(" -> ").append(name).append(" (rejected)");
|
||||
throw new RuntimeException(sb.toString());
|
||||
}
|
||||
groupStack[depth] = name;
|
||||
BackendManager.RENDER_BACKEND.pushDebugGroup(GL43.GL_DEBUG_SOURCE_APPLICATION, ID, name);
|
||||
depth++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popGroup() {
|
||||
if (depth - 1 < 0) {
|
||||
throw new RuntimeException("Stack underflow");
|
||||
}
|
||||
BackendManager.RENDER_BACKEND.popDebugGroup();
|
||||
depth--;
|
||||
groupStack[depth] = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugMessage(String message) {
|
||||
BackendManager.RENDER_BACKEND.debugMessageInsert(
|
||||
GL43.GL_DEBUG_SOURCE_APPLICATION,
|
||||
GL43.GL_DEBUG_TYPE_MARKER,
|
||||
ID,
|
||||
GL43.GL_DEBUG_SEVERITY_NOTIFICATION,
|
||||
message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getObjectLabel(int glProgram, int program) {
|
||||
if (program == 0) return "";
|
||||
|
||||
return BackendManager.RENDER_BACKEND.getObjectLabel(glProgram, program);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnsupportedDebugState implements DebugState {
|
||||
|
||||
@Override
|
||||
public void nameObject(int id, int object, String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushGroup(String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popGroup() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugMessage(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getObjectLabel(int glProgram, int program) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void initDebugState() {
|
||||
if (BackendManager.RENDER_BACKEND.supportsDebugOutput()) {
|
||||
debugState = new KHRDebugState();
|
||||
} else {
|
||||
debugState = new UnsupportedDebugState();
|
||||
}
|
||||
}
|
||||
|
||||
public static void nameObject(int id, int object, String name) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.nameObject(id, object, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void pushGroup(String group) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.pushGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
public static void popGroup() {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.popGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public static void debugMessage(String message) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.debugMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getObjectLabel(int glProgram, int program) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
return debugState.getObjectLabel(glProgram, program);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,116 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Shared GLSL shader transformation utilities.
|
||||
*/
|
||||
public class GlslTransformUtils {
|
||||
|
||||
private static final String RENAMED_PREFIX = "angelica_renamed_";
|
||||
|
||||
/** Texture function renames */
|
||||
public static final Map<String, String> TEXTURE_RENAMES = Map.ofEntries(
|
||||
Map.entry("texture2D", "texture"),
|
||||
Map.entry("texture3D", "texture"),
|
||||
Map.entry("texture2DLod", "textureLod"),
|
||||
Map.entry("texture3DLod", "textureLod"),
|
||||
Map.entry("texture2DProj", "textureProj"),
|
||||
Map.entry("texture3DProj", "textureProj"),
|
||||
Map.entry("texture2DGrad", "textureGrad"),
|
||||
Map.entry("texture2DGradARB", "textureGrad"),
|
||||
Map.entry("texture3DGrad", "textureGrad"),
|
||||
Map.entry("texelFetch2D", "texelFetch"),
|
||||
Map.entry("texelFetch3D", "texelFetch"),
|
||||
Map.entry("textureSize2D", "textureSize")
|
||||
);
|
||||
|
||||
private static final Pattern TEXTURE_PATTERN = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
|
||||
|
||||
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
|
||||
private record ReservedWordRename(Pattern pattern, String replacement) {}
|
||||
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
|
||||
400, List.of(
|
||||
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"),
|
||||
new ReservedWordRename(Pattern.compile("\\bsampler\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
|
||||
)
|
||||
);
|
||||
|
||||
public static String replaceTexture(String input) {
|
||||
final Matcher matcher = TEXTURE_PATTERN.matcher(input);
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
if (matcher.group(1) != null) {
|
||||
matcher.appendReplacement(builder, RENAMED_PREFIX + "texture");
|
||||
} else {
|
||||
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
|
||||
}
|
||||
}
|
||||
matcher.appendTail(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String renameReservedWords(String source, int targetVersion) {
|
||||
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
|
||||
// Rename identifiers that become reserved words at or above this GLSL version
|
||||
if (targetVersion >= entry.getKey()) {
|
||||
for (var rename : entry.getValue()) {
|
||||
source = rename.pattern().matcher(source).replaceAll(rename.replacement());
|
||||
}
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
public static String restoreReservedWords(String source) {
|
||||
source = source.replace(RENAMED_PREFIX + "texture", "texture");
|
||||
source = source.replace(RENAMED_PREFIX + "sampler", "sampler");
|
||||
source = source.replace(RENAMED_PREFIX + "sample", "sample");
|
||||
return source;
|
||||
}
|
||||
|
||||
public static String getFormattedShader(ParseTree tree, String header) {
|
||||
final StringBuilder sb = new StringBuilder(header + "\n");
|
||||
final String[] tabHolder = {""};
|
||||
getFormattedShader(tree, sb, tabHolder);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
|
||||
if (tree instanceof TerminalNode) {
|
||||
final String text = tree.getText();
|
||||
if (text.equals("<EOF>")) {
|
||||
return;
|
||||
}
|
||||
if (text.equals("#")) {
|
||||
stringBuilder.append("\n#");
|
||||
return;
|
||||
}
|
||||
stringBuilder.append(text);
|
||||
if (text.equals("{")) {
|
||||
stringBuilder.append(" \n\t");
|
||||
tabHolder[0] = "\t";
|
||||
}
|
||||
|
||||
if (text.equals("}")) {
|
||||
if (stringBuilder.length() >= 2) {
|
||||
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
|
||||
}
|
||||
tabHolder[0] = "";
|
||||
stringBuilder.append(" \n");
|
||||
} else {
|
||||
stringBuilder.append(text.equals(";") ? " \n" + tabHolder[0] : " ");
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < tree.getChildCount(); ++i) {
|
||||
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
|
||||
|
||||
/**
|
||||
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
|
||||
*
|
||||
* <p>Maintains a single growable element buffer with pre-computed indices mapping
|
||||
* GL_QUADS vertex order to GL_TRIANGLES: {@code (0,1,3, 1,2,3)} per quad.
|
||||
* Uses {@code GL_LAST_VERTEX_CONVENTION} winding (Mesa {@code do_quad} LAST path)
|
||||
* so both emitted triangles share the quad's provoking vertex (v3).
|
||||
*/
|
||||
public final class QuadConverter {
|
||||
public static final int INDEX_TYPE = GL11.GL_UNSIGNED_INT;
|
||||
|
||||
private static int eboId;
|
||||
private static int maxQuads;
|
||||
|
||||
/** Scratch EBO for converted quad-element draws (drawQuadElementsAsTriangles). */
|
||||
private static int scratchEboId;
|
||||
private static int scratchEboCapacity; // in bytes
|
||||
|
||||
private QuadConverter() {}
|
||||
|
||||
private static void ensureCapacity(int quadCount) {
|
||||
if (quadCount <= maxQuads) return;
|
||||
|
||||
// Round up to power of 2 for amortized growth
|
||||
int newMaxQuads = Math.max(256, maxQuads);
|
||||
while (newMaxQuads < quadCount) {
|
||||
newMaxQuads *= 2;
|
||||
}
|
||||
|
||||
if (eboId == 0) {
|
||||
eboId = RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
// Generate index data: 6 ints per quad
|
||||
final ByteBuffer indexData = memAlloc(newMaxQuads * 6 * 4);
|
||||
long ptr = memAddress0(indexData);
|
||||
for (int i = 0; i < newMaxQuads; i++) {
|
||||
final int base = i * 4;
|
||||
// GL_LAST_VERTEX_CONVENTION: both triangles share v3 as provoking vertex
|
||||
// Triangle 1: 0, 1, 3
|
||||
memPutInt(ptr, base);
|
||||
memPutInt(ptr + 4, base + 1);
|
||||
memPutInt(ptr + 8, base + 3);
|
||||
// Triangle 2: 1, 2, 3
|
||||
memPutInt(ptr + 12, base + 1);
|
||||
memPutInt(ptr + 16, base + 2);
|
||||
memPutInt(ptr + 20, base + 3);
|
||||
ptr += 24;
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
|
||||
|
||||
memFree(indexData);
|
||||
maxQuads = newMaxQuads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a GL_QUADS glDrawArrays call to indexed GL_TRIANGLES.
|
||||
* Binds the shared EBO to the current VAO and issues glDrawElements.
|
||||
*
|
||||
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
|
||||
* @param vertexCount number of vertices (must be multiple of 4)
|
||||
*/
|
||||
public static void drawQuadsAsTriangles(int first, int vertexCount) {
|
||||
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
|
||||
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
|
||||
final int quadCount = vertexCount / 4;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
|
||||
final long indexOffset = (long) (first / 4) * 6 * 4;
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload converted triangle indices to the scratch EBO and draw.
|
||||
* Saves/restores the caller's EBO binding so subsequent indexed draws
|
||||
* (or glGetBufferSubData reads) still see the original EBO.
|
||||
*
|
||||
* @param dst off-heap buffer with triangle index data (freed by this method)
|
||||
* @param triIndexCount number of triangle indices
|
||||
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
|
||||
* @param bytesPerIndex bytes per index element
|
||||
*/
|
||||
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
|
||||
final int needed = triIndexCount * bytesPerIndex;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
|
||||
if (scratchEboId == 0) {
|
||||
scratchEboId = RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
|
||||
|
||||
if (needed > scratchEboCapacity) {
|
||||
// Power-of-2 growth — allocate full capacity, upload actual data
|
||||
int newCap = Math.max(4096, scratchEboCapacity);
|
||||
while (newCap < needed) newCap *= 2;
|
||||
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
|
||||
scratchEboCapacity = newCap;
|
||||
}
|
||||
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
|
||||
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
memFree(dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an indexed GL_QUADS draw to GL_TRIANGLES by reading the source EBO,
|
||||
* building a triangle index buffer, and issuing the converted draw.
|
||||
*
|
||||
* @param indexCount number of quad indices (must be multiple of 4)
|
||||
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
|
||||
* @param offset byte offset into the currently bound EBO
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
|
||||
|
||||
// Read source indices from caller's EBO
|
||||
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
|
||||
final long srcAddr = memAddress0(src);
|
||||
|
||||
// Allocate output as GL_UNSIGNED_INT
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a, b, c, d;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
final long p = srcAddr + (long) i * 16;
|
||||
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
final long p = srcAddr + (long) i * 8;
|
||||
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
|
||||
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
|
||||
}
|
||||
default /* GL_UNSIGNED_BYTE */ -> {
|
||||
final long p = srcAddr + (long) i * 4;
|
||||
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
|
||||
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
|
||||
}
|
||||
}
|
||||
// GL_LAST_VERTEX_CONVENTION: (a,b,d), (b,c,d)
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a);
|
||||
memPutInt(dstAddr + o + 4, b);
|
||||
memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b);
|
||||
memPutInt(dstAddr + o + 16, c);
|
||||
memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
memFree(src);
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side IntBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
final int basePos = indices.position();
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = basePos + i * 4;
|
||||
final int a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ShortBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 2);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
final int basePos = indices.position();
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = basePos + i * 4;
|
||||
final short a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
|
||||
final long o = (long) i * 12;
|
||||
memPutShort(dstAddr + o, a); memPutShort(dstAddr + o + 2, b); memPutShort(dstAddr + o + 4, d);
|
||||
memPutShort(dstAddr + o + 6, b); memPutShort(dstAddr + o + 8, c); memPutShort(dstAddr + o + 10, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
|
||||
if (count == 0) return;
|
||||
assert count % 4 == 0;
|
||||
final int quadCount = count / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
final long srcAddr = memAddress0(indices) + indices.position();
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a, b, c, d;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
final long p = srcAddr + (long) i * 16;
|
||||
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
final long p = srcAddr + (long) i * 8;
|
||||
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
|
||||
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
|
||||
}
|
||||
case GL11.GL_UNSIGNED_BYTE -> {
|
||||
final long p = srcAddr + (long) i * 4;
|
||||
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
|
||||
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
|
||||
}
|
||||
default -> { memFree(dst); return; }
|
||||
}
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the shared EBO.
|
||||
*/
|
||||
public static void destroy() {
|
||||
if (eboId != 0) {
|
||||
RENDER_BACKEND.deleteBuffers(eboId);
|
||||
eboId = 0;
|
||||
maxQuads = 0;
|
||||
}
|
||||
if (scratchEboId != 0) {
|
||||
RENDER_BACKEND.deleteBuffers(scratchEboId);
|
||||
scratchEboId = 0;
|
||||
scratchEboCapacity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3i;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.ARBShaderImageLoadStore;
|
||||
import org.lwjgl.opengl.ARBShaderStorageBufferObject;
|
||||
import org.lwjgl.opengl.EXTShaderImageLoadStore;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.NVXGpuMemoryInfo;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* This class is responsible for abstracting calls to OpenGL and asserting that calls are run on the render thread.
|
||||
*/
|
||||
public class RenderSystem {
|
||||
private static DSAAccess dsaState;
|
||||
private static boolean supportsCompute;
|
||||
private static boolean supportsImageLoadStore;
|
||||
private static boolean supportsSSBO;
|
||||
private static boolean supportsBufferStorage;
|
||||
private static boolean supportsClearTexture;
|
||||
private static boolean supportsTesselation;
|
||||
private static boolean supportsSamplerObjects;
|
||||
private static int maxImageUnits;
|
||||
private static int maxSSBOBindings;
|
||||
private static int maxGlslVersion;
|
||||
private static boolean supportsGpuShader4;
|
||||
|
||||
// Sampler object state tracking (null if unsupported)
|
||||
private static int[] samplers;
|
||||
|
||||
private static final Pattern SEMVER_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)\\.*(?<bugfix>\\d*)(.*)");
|
||||
|
||||
private RenderSystem() {}
|
||||
|
||||
private static boolean rendererInitialized;
|
||||
|
||||
public static void initRenderer() {
|
||||
if (rendererInitialized) return;
|
||||
rendererInitialized = true;
|
||||
try {
|
||||
if (GLStateManager.capabilities.OpenGL45) {
|
||||
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
|
||||
GLStateManager.LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
|
||||
}
|
||||
|
||||
} catch (NoSuchFieldError ignored) {
|
||||
}
|
||||
try {
|
||||
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
|
||||
dsaState = new DSAARB();
|
||||
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
|
||||
}
|
||||
} catch (NoSuchFieldError ignored) {
|
||||
}
|
||||
if (dsaState == null) {
|
||||
dsaState = new DSAUnsupported();
|
||||
GLStateManager.LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
|
||||
}
|
||||
|
||||
BackendManager.init();
|
||||
|
||||
supportsCompute = supportsCompute();
|
||||
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
|
||||
|
||||
supportsImageLoadStore = GLStateManager.capabilities.OpenGL42
|
||||
|| GLStateManager.capabilities.GL_ARB_shader_image_load_store
|
||||
|| GLStateManager.capabilities.GL_EXT_shader_image_load_store;
|
||||
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
|
||||
supportsBufferStorage = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage;
|
||||
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
|
||||
|
||||
// Cache maximum image units
|
||||
if (supportsImageLoadStore) {
|
||||
if (GLStateManager.capabilities.OpenGL42) {
|
||||
maxImageUnits = RENDER_BACKEND.getInteger(GL42.GL_MAX_IMAGE_UNITS);
|
||||
} else if (GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
|
||||
maxImageUnits = RENDER_BACKEND.getInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS);
|
||||
} else {
|
||||
maxImageUnits = RENDER_BACKEND.getInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
|
||||
}
|
||||
} else {
|
||||
maxImageUnits = 0;
|
||||
}
|
||||
|
||||
// Cache maximum SSBO bindings
|
||||
if (supportsSSBO) {
|
||||
if (GLStateManager.capabilities.OpenGL43) {
|
||||
maxSSBOBindings = RENDER_BACKEND.getInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
|
||||
} else {
|
||||
maxSSBOBindings = RENDER_BACKEND.getInteger(ARBShaderStorageBufferObject.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
|
||||
}
|
||||
} else {
|
||||
maxSSBOBindings = 0;
|
||||
}
|
||||
|
||||
// Check for sampler objects support (GL 3.3+ or ARB extension)
|
||||
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
|
||||
if (supportsSamplerObjects) {
|
||||
samplers = new int[RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
|
||||
}
|
||||
|
||||
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
|
||||
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
|
||||
|
||||
GLStateManager.LOGGER.info("Max GLSL version: {}, GPU Shader4: {}", maxGlslVersion, supportsGpuShader4);
|
||||
GLStateManager.LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
|
||||
GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
|
||||
GLStateManager.LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
|
||||
|
||||
if (GLStateManager.capabilities.OpenGL32) {
|
||||
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
|
||||
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
|
||||
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
|
||||
ShaderManager.getInstance().enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateMipmaps(int texture, int mipmapTarget) {
|
||||
dsaState.generateMipmaps(texture, mipmapTarget);
|
||||
}
|
||||
|
||||
public static void bindAttributeLocation(int program, int index, CharSequence name) {
|
||||
RENDER_BACKEND.bindAttribLocation(program, index, name);
|
||||
}
|
||||
|
||||
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
|
||||
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
|
||||
}
|
||||
|
||||
public static void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
|
||||
GLStateManager.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
|
||||
}
|
||||
|
||||
public static void uniform1f(int location, float v0) {
|
||||
RENDER_BACKEND.uniform1f(location, v0);
|
||||
}
|
||||
|
||||
public static void uniform1i(int location, int v0) {
|
||||
RENDER_BACKEND.uniform1i(location, v0);
|
||||
}
|
||||
|
||||
public static void uniform2f(int location, float v0, float v1) {
|
||||
RENDER_BACKEND.uniform2f(location, v0, v1);
|
||||
}
|
||||
|
||||
public static void uniform2i(int location, int v0, int v1) {
|
||||
RENDER_BACKEND.uniform2i(location, v0, v1);
|
||||
}
|
||||
|
||||
public static void uniform3f(int location, float v0, float v1, float v2) {
|
||||
RENDER_BACKEND.uniform3f(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
public static void uniform3i(int location, int v0, int v1, int v2) {RENDER_BACKEND.uniform3i(location, v0, v1, v2);}
|
||||
|
||||
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
|
||||
RENDER_BACKEND.uniform4f(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
|
||||
RENDER_BACKEND.uniform4i(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static int getAttribLocation(int programId, String name) {
|
||||
return RENDER_BACKEND.getAttribLocation(programId, name);
|
||||
}
|
||||
|
||||
public static int getUniformLocation(int programId, String name) {
|
||||
return RENDER_BACKEND.getUniformLocation(programId, name);
|
||||
}
|
||||
|
||||
public static void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
dsaState.texParameteriv(texture, target, pname, params);
|
||||
}
|
||||
|
||||
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
|
||||
}
|
||||
|
||||
public static void texParameteri(int texture, int target, int pname, int param) {
|
||||
dsaState.texParameteri(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void texParameterf(int texture, int target, int pname, float param) {
|
||||
dsaState.texParameterf(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static String getProgramInfoLog(int program) {
|
||||
return RENDER_BACKEND.getProgramInfoLog(program, RENDER_BACKEND.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
|
||||
public static String getShaderInfoLog(int shader) {
|
||||
return RENDER_BACKEND.getShaderInfoLog(shader, RENDER_BACKEND.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
|
||||
public static void drawBuffers(int framebuffer, IntBuffer buffers) {
|
||||
dsaState.drawBuffers(framebuffer, buffers);
|
||||
}
|
||||
|
||||
public static void readBuffer(int framebuffer, int buffer) {
|
||||
dsaState.readBuffer(framebuffer, buffer);
|
||||
}
|
||||
|
||||
public static String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType) {
|
||||
return RENDER_BACKEND.getActiveUniform(program, index, maxLength, sizeType);
|
||||
}
|
||||
|
||||
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
|
||||
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void bufferData(int target, FloatBuffer data, int usage) {
|
||||
RENDER_BACKEND.bufferData(target, data, usage);
|
||||
}
|
||||
|
||||
public static int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
return dsaState.bufferStorage(target, data, usage);
|
||||
}
|
||||
|
||||
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
|
||||
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static void detachShader(int program, int shader) {
|
||||
RENDER_BACKEND.detachShader(program, shader);
|
||||
}
|
||||
|
||||
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
|
||||
}
|
||||
|
||||
public static int getTexParameteri(int texture, int target, int pname) {
|
||||
return dsaState.getTexParameteri(texture, target, pname);
|
||||
}
|
||||
|
||||
public static int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
return dsaState.getTexLevelParameteri(texture, level, pname);
|
||||
}
|
||||
|
||||
public static void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
|
||||
RENDER_BACKEND.bindImageTexture(unit, texture, level, layered, layer, access, format);
|
||||
}
|
||||
|
||||
public static int getMaxImageUnits() {
|
||||
return maxImageUnits;
|
||||
}
|
||||
|
||||
public static boolean supportsImageLoadStore() {
|
||||
return supportsImageLoadStore;
|
||||
}
|
||||
|
||||
public static boolean supportsSSBO() {
|
||||
return supportsSSBO;
|
||||
}
|
||||
|
||||
public static boolean supportsBufferStorage() {
|
||||
return supportsBufferStorage;
|
||||
}
|
||||
|
||||
public static boolean supportsClearTexture() {
|
||||
return supportsClearTexture;
|
||||
}
|
||||
|
||||
public static int getMaxSSBOBindings() {
|
||||
return maxSSBOBindings;
|
||||
}
|
||||
|
||||
public static void getProgramiv(int program, int value, IntBuffer storage) {
|
||||
RENDER_BACKEND.getProgramiv(program, value, storage);
|
||||
}
|
||||
|
||||
public static void dispatchCompute(int workX, int workY, int workZ) {
|
||||
RENDER_BACKEND.dispatchCompute(workX, workY, workZ);
|
||||
}
|
||||
|
||||
public static void dispatchCompute(Vector3i workGroups) {
|
||||
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
|
||||
}
|
||||
|
||||
public static void dispatchComputeIndirect(long offset) {
|
||||
RENDER_BACKEND.dispatchComputeIndirect(offset);
|
||||
}
|
||||
|
||||
public static void bindBuffer(int target, int buffer) {
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
public static void memoryBarrier(int barriers) {
|
||||
if (supportsCompute) {
|
||||
RENDER_BACKEND.memoryBarrier(barriers);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean supportsBufferBlending() {
|
||||
return GLStateManager.capabilities.GL_ARB_draw_buffers_blend || GLStateManager.capabilities.OpenGL40;
|
||||
}
|
||||
|
||||
public static void disableBufferBlend(int buffer) {
|
||||
RENDER_BACKEND.disablei(GL11.GL_BLEND, buffer);
|
||||
GLStateManager.getBlendMode().setUnknownState();
|
||||
}
|
||||
|
||||
public static void enableBufferBlend(int buffer) {
|
||||
RENDER_BACKEND.enablei(GL11.GL_BLEND, buffer);
|
||||
GLStateManager.getBlendMode().setUnknownState();
|
||||
}
|
||||
|
||||
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
RENDER_BACKEND.blendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int unit, int texture) {
|
||||
dsaState.bindTextureToUnit(unit, texture);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int target, int unit, int texture) {
|
||||
dsaState.bindTextureToUnit(target, unit, texture);
|
||||
}
|
||||
|
||||
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
public static void setupProjectionMatrix(Matrix4f matrix) {
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPushMatrix();
|
||||
matrix.get(0, PROJECTION_MATRIX_BUFFER);
|
||||
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
public static void restoreProjectionMatrix() {
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2,
|
||||
int height2, int bufferChoice, int filter) {
|
||||
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
|
||||
}
|
||||
|
||||
public static int createFramebuffer() {
|
||||
return dsaState.createFramebuffer();
|
||||
}
|
||||
|
||||
public static int createTexture(int target) {
|
||||
return dsaState.createTexture(target);
|
||||
}
|
||||
|
||||
// TODO: Proper notification of compute support
|
||||
public static boolean supportsCompute() {
|
||||
try {
|
||||
return GLStateManager.capabilities.GL_ARB_compute_shader;
|
||||
} catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int createBuffers() {
|
||||
return RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
public static void bindBufferBase(int target, int index, int buffer) {
|
||||
RENDER_BACKEND.bindBufferBase(target, index, buffer);
|
||||
}
|
||||
|
||||
public static void bufferStorage(int target, long size, int flags) {
|
||||
RENDER_BACKEND.bufferStorage(target, size, flags);
|
||||
}
|
||||
|
||||
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
|
||||
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
|
||||
buf.asIntBuffer().put(data);
|
||||
RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, buf);
|
||||
}
|
||||
|
||||
public static void deleteBuffers(int buffer) {
|
||||
RENDER_BACKEND.deleteBuffers(buffer);
|
||||
}
|
||||
|
||||
public static long getVRAM() {
|
||||
if (GLStateManager.capabilities.GL_NVX_gpu_memory_info) {
|
||||
return RENDER_BACKEND.getInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1024L;
|
||||
} else {
|
||||
// Default to 4GB if we can't query VRAM
|
||||
return 4294967296L;
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearTexImage(int texture, int target, int level, int format, int type) {
|
||||
RENDER_BACKEND.clearTexImage(texture, level, format, type);
|
||||
}
|
||||
|
||||
public static void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
dsaState.textureStorage1D(texture, target, levels, internalFormat, width);
|
||||
}
|
||||
|
||||
public static void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
dsaState.textureStorage2D(texture, target, levels, internalFormat, width, height);
|
||||
}
|
||||
|
||||
public static void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
dsaState.textureStorage3D(texture, target, levels, internalFormat, width, height, depth);
|
||||
}
|
||||
|
||||
public static int getMaxGlslVersion() {
|
||||
return maxGlslVersion;
|
||||
}
|
||||
|
||||
public static boolean supportsGpuShader4() {
|
||||
return supportsGpuShader4;
|
||||
}
|
||||
|
||||
public static boolean supportsSnormFormats() {
|
||||
return GLStateManager.capabilities.OpenGL31;
|
||||
}
|
||||
|
||||
public static boolean supportsPackedFloatRenderable() {
|
||||
return GLStateManager.capabilities.OpenGL30 && maxGlslVersion >= 130;
|
||||
}
|
||||
|
||||
public static boolean supportsTesselation() {
|
||||
return supportsTesselation;
|
||||
}
|
||||
|
||||
public static boolean supportsSamplerObjects() {
|
||||
return supportsSamplerObjects;
|
||||
}
|
||||
|
||||
public static int genSampler() {
|
||||
if (!supportsSamplerObjects) return 0;
|
||||
return RENDER_BACKEND.genSamplers();
|
||||
}
|
||||
|
||||
public static void destroySampler(int sampler) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
RENDER_BACKEND.deleteSamplers(sampler);
|
||||
}
|
||||
|
||||
public static void samplerParameteri(int sampler, int pname, int param) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
RENDER_BACKEND.samplerParameteri(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void bindSamplerToUnit(int unit, int sampler) {
|
||||
if (!supportsSamplerObjects) return;
|
||||
if (samplers[unit] == sampler) return;
|
||||
RENDER_BACKEND.bindSampler(unit, sampler);
|
||||
samplers[unit] = sampler;
|
||||
}
|
||||
|
||||
public static void unbindAllSamplers() {
|
||||
if (!supportsSamplerObjects) return;
|
||||
for (int i = 0; i < samplers.length; i++) {
|
||||
if (samplers[i] != 0) {
|
||||
RENDER_BACKEND.bindSampler(i, 0);
|
||||
samplers[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Inlined from StandardMacros.getGlVersion(). */
|
||||
static String parseGlVersionString(String info) {
|
||||
final Matcher matcher = SEMVER_PATTERN.matcher(Objects.requireNonNull(info));
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalStateException("Could not parse GL version from \"" + info + "\"");
|
||||
}
|
||||
final String major = matcher.group("major");
|
||||
final String minor = matcher.group("minor");
|
||||
String bugfix = matcher.group("bugfix");
|
||||
if (bugfix == null || bugfix.isEmpty()) bugfix = "0";
|
||||
return major + minor + bugfix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.MultMatrixCmd;
|
||||
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 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 {
|
||||
GLStateManager.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,54 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public final class BackendManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger("BackendManager");
|
||||
|
||||
public static final RenderBackend RENDER_BACKEND = loadService();
|
||||
|
||||
private BackendManager() {}
|
||||
|
||||
public static void init() {
|
||||
RENDER_BACKEND.init();
|
||||
LOGGER.info("Initialized backend: {} (priority {})", RENDER_BACKEND.getName(), RENDER_BACKEND.getPriority());
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
RENDER_BACKEND.shutdown();
|
||||
}
|
||||
|
||||
private static RenderBackend loadService() {
|
||||
final ServiceLoader<RenderBackend> loader = ServiceLoader.load(RenderBackend.class, RenderBackend.class.getClassLoader());
|
||||
|
||||
RenderBackend best = null;
|
||||
final Iterator<RenderBackend> iterator = loader.iterator();
|
||||
int attempts = 0;
|
||||
|
||||
while (attempts < 16) {
|
||||
try {
|
||||
attempts++;
|
||||
if (!iterator.hasNext()) break;
|
||||
final RenderBackend backend = iterator.next();
|
||||
LOGGER.info("Found backend: {} (priority {})", backend.getName(), backend.getPriority());
|
||||
if (backend.isAvailable() && (best == null || backend.getPriority() > best.getPriority())) {
|
||||
best = backend;
|
||||
}
|
||||
} catch (ServiceConfigurationError | LinkageError e) {
|
||||
LOGGER.debug("Skipping unavailable backend: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (best == null) {
|
||||
throw new IllegalStateException("No available render backend!");
|
||||
}
|
||||
|
||||
LOGGER.info("Selected backend: {} (priority {})", best.getName(), best.getPriority());
|
||||
return best;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DebugMessageHandler {
|
||||
void handleMessage(int source, int type, int id, int severity, String message);
|
||||
}
|
||||
+1413
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,312 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Abstract rendering backend.
|
||||
*/
|
||||
public abstract class RenderBackend {
|
||||
|
||||
public abstract void init();
|
||||
public abstract void shutdown();
|
||||
public abstract boolean isAvailable();
|
||||
public abstract String getName();
|
||||
|
||||
/** Higher priority backends are preferred. */
|
||||
public int getPriority() { return 0; }
|
||||
|
||||
public abstract int getMinGLSLVersion();
|
||||
|
||||
public abstract void flush();
|
||||
public abstract void finish();
|
||||
|
||||
public void onFrameBegin() {}
|
||||
public void onFrameEnd() {}
|
||||
|
||||
public abstract void enable(int cap);
|
||||
public abstract void enablei(int cap, int index);
|
||||
public abstract void disable(int cap);
|
||||
public abstract void disablei(int cap, int index);
|
||||
public abstract void blendFuncSeparatei(int buf, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
|
||||
public abstract void blendFunc(int sfactor, int dfactor);
|
||||
public abstract void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
|
||||
public abstract void blendEquation(int mode);
|
||||
public abstract void blendEquationSeparate(int modeRGB, int modeAlpha);
|
||||
public abstract void blendColor(float red, float green, float blue, float alpha);
|
||||
public abstract void depthFunc(int func);
|
||||
public abstract void depthMask(boolean flag);
|
||||
public abstract void colorMask(boolean red, boolean green, boolean blue, boolean alpha);
|
||||
public abstract void cullFace(int mode);
|
||||
public abstract void frontFace(int mode);
|
||||
public abstract void polygonMode(int face, int mode);
|
||||
public abstract void polygonOffset(float factor, float units);
|
||||
public abstract void stencilFunc(int func, int ref, int mask);
|
||||
public abstract void stencilOp(int sfail, int dpfail, int dppass);
|
||||
public abstract void stencilMask(int mask);
|
||||
public abstract void stencilFuncSeparate(int face, int func, int ref, int mask);
|
||||
public abstract void stencilOpSeparate(int face, int sfail, int dpfail, int dppass);
|
||||
public abstract void stencilMaskSeparate(int face, int mask);
|
||||
public abstract void viewport(int x, int y, int width, int height);
|
||||
public abstract void depthRange(double nearVal, double farVal);
|
||||
public abstract void scissor(int x, int y, int width, int height);
|
||||
public abstract void clearColor(float red, float green, float blue, float alpha);
|
||||
public abstract void clearDepth(double depth);
|
||||
public abstract void clearStencil(int s);
|
||||
public abstract void clear(int mask);
|
||||
public abstract void lineWidth(float width);
|
||||
public abstract void pointSize(float size);
|
||||
public abstract void logicOp(int opcode);
|
||||
public abstract void hint(int target, int mode);
|
||||
|
||||
public abstract void drawArrays(int mode, int first, int count);
|
||||
public abstract void drawElements(int mode, int count, int type, long indices);
|
||||
public void drawElements(int mode, ByteBuffer indices) {}
|
||||
public void drawElements(int mode, IntBuffer indices) {}
|
||||
public void drawElements(int mode, ShortBuffer indices) {}
|
||||
public void drawElements(int mode, int count, int type, ByteBuffer indices) {}
|
||||
public abstract void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
|
||||
public abstract void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size);
|
||||
public abstract void drawElementsInstanced(int mode, int count, int type, long indices, int primcount);
|
||||
public abstract void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex);
|
||||
public abstract void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex);
|
||||
public abstract void drawBuffer(int mode);
|
||||
public abstract void dispatchCompute(int numGroupsX, int numGroupsY, int numGroupsZ);
|
||||
public abstract void dispatchComputeIndirect(long offset);
|
||||
|
||||
public abstract int genTextures();
|
||||
public abstract void genTextures(IntBuffer textures);
|
||||
public abstract void deleteTextures(int texture);
|
||||
public abstract void bindTexture(int target, int texture);
|
||||
public abstract void activeTexture(int texture);
|
||||
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels);
|
||||
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels);
|
||||
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels);
|
||||
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels);
|
||||
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset);
|
||||
public abstract void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
|
||||
public abstract void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
|
||||
public abstract void copyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height);
|
||||
public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {}
|
||||
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {}
|
||||
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {}
|
||||
public void texSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {}
|
||||
public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {}
|
||||
public void texSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {}
|
||||
public void copyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {}
|
||||
public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {}
|
||||
public void copyTexSubImage1D(int target, int level, int xoffset, int x, int y, int width) {}
|
||||
public void copyTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int x, int y, int width, int height) {}
|
||||
public abstract void texParameteri(int target, int pname, int param);
|
||||
public abstract void texParameterf(int target, int pname, float param);
|
||||
public abstract void texParameteriv(int target, int pname, IntBuffer params);
|
||||
public abstract void texParameterfv(int target, int pname, FloatBuffer params);
|
||||
public abstract int getTexParameteri(int target, int pname);
|
||||
public abstract float getTexParameterf(int target, int pname);
|
||||
public abstract int getTexLevelParameteri(int target, int level, int pname);
|
||||
public abstract void generateMipmap(int target);
|
||||
public abstract void pixelStorei(int pname, int param);
|
||||
public void pixelStoref(int pname, float param) {}
|
||||
public void sampleCoverage(float value, boolean invert) {}
|
||||
|
||||
|
||||
public abstract int genSamplers();
|
||||
public abstract void deleteSamplers(int sampler);
|
||||
public abstract void bindSampler(int unit, int sampler);
|
||||
public abstract void samplerParameteri(int sampler, int pname, int param);
|
||||
public abstract void samplerParameterf(int sampler, int pname, float param);
|
||||
|
||||
public abstract int genFramebuffers();
|
||||
public abstract void deleteFramebuffers(int framebuffer);
|
||||
public abstract void bindFramebuffer(int target, int framebuffer);
|
||||
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level);
|
||||
public abstract void framebufferTexture(int target, int attachment, int texture, int level);
|
||||
public abstract int checkFramebufferStatus(int target);
|
||||
public abstract void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
|
||||
public abstract void drawBuffers(int buffer);
|
||||
public abstract void drawBuffers(IntBuffer bufs);
|
||||
public abstract void readBuffer(int mode);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
|
||||
public abstract int getFramebufferAttachmentParameteri(int target, int attachment, int pname);
|
||||
|
||||
public abstract int createShader(int type);
|
||||
public abstract void deleteShader(int shader);
|
||||
public abstract void shaderSource(int shader, CharSequence source);
|
||||
public abstract void compileShader(int shader);
|
||||
public abstract int createProgram();
|
||||
public abstract void deleteProgram(int program);
|
||||
public abstract void attachShader(int program, int shader);
|
||||
public abstract void detachShader(int program, int shader);
|
||||
public abstract void linkProgram(int program);
|
||||
public abstract void useProgram(int program);
|
||||
public abstract String getShaderInfoLog(int shader, int maxLength);
|
||||
public String getShaderInfoLog(int shader) {
|
||||
return getShaderInfoLog(shader, getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
public abstract void getShaderInfoLog(int shader, IntBuffer length, ByteBuffer infoLog);
|
||||
public abstract String getProgramInfoLog(int program, int maxLength);
|
||||
public String getProgramInfoLog(int program) {
|
||||
return getProgramInfoLog(program, getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
public abstract void getProgramInfoLog(int program, IntBuffer length, ByteBuffer infoLog);
|
||||
public abstract int getShaderi(int shader, int pname);
|
||||
public abstract int getProgrami(int program, int pname);
|
||||
public abstract void getProgramiv(int program, int pname, IntBuffer params);
|
||||
public abstract String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType);
|
||||
public abstract void getActiveUniform(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name);
|
||||
public abstract void bindAttribLocation(int program, int index, CharSequence name);
|
||||
public abstract int getAttribLocation(int program, CharSequence name);
|
||||
public abstract int getAttribLocation(int program, ByteBuffer name);
|
||||
public abstract int getUniformLocation(int program, CharSequence name);
|
||||
public abstract int getUniformLocation(int program, ByteBuffer name);
|
||||
public boolean isShader(int obj) { return false; }
|
||||
public boolean isProgram(int obj) { return false; }
|
||||
public void validateProgram(int program) {}
|
||||
public void getAttachedShaders(int program, IntBuffer count, IntBuffer shaders) {
|
||||
if (count != null) count.put(0, 0);
|
||||
}
|
||||
public String getShaderSource(int shader, int maxLength) { return ""; }
|
||||
public abstract void getShaderSource(int shader, IntBuffer length, ByteBuffer source);
|
||||
|
||||
public abstract void uniform1i(int location, int v0);
|
||||
public abstract void uniform1f(int location, float v0);
|
||||
public void uniform1fv(int location, FloatBuffer values) {}
|
||||
public abstract void uniform2f(int location, float v0, float v1);
|
||||
public abstract void uniform2i(int location, int v0, int v1);
|
||||
public abstract void uniform3f(int location, float v0, float v1, float v2);
|
||||
public abstract void uniform3i(int location, int v0, int v1, int v2);
|
||||
public abstract void uniform4f(int location, float v0, float v1, float v2, float v3);
|
||||
public abstract void uniform4i(int location, int v0, int v1, int v2, int v3);
|
||||
public abstract void uniform3(int location, FloatBuffer value);
|
||||
public abstract void uniform4(int location, FloatBuffer value);
|
||||
public abstract void uniformMatrix3(int location, boolean transpose, FloatBuffer value);
|
||||
public abstract void uniformMatrix4(int location, boolean transpose, FloatBuffer value);
|
||||
public void uniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {}
|
||||
public void getUniformfv(int program, int location, FloatBuffer params) {}
|
||||
public void getUniformiv(int program, int location, IntBuffer params) {}
|
||||
|
||||
public abstract void vertexAttrib2f(int index, float v0, float v1);
|
||||
public abstract void vertexAttrib3f(int index, float v0, float v1, float v2);
|
||||
public abstract void vertexAttrib4f(int index, float v0, float v1, float v2, float v3);
|
||||
|
||||
public abstract int genBuffers();
|
||||
public abstract void deleteBuffers(int buffer);
|
||||
public abstract void deleteBuffers(IntBuffer buffers);
|
||||
public abstract void bindBuffer(int target, int buffer);
|
||||
public abstract void bindBufferBase(int target, int index, int buffer);
|
||||
public abstract void bufferData(int target, long size, int usage);
|
||||
public abstract void bufferData(int target, ByteBuffer data, int usage);
|
||||
public abstract void bufferData(int target, FloatBuffer data, int usage);
|
||||
public abstract void bufferData(int target, ShortBuffer data, int usage);
|
||||
public abstract void bufferData(int target, int[] data, int usage);
|
||||
public abstract void bufferData(int target, float[] data, int usage);
|
||||
public abstract void bufferSubData(int target, long offset, ByteBuffer data);
|
||||
public abstract ByteBuffer mapBuffer(int target, int access);
|
||||
public abstract ByteBuffer mapBuffer(int target, int access, long length, ByteBuffer old_buffer);
|
||||
public abstract boolean unmapBuffer(int target);
|
||||
public abstract void bufferStorage(int target, ByteBuffer data, int flags);
|
||||
public abstract void bufferStorage(int target, long size, int flags);
|
||||
public abstract void getBufferSubData(int target, long offset, ByteBuffer data);
|
||||
public abstract int getBufferParameteri(int target, int pname);
|
||||
public abstract boolean isBuffer(int buffer);
|
||||
public abstract ByteBuffer mapBufferRange(int target, long offset, long length, int access);
|
||||
|
||||
public abstract int genVertexArrays();
|
||||
public abstract void deleteVertexArrays(int array);
|
||||
public abstract void bindVertexArray(int array);
|
||||
public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer);
|
||||
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
|
||||
vertexAttribPointer(index, size, type, normalized, stride, MemoryUtilities.memAddress0(pointer));
|
||||
}
|
||||
public void vertexAttribIPointer(int index, int size, int type, int stride, ByteBuffer pointer) {
|
||||
vertexAttribIPointer(index, size, type, stride, MemoryUtilities.memAddress0(pointer));
|
||||
}
|
||||
public abstract void vertexAttribIPointer(int index, int size, int type, int stride, long pointer);
|
||||
public abstract void enableVertexAttribArray(int index);
|
||||
public abstract void disableVertexAttribArray(int index);
|
||||
public abstract void vertexAttribDivisor(int index, int divisor);
|
||||
public abstract void bindVertexBuffer(int bindingindex, int buffer, long offset, int stride);
|
||||
public abstract void vertexAttribFormat(int attribindex, int size, int type, boolean normalized, int relativeoffset);
|
||||
public abstract void vertexAttribIFormat(int attribindex, int size, int type, int relativeoffset);
|
||||
public abstract void vertexAttribBinding(int attribindex, int bindingindex);
|
||||
|
||||
public abstract int createTextures(int target);
|
||||
public abstract void bindTextureUnit(int unit, int texture);
|
||||
public abstract void textureParameteri(int texture, int target, int pname, int param);
|
||||
public abstract void textureParameterf(int texture, int target, int pname, float param);
|
||||
public abstract void textureParameteriv(int texture, int target, int pname, IntBuffer params);
|
||||
public abstract void texStorage1D(int target, int levels, int internalFormat, int width);
|
||||
public abstract void texStorage2D(int target, int levels, int internalFormat, int width, int height);
|
||||
public abstract void texStorage3D(int target, int levels, int internalFormat, int width, int height, int depth);
|
||||
public abstract void textureStorage1D(int texture, int levels, int internalFormat, int width);
|
||||
public abstract void textureStorage2D(int texture, int levels, int internalFormat, int width, int height);
|
||||
public abstract void textureStorage3D(int texture, int levels, int internalFormat, int width, int height, int depth);
|
||||
public abstract void generateTextureMipmap(int texture);
|
||||
public abstract void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
|
||||
public abstract void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
|
||||
public abstract void textureSubImage2D(int texture, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
|
||||
public abstract void textureSubImage2D(int texture, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
|
||||
public abstract int createFramebuffers();
|
||||
public abstract void namedFramebufferTexture(int framebuffer, int attachment, int texture, int level);
|
||||
public abstract void namedFramebufferReadBuffer(int framebuffer, int mode);
|
||||
public abstract void namedFramebufferDrawBuffers(int framebuffer, IntBuffer bufs);
|
||||
public abstract void blitNamedFramebuffer(int readFramebuffer, int drawFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
|
||||
public abstract int createBuffers();
|
||||
public abstract void namedBufferData(int buffer, ByteBuffer data, int usage);
|
||||
public abstract void namedBufferData(int buffer, FloatBuffer data, int usage);
|
||||
public abstract void namedBufferSubData(int buffer, long offset, ByteBuffer data);
|
||||
public abstract void copyTextureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int x, int y, int width, int height);
|
||||
public abstract int getTextureParameteri(int texture, int target, int pname);
|
||||
public abstract int getTextureLevelParameteri(int texture, int level, int pname);
|
||||
|
||||
public abstract int getInteger(int pname);
|
||||
public abstract void getInteger(int pname, IntBuffer params);
|
||||
public abstract float getFloat(int pname);
|
||||
public abstract void getFloat(int pname, FloatBuffer params);
|
||||
public abstract boolean getBoolean(int pname);
|
||||
public abstract void getBoolean(int pname, ByteBuffer params);
|
||||
public abstract String getString(int pname);
|
||||
public abstract String getStringi(int name, int index);
|
||||
public abstract int getError();
|
||||
|
||||
public abstract long fenceSync(int condition, int flags);
|
||||
public abstract int clientWaitSync(long sync, int flags, long timeout);
|
||||
public abstract void deleteSync(long sync);
|
||||
|
||||
public abstract void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data);
|
||||
public abstract void clearTexImage(int texture, int level, int format, int type);
|
||||
|
||||
public abstract void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format);
|
||||
public abstract void memoryBarrier(int barriers);
|
||||
|
||||
public abstract void copyImageSubData(int srcName, int srcTarget, int srcLevel, int srcX, int srcY, int srcZ,
|
||||
int dstName, int dstTarget, int dstLevel, int dstX, int dstY, int dstZ,
|
||||
int srcWidth, int srcHeight, int srcDepth);
|
||||
|
||||
// Debug output
|
||||
public boolean supportsDebugOutput() { return false; }
|
||||
public int setupDebugOutput(DebugMessageHandler handler) { return 0; }
|
||||
public int disableDebugOutput() { return 0; }
|
||||
|
||||
// Object labeling
|
||||
public void objectLabel(int identifier, int name, CharSequence label) {}
|
||||
public String getObjectLabel(int identifier, int name) { return ""; }
|
||||
|
||||
// Debug groups
|
||||
public void pushDebugGroup(int source, int id, CharSequence message) {}
|
||||
public void popDebugGroup() {}
|
||||
|
||||
// Debug message insertion
|
||||
public void debugMessageInsert(int source, int type, int id, int severity, CharSequence message) {}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.util.glu.Cylinder;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_LINES;
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
|
||||
import static org.lwjgl.opengl.GL11.GL_POINTS;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FILL;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_LINE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_POINT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
|
||||
|
||||
/**
|
||||
* Core-profile compatible replacement for {@link Cylinder}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaCylinder extends Cylinder {
|
||||
|
||||
private static final float PI = (float) Math.PI;
|
||||
|
||||
private void emitNormal(float x, float y, float z) {
|
||||
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
|
||||
if (mag > 0.00001F) {
|
||||
x /= mag;
|
||||
y /= mag;
|
||||
z /= mag;
|
||||
}
|
||||
GLStateManager.glNormal3f(x, y, z);
|
||||
}
|
||||
|
||||
private void emitTexCoord(float x, float y) {
|
||||
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(float baseRadius, float topRadius, float height, int slices, int stacks) {
|
||||
final float da, dr, dz, nz, nsign;
|
||||
float r, x, y, z;
|
||||
|
||||
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
|
||||
|
||||
da = 2.0f * PI / slices;
|
||||
dr = (topRadius - baseRadius) / stacks;
|
||||
dz = height / stacks;
|
||||
nz = (baseRadius - topRadius) / height;
|
||||
|
||||
if (super.drawStyle == GLU_POINT) {
|
||||
GLStateManager.glBegin(GL_POINTS);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
|
||||
z = 0.0f;
|
||||
r = baseRadius;
|
||||
for (int j = 0; j <= stacks; j++) {
|
||||
GLStateManager.glVertex3f(x * r, y * r, z);
|
||||
z += dz;
|
||||
r += dr;
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
|
||||
// Draw rings
|
||||
if (super.drawStyle == GLU_LINE) {
|
||||
z = 0.0f;
|
||||
r = baseRadius;
|
||||
for (int j = 0; j <= stacks; j++) {
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
GLStateManager.glVertex3f(x * r, y * r, z);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
z += dz;
|
||||
r += dr;
|
||||
}
|
||||
} else {
|
||||
// draw one ring at each end
|
||||
if (baseRadius != 0.0) {
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
// draw length lines
|
||||
GLStateManager.glBegin(GL_LINES);
|
||||
for (int i = 0; i < slices; i++) {
|
||||
x = cos(i * da);
|
||||
y = sin(i * da);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
|
||||
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
} else if (super.drawStyle == GLU_FILL) {
|
||||
final float ds = 1.0f / slices;
|
||||
final float dt = 1.0f / stacks;
|
||||
float t = 0.0f;
|
||||
z = 0.0f;
|
||||
r = baseRadius;
|
||||
for (int j = 0; j < stacks; j++) {
|
||||
float s = 0.0f;
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
final float angle = (i == slices) ? 0.0f : i * da;
|
||||
x = sin(angle);
|
||||
y = cos(angle);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
emitTexCoord(s, t);
|
||||
GLStateManager.glVertex3f(x * r, y * r, z);
|
||||
emitNormal(x * nsign, y * nsign, nz * nsign);
|
||||
emitTexCoord(s, t + dt);
|
||||
GLStateManager.glVertex3f(x * (r + dr), y * (r + dr), z + dz);
|
||||
s += ds;
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
r += dr;
|
||||
t += dt;
|
||||
z += dz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.util.glu.Disk;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
|
||||
import static org.lwjgl.opengl.GL11.GL_POINTS;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FILL;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_LINE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_NONE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_POINT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
|
||||
|
||||
/**
|
||||
* Core-profile compatible replacement for {@link Disk}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaDisk extends Disk {
|
||||
|
||||
private static final float PI = (float) Math.PI;
|
||||
|
||||
private void emitTexCoord(float x, float y) {
|
||||
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(float innerRadius, float outerRadius, int slices, int loops) {
|
||||
final float da, dr;
|
||||
|
||||
if (super.normals != GLU_NONE) {
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
|
||||
}
|
||||
|
||||
da = 2.0f * PI / slices;
|
||||
dr = (outerRadius - innerRadius) / loops;
|
||||
|
||||
switch (super.drawStyle) {
|
||||
case GLU_FILL -> {
|
||||
final float dtc = 2.0f * outerRadius;
|
||||
float sa, ca;
|
||||
float r1 = innerRadius;
|
||||
for (int l = 0; l < loops; l++) {
|
||||
final float r2 = r1 + dr;
|
||||
if (super.orientation == GLU_OUTSIDE) {
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
for (int s = 0; s <= slices; s++) {
|
||||
final float a = (s == slices) ? 0.0f : s * da;
|
||||
sa = sin(a);
|
||||
ca = cos(a);
|
||||
emitTexCoord(0.5f + sa * r2 / dtc, 0.5f + ca * r2 / dtc);
|
||||
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
|
||||
emitTexCoord(0.5f + sa * r1 / dtc, 0.5f + ca * r1 / dtc);
|
||||
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
} else {
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
for (int s = slices; s >= 0; s--) {
|
||||
final float a = (s == slices) ? 0.0f : s * da;
|
||||
sa = sin(a);
|
||||
ca = cos(a);
|
||||
emitTexCoord(0.5f - sa * r2 / dtc, 0.5f + ca * r2 / dtc);
|
||||
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
|
||||
emitTexCoord(0.5f - sa * r1 / dtc, 0.5f + ca * r1 / dtc);
|
||||
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
r1 = r2;
|
||||
}
|
||||
}
|
||||
case GLU_LINE -> {
|
||||
/* draw loops */
|
||||
for (int l = 0; l <= loops; l++) {
|
||||
final float r = innerRadius + l * dr;
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int s = 0; s < slices; s++) {
|
||||
final float a = s * da;
|
||||
GLStateManager.glVertex2f(r * sin(a), r * cos(a));
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
/* draw spokes */
|
||||
for (int s = 0; s < slices; s++) {
|
||||
final float a = s * da;
|
||||
final float x = sin(a);
|
||||
final float y = cos(a);
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int l = 0; l <= loops; l++) {
|
||||
final float r = innerRadius + l * dr;
|
||||
GLStateManager.glVertex2f(r * x, r * y);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
case GLU_POINT -> {
|
||||
GLStateManager.glBegin(GL_POINTS);
|
||||
for (int s = 0; s < slices; s++) {
|
||||
final float a = s * da;
|
||||
final float x = sin(a);
|
||||
final float y = cos(a);
|
||||
for (int l = 0; l <= loops; l++) {
|
||||
final float r = innerRadius * l * dr;
|
||||
GLStateManager.glVertex2f(r * x, r * y);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
case GLU_SILHOUETTE -> {
|
||||
if (innerRadius != 0.0) {
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (float a = 0.0f; a < 2.0 * PI; a += da) {
|
||||
final float x = innerRadius * sin(a);
|
||||
final float y = innerRadius * cos(a);
|
||||
GLStateManager.glVertex2f(x, y);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (float a = 0; a < 2.0f * PI; a += da) {
|
||||
final float x = outerRadius * sin(a);
|
||||
final float y = outerRadius * cos(a);
|
||||
GLStateManager.glVertex2f(x, y);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.util.glu.PartialDisk;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
|
||||
import static org.lwjgl.opengl.GL11.GL_POINTS;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FILL;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FLAT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_LINE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_POINT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SMOOTH;
|
||||
|
||||
/**
|
||||
* Core-profile compatible replacement for {@link PartialDisk}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaPartialDisk extends PartialDisk {
|
||||
|
||||
private static final float PI = (float) Math.PI;
|
||||
private static final int CACHE_SIZE = 240;
|
||||
private final float[] sinCache = new float[CACHE_SIZE];
|
||||
private final float[] cosCache = new float[CACHE_SIZE];
|
||||
|
||||
@Override
|
||||
public void draw(float innerRadius, float outerRadius, int slices, int loops, float startAngle, float sweepAngle) {
|
||||
final float deltaRadius, angleOffset;
|
||||
float angle, sintemp, costemp, radiusLow, radiusHigh, texLow = 0, texHigh = 0;
|
||||
final int slices2, finish;
|
||||
|
||||
if (slices >= CACHE_SIZE) slices = CACHE_SIZE - 1;
|
||||
if (slices < 2 || loops < 1 || outerRadius <= 0.0f || innerRadius < 0.0f || innerRadius > outerRadius) {
|
||||
GLStateManager.LOGGER.warn("PartialDisk: GLU_INVALID_VALUE (slices={}, loops={}, innerRadius={}, outerRadius={})", slices, loops, innerRadius, outerRadius);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sweepAngle < -360.0f) sweepAngle = 360.0f;
|
||||
if (sweepAngle > 360.0f) sweepAngle = 360.0f;
|
||||
if (sweepAngle < 0) {
|
||||
startAngle += sweepAngle;
|
||||
sweepAngle = -sweepAngle;
|
||||
}
|
||||
|
||||
slices2 = (sweepAngle == 360.0f) ? slices : slices + 1;
|
||||
|
||||
/* Compute length (needed for normal calculations) */
|
||||
deltaRadius = outerRadius - innerRadius;
|
||||
|
||||
angleOffset = startAngle / 180.0f * PI;
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
angle = angleOffset + ((PI * sweepAngle) / 180.0f) * i / slices;
|
||||
sinCache[i] = sin(angle);
|
||||
cosCache[i] = cos(angle);
|
||||
}
|
||||
|
||||
if (sweepAngle == 360.0f) {
|
||||
sinCache[slices] = sinCache[0];
|
||||
cosCache[slices] = cosCache[0];
|
||||
}
|
||||
|
||||
switch (super.normals) {
|
||||
case GLU_FLAT, GLU_SMOOTH -> GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
|
||||
switch (super.drawStyle) {
|
||||
case GLU_FILL -> {
|
||||
if (innerRadius == .0f) {
|
||||
finish = loops - 1;
|
||||
/* Triangle strip for inner polygons */
|
||||
GLStateManager.glBegin(GL_TRIANGLE_FAN);
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(0.5f, 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
|
||||
radiusLow = outerRadius - deltaRadius * ((float) (loops - 1) / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
}
|
||||
|
||||
if (super.orientation == GLU_OUTSIDE) {
|
||||
for (int i = slices; i >= 0; i--) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
} else {
|
||||
finish = loops;
|
||||
}
|
||||
for (int j = 0; j < finish; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
radiusHigh = outerRadius - deltaRadius * ((float) (j + 1) / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
texHigh = radiusHigh / outerRadius / 2;
|
||||
}
|
||||
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.orientation == GLU_OUTSIDE) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
|
||||
} else {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
|
||||
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
case GLU_POINT -> {
|
||||
GLStateManager.glBegin(GL_POINTS);
|
||||
for (int i = 0; i < slices2; i++) {
|
||||
sintemp = sinCache[i];
|
||||
costemp = cosCache[i];
|
||||
for (int j = 0; j <= loops; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
case GLU_LINE -> {
|
||||
if (innerRadius == outerRadius) {
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(sinCache[i] / 2 + 0.5f, cosCache[i] / 2 + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(innerRadius * sinCache[i], innerRadius * cosCache[i], 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j <= loops; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
}
|
||||
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
for (int i = 0; i < slices2; i++) {
|
||||
sintemp = sinCache[i];
|
||||
costemp = cosCache[i];
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int j = 0; j <= loops; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
}
|
||||
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
case GLU_SILHOUETTE -> {
|
||||
if (sweepAngle < 360.0f) {
|
||||
for (int i = 0; i <= slices; i += slices) {
|
||||
sintemp = sinCache[i];
|
||||
costemp = cosCache[i];
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int j = 0; j <= loops; j++) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
for (int j = 0; j <= loops; j += loops) {
|
||||
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
|
||||
if (super.textureFlag) {
|
||||
texLow = radiusLow / outerRadius / 2;
|
||||
}
|
||||
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int i = 0; i <= slices; i++) {
|
||||
if (super.textureFlag) {
|
||||
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
|
||||
}
|
||||
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
if (innerRadius == outerRadius) break;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.util.glu.Sphere;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
|
||||
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
|
||||
import static org.lwjgl.opengl.GL11.GL_POINTS;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_FILL;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_LINE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_NONE;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_POINT;
|
||||
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
|
||||
|
||||
/**
|
||||
* Core-profile compatible replacement for {@link Sphere}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaSphere extends Sphere {
|
||||
|
||||
private static final float PI = (float) Math.PI;
|
||||
|
||||
private void emitNormal(float x, float y, float z) {
|
||||
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
|
||||
if (mag > 0.00001F) {
|
||||
x /= mag;
|
||||
y /= mag;
|
||||
z /= mag;
|
||||
}
|
||||
GLStateManager.glNormal3f(x, y, z);
|
||||
}
|
||||
|
||||
private void emitTexCoord(float x, float y) {
|
||||
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(float radius, int slices, int stacks) {
|
||||
final float drho, dtheta, ds, dt, nsign;
|
||||
float rho, theta, x, y, z, s, t;
|
||||
|
||||
final int imin, imax;
|
||||
final boolean normals;
|
||||
|
||||
normals = super.normals != GLU_NONE;
|
||||
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
|
||||
|
||||
drho = PI / stacks;
|
||||
dtheta = 2.0f * PI / slices;
|
||||
|
||||
if (super.drawStyle == GLU_FILL) {
|
||||
if (!super.textureFlag) {
|
||||
// draw +Z end as a triangle fan
|
||||
GLStateManager.glBegin(GL_TRIANGLE_FAN);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, nsign * radius);
|
||||
for (int j = 0; j <= slices; j++) {
|
||||
theta = (j == slices) ? 0.0f : j * dtheta;
|
||||
x = -sin(theta) * sin(drho);
|
||||
y = cos(theta) * sin(drho);
|
||||
z = nsign * cos(drho);
|
||||
if (normals) {
|
||||
emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
}
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
ds = 1.0f / slices;
|
||||
dt = 1.0f / stacks;
|
||||
t = 1.0f;
|
||||
if (super.textureFlag) {
|
||||
imin = 0;
|
||||
imax = stacks;
|
||||
} else {
|
||||
imin = 1;
|
||||
imax = stacks - 1;
|
||||
}
|
||||
|
||||
// draw intermediate stacks as triangle strips (GL_QUAD_STRIP removed in core profile)
|
||||
for (int i = imin; i < imax; i++) {
|
||||
rho = i * drho;
|
||||
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
|
||||
s = 0.0f;
|
||||
for (int j = 0; j <= slices; j++) {
|
||||
theta = (j == slices) ? 0.0f : j * dtheta;
|
||||
x = -sin(theta) * sin(rho);
|
||||
y = cos(theta) * sin(rho);
|
||||
z = nsign * cos(rho);
|
||||
if (normals) {
|
||||
emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
}
|
||||
emitTexCoord(s, t);
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
x = -sin(theta) * sin(rho + drho);
|
||||
y = cos(theta) * sin(rho + drho);
|
||||
z = nsign * cos(rho + drho);
|
||||
if (normals) {
|
||||
emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
}
|
||||
emitTexCoord(s, t - dt);
|
||||
s += ds;
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
t -= dt;
|
||||
}
|
||||
|
||||
if (!super.textureFlag) {
|
||||
// draw -Z end as a triangle fan
|
||||
GLStateManager.glBegin(GL_TRIANGLE_FAN);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, -1.0f);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, -radius * nsign);
|
||||
rho = PI - drho;
|
||||
s = 1.0f;
|
||||
for (int j = slices; j >= 0; j--) {
|
||||
theta = (j == slices) ? 0.0f : j * dtheta;
|
||||
x = -sin(theta) * sin(rho);
|
||||
y = cos(theta) * sin(rho);
|
||||
z = nsign * cos(rho);
|
||||
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
s -= ds;
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
|
||||
// draw stack lines
|
||||
for (int i = 1; i < stacks; i++) {
|
||||
rho = i * drho;
|
||||
GLStateManager.glBegin(GL_LINE_LOOP);
|
||||
for (int j = 0; j < slices; j++) {
|
||||
theta = j * dtheta;
|
||||
x = cos(theta) * sin(rho);
|
||||
y = sin(theta) * sin(rho);
|
||||
z = cos(rho);
|
||||
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
// draw slice lines
|
||||
for (int j = 0; j < slices; j++) {
|
||||
theta = j * dtheta;
|
||||
GLStateManager.glBegin(GL_LINE_STRIP);
|
||||
for (int i = 0; i <= stacks; i++) {
|
||||
rho = i * drho;
|
||||
x = cos(theta) * sin(rho);
|
||||
y = sin(theta) * sin(rho);
|
||||
z = cos(rho);
|
||||
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
} else if (super.drawStyle == GLU_POINT) {
|
||||
// top and bottom-most points
|
||||
GLStateManager.glBegin(GL_POINTS);
|
||||
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, nsign);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, radius);
|
||||
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, -nsign);
|
||||
GLStateManager.glVertex3f(0.0f, 0.0f, -radius);
|
||||
|
||||
// loop over stacks
|
||||
for (int i = 1; i < stacks - 1; i++) {
|
||||
rho = i * drho;
|
||||
for (int j = 0; j < slices; j++) {
|
||||
theta = j * dtheta;
|
||||
x = cos(theta) * sin(rho);
|
||||
y = sin(theta) * sin(rho);
|
||||
z = cos(rho);
|
||||
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
|
||||
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
|
||||
}
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public void generateMipmaps(int texture, int target) {
|
||||
RENDER_BACKEND.generateTextureMipmap(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
// Note: DSA glTextureImage2D doesn't exist in ARB_direct_state_access
|
||||
// We need to use glTextureStorage2D + glTextureSubImage2D, or fall back to bind-based approach
|
||||
// For simplicity, use the fallback which binds, uploads, and restores
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
RENDER_BACKEND.textureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
RENDER_BACKEND.textureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameteri(int texture, int target, int pname, int param) {
|
||||
param = GLStateManager.remapTexClamp(pname, param);
|
||||
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
|
||||
|
||||
RENDER_BACKEND.textureParameteri(texture, target, pname, param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameterf(int texture, int target, int pname, float param) {
|
||||
param = GLStateManager.remapTexClamp(pname, param);
|
||||
if(!GLStateManager.updateTexParameterfCache(target, texture, pname, param)) return;
|
||||
|
||||
RENDER_BACKEND.textureParameterf(texture, target, pname, param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
GLStateManager.remapTexClampBuffer(pname, params);
|
||||
RENDER_BACKEND.textureParameteriv(texture, target, pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBuffer(int framebuffer, int buffer) {
|
||||
RENDER_BACKEND.namedFramebufferReadBuffer(framebuffer, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawBuffers(int framebuffer, IntBuffer buffers) {
|
||||
RENDER_BACKEND.namedFramebufferDrawBuffers(framebuffer, buffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexParameteri(int texture, int target, int pname) {
|
||||
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
return RENDER_BACKEND.getTextureLevelParameteri(texture, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
RENDER_BACKEND.copyTextureSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTextureToUnit(int unit, int texture) {
|
||||
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
|
||||
|
||||
if (texture == 0) {
|
||||
super.bindTextureToUnit(unit, texture);
|
||||
} else {
|
||||
RENDER_BACKEND.bindTextureUnit(unit, texture);
|
||||
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTextureToUnit(int target, int unit, int texture) {
|
||||
bindTextureToUnit(unit, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
final int buffer = RENDER_BACKEND.createBuffers();
|
||||
RENDER_BACKEND.namedBufferData(buffer, data, usage);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2,
|
||||
int bufferChoice, int filter) {
|
||||
RENDER_BACKEND.blitNamedFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
RENDER_BACKEND.namedFramebufferTexture(fb, attachment, texture, levels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createFramebuffer() {
|
||||
return RENDER_BACKEND.createFramebuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createTexture(int target) {
|
||||
return RENDER_BACKEND.createTextures(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
RENDER_BACKEND.textureStorage1D(texture, levels, internalFormat, width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
RENDER_BACKEND.textureStorage2D(texture, levels, internalFormat, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
RENDER_BACKEND.textureStorage3D(texture, levels, internalFormat, width, height, depth);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public interface DSAAccess {
|
||||
void generateMipmaps(int texture, int target);
|
||||
|
||||
// Texture image upload - DSA allows specifying texture directly without binding
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
|
||||
|
||||
void texParameteri(int texture, int target, int pname, int param);
|
||||
void texParameterf(int texture, int target, int pname, float param);
|
||||
void texParameteriv(int texture, int target, int pname, IntBuffer params);
|
||||
|
||||
void readBuffer(int framebuffer, int buffer);
|
||||
|
||||
void drawBuffers(int framebuffer, IntBuffer buffers);
|
||||
|
||||
int getTexParameteri(int texture, int target, int pname);
|
||||
|
||||
int getTexLevelParameteri(int texture, int level, int pname);
|
||||
|
||||
void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height);
|
||||
|
||||
void bindTextureToUnit(int unit, int texture);
|
||||
void bindTextureToUnit(int target, int unit, int texture);
|
||||
|
||||
int bufferStorage(int target, FloatBuffer data, int usage);
|
||||
|
||||
void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter);
|
||||
|
||||
void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels);
|
||||
|
||||
int createFramebuffer();
|
||||
int createTexture(int target);
|
||||
|
||||
// Texture storage methods for custom images
|
||||
void textureStorage1D(int texture, int target, int levels, int internalFormat, int width);
|
||||
void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height);
|
||||
void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth);
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
public class DSAEXT extends DSACore {
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
RENDER_BACKEND.textureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
RENDER_BACKEND.textureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class DSAUnsupported implements DSAAccess {
|
||||
|
||||
@Override
|
||||
public void generateMipmaps(int texture, int target) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
RENDER_BACKEND.generateMipmap(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding); // Restore to cache value
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
RENDER_BACKEND.bindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameteri(int texture, int target, int pname, int param) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glTexParameteri(target, pname, param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameterf(int texture, int target, int pname, float param) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glTexParameterf(target, pname, param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glTexParameter(target, pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBuffer(int framebuffer, int buffer) {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
RENDER_BACKEND.readBuffer(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawBuffers(int framebuffer, IntBuffer buffers) {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
RENDER_BACKEND.drawBuffers(buffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexParameteri(int texture, int target, int pname) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
return GLStateManager.glGetTexParameteri(target, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
final int previous = GLStateManager.getBoundTextureForServerState();
|
||||
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
final int result = RENDER_BACKEND.getTexLevelParameteri(GL11.GL_TEXTURE_2D, level, pname);
|
||||
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, previous);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
final int previous = GLStateManager.getBoundTextureForServerState();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, destTexture);
|
||||
RENDER_BACKEND.copyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTextureToUnit(int unit, int texture) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTextureToUnit(int target, int unit, int texture) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
final int buffer = RENDER_BACKEND.genBuffers();
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
RENDER_BACKEND.bufferData(target, data, usage);
|
||||
GLStateManager.glBindBuffer(target, 0);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2,
|
||||
int bufferChoice, int filter) {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, source);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, dest);
|
||||
RENDER_BACKEND.blitFramebuffer(offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
GLStateManager.glBindFramebuffer(fbtarget, fb);
|
||||
RENDER_BACKEND.framebufferTexture2D(fbtarget, attachment, target, texture, levels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createFramebuffer() {
|
||||
final int framebuffer = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createTexture(int target) {
|
||||
final int texture = RENDER_BACKEND.genTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
|
||||
final int previous = RENDER_BACKEND.getInteger(GL11.GL_TEXTURE_BINDING_1D);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texStorage1D(target, levels, internalFormat, width);
|
||||
RENDER_BACKEND.bindTexture(target, previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
|
||||
final int previous = (target == GL11.GL_TEXTURE_2D) ? GLStateManager.getBoundTextureForServerState() : RENDER_BACKEND.getInteger(GL31.GL_TEXTURE_BINDING_RECTANGLE);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texStorage2D(target, levels, internalFormat, width, height);
|
||||
RENDER_BACKEND.bindTexture(target, previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
final int previous = RENDER_BACKEND.getInteger(GL12.GL_TEXTURE_BINDING_3D);
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
RENDER_BACKEND.texStorage3D(target, levels, internalFormat, width, height, depth);
|
||||
RENDER_BACKEND.bindTexture(target, previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
/**
|
||||
* Packed-long fragment shader permutation key for FFP emulation.
|
||||
*
|
||||
* Layout:
|
||||
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
|
||||
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
|
||||
* Only max(1, nrEnabledUnits) longs are significant.
|
||||
*
|
||||
* Per-unit packing (47 bits):
|
||||
* 0: enabled
|
||||
* 1-3: mode (MODULATE=0..COMBINE=5)
|
||||
* 4: needsEnvColor
|
||||
* 5-8: combineRgb (encoded 0-7)
|
||||
* 9-12: combineAlpha (encoded 0-7)
|
||||
* 13-14: scaleShiftRgb
|
||||
* 15-16: scaleShiftAlpha
|
||||
* 17-21: arg0 RGB (source:3 + operand:2)
|
||||
* 22-26: arg1 RGB
|
||||
* 27-31: arg2 RGB
|
||||
* 32-36: arg0 Alpha
|
||||
* 37-41: arg1 Alpha
|
||||
* 42-46: arg2 Alpha
|
||||
*/
|
||||
public final class FragmentKey {
|
||||
public static final int TEX_ENV_MODULATE = 0;
|
||||
public static final int TEX_ENV_REPLACE = 1;
|
||||
public static final int TEX_ENV_ADD = 2;
|
||||
public static final int TEX_ENV_DECAL = 3;
|
||||
public static final int TEX_ENV_BLEND = 4;
|
||||
public static final int TEX_ENV_COMBINE = 5;
|
||||
|
||||
public static final int COMBINE_REPLACE = 0;
|
||||
public static final int COMBINE_MODULATE = 1;
|
||||
public static final int COMBINE_ADD = 2;
|
||||
public static final int COMBINE_ADD_SIGNED = 3;
|
||||
public static final int COMBINE_INTERPOLATE = 4;
|
||||
public static final int COMBINE_SUBTRACT = 5;
|
||||
public static final int COMBINE_DOT3_RGB = 6;
|
||||
public static final int COMBINE_DOT3_RGBA = 7;
|
||||
|
||||
public static final int SRC_TEXTURE = 0;
|
||||
public static final int SRC_CONSTANT = 1;
|
||||
public static final int SRC_PRIMARY_COLOR = 2;
|
||||
public static final int SRC_PREVIOUS = 3;
|
||||
|
||||
public static final int OP_SRC_COLOR = 0;
|
||||
public static final int OP_ONE_MINUS_SRC_COLOR = 1;
|
||||
public static final int OP_SRC_ALPHA = 2;
|
||||
public static final int OP_ONE_MINUS_SRC_ALPHA = 3;
|
||||
|
||||
public static final int FOG_NONE = 0;
|
||||
public static final int FOG_LINEAR = 1;
|
||||
public static final int FOG_EXP = 2;
|
||||
public static final int FOG_EXP2 = 3;
|
||||
|
||||
private static final int GLOBAL_BITS = 10;
|
||||
private static final int BIT_FOG_MODE = 0; // 2 bits
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
|
||||
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
|
||||
|
||||
private static final int U_ENABLED = 0;
|
||||
private static final int U_MODE = 1; // 3 bits
|
||||
private static final int U_NEEDS_ENV_COLOR = 4; // 1 bit
|
||||
private static final int U_COMBINE_RGB = 5; // 4 bits
|
||||
private static final int U_COMBINE_ALPHA = 9; // 4 bits
|
||||
private static final int U_SCALE_RGB = 13; // 2 bits
|
||||
private static final int U_SCALE_ALPHA = 15; // 2 bits
|
||||
private static final int U_ARG0_RGB = 17; // 5 bits (source:3 + operand:2)
|
||||
private static final int U_ARG1_RGB = 22; // 5 bits
|
||||
private static final int U_ARG2_RGB = 27; // 5 bits
|
||||
private static final int U_ARG0_ALPHA = 32; // 5 bits
|
||||
private static final int U_ARG1_ALPHA = 37; // 5 bits
|
||||
private static final int U_ARG2_ALPHA = 42; // 5 bits
|
||||
|
||||
static final int MAX_UNITS = 4;
|
||||
|
||||
private final long[] packed;
|
||||
private final int hash;
|
||||
|
||||
private FragmentKey(long[] packed, int hash) {
|
||||
this.packed = packed;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack current GLSM fragment state into pre-allocated output array.
|
||||
* @param out must be at least MAX_UNITS (4) longs
|
||||
* @return number of significant longs (1..4)
|
||||
*/
|
||||
public static int packFromState(long[] out) {
|
||||
long global = 0;
|
||||
|
||||
// Fog
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
|
||||
case GL11.GL_LINEAR -> FOG_LINEAR;
|
||||
case GL11.GL_EXP -> FOG_EXP;
|
||||
case GL11.GL_EXP2 -> FOG_EXP2;
|
||||
default -> FOG_NONE;
|
||||
};
|
||||
global |= (long) fogMode;
|
||||
}
|
||||
|
||||
// Alpha test
|
||||
if (GLStateManager.getAlphaTest().isEnabled()) {
|
||||
global |= (1L << BIT_ALPHA_TEST);
|
||||
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
|
||||
}
|
||||
|
||||
// Separate specular
|
||||
if (GLStateManager.getLightingState().isEnabled()
|
||||
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
global |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
}
|
||||
|
||||
// Per-unit state
|
||||
int highestEnabled = -1;
|
||||
long unit0Bits = 0;
|
||||
for (int i = 0; i < MAX_UNITS; i++) {
|
||||
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled();
|
||||
if (texEnabled) highestEnabled = i;
|
||||
|
||||
final long unitBits = packUnit(i, texEnabled);
|
||||
if (i == 0) {
|
||||
unit0Bits = unitBits;
|
||||
} else {
|
||||
out[i] = unitBits;
|
||||
}
|
||||
}
|
||||
|
||||
final int nrEnabled = highestEnabled + 1;
|
||||
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
|
||||
global |= (unit0Bits << GLOBAL_BITS);
|
||||
out[0] = global;
|
||||
|
||||
return Math.max(1, nrEnabled);
|
||||
}
|
||||
|
||||
private static long packUnit(int unitIndex, boolean texEnabled) {
|
||||
if (!texEnabled) return 0;
|
||||
|
||||
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
|
||||
final int mode = encodeTexEnvMode(envState.mode);
|
||||
|
||||
long bits = 1L; // enabled
|
||||
bits |= ((long) (mode & 0x7)) << U_MODE;
|
||||
|
||||
boolean needsEnvColor = (mode == TEX_ENV_BLEND);
|
||||
|
||||
if (mode == TEX_ENV_COMBINE) {
|
||||
final int combRgb = encodeCombineFunc(envState.combineRgb);
|
||||
final int combAlpha = encodeCombineFunc(envState.combineAlpha);
|
||||
bits |= ((long) (combRgb & 0xF)) << U_COMBINE_RGB;
|
||||
bits |= ((long) (combAlpha & 0xF)) << U_COMBINE_ALPHA;
|
||||
bits |= ((long) (encodeScale(envState.scaleRgb) & 0x3)) << U_SCALE_RGB;
|
||||
bits |= ((long) (encodeScale(envState.scaleAlpha) & 0x3)) << U_SCALE_ALPHA;
|
||||
|
||||
// Only pack args actually used by each channel's combine function.
|
||||
// Unused arg slots stay 0, preventing TexEnvState defaults from inflating the cache.
|
||||
final int numArgsRgb = combineNumArgs(combRgb);
|
||||
final int numArgsAlpha = combineNumArgs(combAlpha);
|
||||
final int maxArgs = Math.max(numArgsRgb, numArgsAlpha);
|
||||
|
||||
for (int a = 0; a < maxArgs; a++) {
|
||||
if (a < numArgsRgb) {
|
||||
final int srcR = encodeSource(envState.sourceRgb[a]);
|
||||
final int opR = encodeOperand(envState.operandRgb[a]);
|
||||
bits |= ((long) ((srcR & 0x7) | ((opR & 0x3) << 3))) << (U_ARG0_RGB + a * 5);
|
||||
if (srcR == SRC_CONSTANT) needsEnvColor = true;
|
||||
}
|
||||
if (a < numArgsAlpha) {
|
||||
final int srcA = encodeSource(envState.sourceAlpha[a]);
|
||||
final int opA = encodeOperand(envState.operandAlpha[a]);
|
||||
bits |= ((long) ((srcA & 0x7) | ((opA & 0x3) << 3))) << (U_ARG0_ALPHA + a * 5);
|
||||
if (srcA == SRC_CONSTANT) needsEnvColor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsEnvColor) {
|
||||
bits |= (1L << U_NEEDS_ENV_COLOR);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/** Create FragmentKey from packed scratch data. Only called on cache miss. */
|
||||
public static FragmentKey fromPacked(long[] scratch, int len) {
|
||||
final long[] copy = Arrays.copyOf(scratch, len);
|
||||
return new FragmentKey(copy, Arrays.hashCode(copy));
|
||||
}
|
||||
|
||||
/** Convenience: pack + create. Allocates. For infrequent paths. */
|
||||
public static FragmentKey fromState() {
|
||||
final long[] scratch = new long[MAX_UNITS];
|
||||
final int len = packFromState(scratch);
|
||||
return fromPacked(scratch, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof FragmentKey other)) return false;
|
||||
if (hash != other.hash) return false;
|
||||
return Arrays.equals(packed, other.packed);
|
||||
}
|
||||
|
||||
public int fogMode() { return (int) (packed[0] & 0x3); }
|
||||
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
|
||||
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
|
||||
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
|
||||
|
||||
private long unitBits(int i) {
|
||||
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
|
||||
}
|
||||
|
||||
public boolean unitEnabled(int i) { return (unitBits(i) & 1) != 0; }
|
||||
public int unitMode(int i) { return (int) ((unitBits(i) >> U_MODE) & 0x7); }
|
||||
public boolean unitNeedsEnvColor(int i) { return ((unitBits(i) >> U_NEEDS_ENV_COLOR) & 1) != 0; }
|
||||
public int unitCombineRgb(int i) { return (int) ((unitBits(i) >> U_COMBINE_RGB) & 0xF); }
|
||||
public int unitCombineAlpha(int i) { return (int) ((unitBits(i) >> U_COMBINE_ALPHA) & 0xF); }
|
||||
public int unitScaleShiftRgb(int i) { return (int) ((unitBits(i) >> U_SCALE_RGB) & 0x3); }
|
||||
public int unitScaleShiftAlpha(int i) { return (int) ((unitBits(i) >> U_SCALE_ALPHA) & 0x3); }
|
||||
|
||||
public int unitSourceRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5)) & 0x7); }
|
||||
public int unitOperandRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5 + 3)) & 0x3); }
|
||||
public int unitSourceAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5)) & 0x7); }
|
||||
public int unitOperandAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5 + 3)) & 0x3); }
|
||||
|
||||
public boolean textureEnabled() {
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (unitEnabled(i)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean lightmapEnabled() {
|
||||
return nrEnabledUnits() > 1 && unitEnabled(1);
|
||||
}
|
||||
|
||||
private static int encodeAlphaFunc(int glFunc) {
|
||||
return glFunc & 0x7;
|
||||
}
|
||||
|
||||
static int encodeTexEnvMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_MODULATE -> TEX_ENV_MODULATE;
|
||||
case GL11.GL_REPLACE -> TEX_ENV_REPLACE;
|
||||
case GL11.GL_ADD -> TEX_ENV_ADD;
|
||||
case GL11.GL_DECAL -> TEX_ENV_DECAL;
|
||||
case GL11.GL_BLEND -> TEX_ENV_BLEND;
|
||||
case GL13.GL_COMBINE -> TEX_ENV_COMBINE;
|
||||
default -> TEX_ENV_MODULATE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeCombineFunc(int glFunc) {
|
||||
return switch (glFunc) {
|
||||
case GL11.GL_REPLACE -> COMBINE_REPLACE;
|
||||
case GL11.GL_MODULATE -> COMBINE_MODULATE;
|
||||
case GL11.GL_ADD -> COMBINE_ADD;
|
||||
case GL13.GL_ADD_SIGNED -> COMBINE_ADD_SIGNED;
|
||||
case GL13.GL_INTERPOLATE -> COMBINE_INTERPOLATE;
|
||||
case GL13.GL_SUBTRACT -> COMBINE_SUBTRACT;
|
||||
case GL13.GL_DOT3_RGB -> COMBINE_DOT3_RGB;
|
||||
case GL13.GL_DOT3_RGBA -> COMBINE_DOT3_RGBA;
|
||||
default -> COMBINE_REPLACE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeSource(int glSource) {
|
||||
return switch (glSource) {
|
||||
case GL11.GL_TEXTURE -> SRC_TEXTURE;
|
||||
case GL13.GL_CONSTANT -> SRC_CONSTANT;
|
||||
case GL13.GL_PRIMARY_COLOR -> SRC_PRIMARY_COLOR;
|
||||
case GL13.GL_PREVIOUS -> SRC_PREVIOUS;
|
||||
default -> {
|
||||
GLStateManager.LOGGER.debug("Unknown TexEnv source 0x{}, falling back to SRC_PREVIOUS", Integer.toHexString(glSource));
|
||||
yield SRC_PREVIOUS;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeOperand(int glOperand) {
|
||||
return switch (glOperand) {
|
||||
case GL11.GL_SRC_COLOR -> OP_SRC_COLOR;
|
||||
case GL11.GL_ONE_MINUS_SRC_COLOR -> OP_ONE_MINUS_SRC_COLOR;
|
||||
case GL11.GL_SRC_ALPHA -> OP_SRC_ALPHA;
|
||||
case GL11.GL_ONE_MINUS_SRC_ALPHA -> OP_ONE_MINUS_SRC_ALPHA;
|
||||
default -> OP_SRC_COLOR;
|
||||
};
|
||||
}
|
||||
|
||||
/** Number of source/operand args used by the given (encoded) combine function. */
|
||||
public static int combineNumArgs(int func) {
|
||||
return switch (func) {
|
||||
case COMBINE_REPLACE -> 1;
|
||||
case COMBINE_INTERPOLATE -> 3;
|
||||
default -> 2; // MODULATE, ADD, ADD_SIGNED, SUBTRACT, DOT3_*
|
||||
};
|
||||
}
|
||||
|
||||
private static int encodeScale(float scale) {
|
||||
if (scale >= 4.0f) return 2;
|
||||
if (scale >= 2.0f) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int decodeAlphaFunc(int encoded) {
|
||||
return 0x0200 | encoded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String fogName = switch (fogMode()) {
|
||||
case FOG_NONE -> "NONE";
|
||||
case FOG_LINEAR -> "LINEAR";
|
||||
case FOG_EXP -> "EXP";
|
||||
case FOG_EXP2 -> "EXP2";
|
||||
default -> "?";
|
||||
};
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
|
||||
fogName, alphaTestEnabled(),
|
||||
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
|
||||
separateSpecular(), nrEnabledUnits()));
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (!unitEnabled(i)) {
|
||||
sb.append(String.format(" u%d=OFF", i));
|
||||
} else {
|
||||
final String modeName = switch (unitMode(i)) {
|
||||
case TEX_ENV_MODULATE -> "MOD";
|
||||
case TEX_ENV_REPLACE -> "REPL";
|
||||
case TEX_ENV_ADD -> "ADD";
|
||||
case TEX_ENV_DECAL -> "DEC";
|
||||
case TEX_ENV_BLEND -> "BL";
|
||||
case TEX_ENV_COMBINE -> "COMB";
|
||||
default -> "?";
|
||||
};
|
||||
sb.append(String.format(" u%d=%s", i, modeName));
|
||||
}
|
||||
}
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core fragment shaders for FFP emulation.
|
||||
* Supports per-unit texenv chain including GL_COMBINE mode.
|
||||
* Follows Mesa's ff_fragment_shader.c and st_nir_lower_fog.c.
|
||||
*/
|
||||
public final class FragmentShaderGenerator {
|
||||
|
||||
private FragmentShaderGenerator() {}
|
||||
|
||||
public static String generate(FragmentKey key) {
|
||||
final StringBuilder sb = new StringBuilder(2048);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
emitInputs(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
sb.append("out vec4 fragColor;\n\n");
|
||||
|
||||
sb.append("void main() {\n");
|
||||
emitTextureSampling(sb, key);
|
||||
emitTexEnvChain(sb, key);
|
||||
emitSpecularAdd(sb, key);
|
||||
emitAlphaTest(sb, key);
|
||||
emitFog(sb, key);
|
||||
sb.append(" fragColor = color;\n");
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void emitInputs(StringBuilder sb, FragmentKey key) {
|
||||
sb.append("// Inputs from vertex shader\n");
|
||||
sb.append("in vec4 v_Color;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("in vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("in float v_FogCoord;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
|
||||
// Samplers for each enabled unit
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (key.unitEnabled(i)) {
|
||||
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
|
||||
}
|
||||
}
|
||||
if (key.alphaTestEnabled()) {
|
||||
sb.append("uniform float u_AlphaRef;\n");
|
||||
}
|
||||
// Per-unit texEnvColor uniforms
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (key.unitEnabled(i) && key.unitNeedsEnvColor(i)) {
|
||||
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
|
||||
}
|
||||
}
|
||||
if (key.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("uniform vec4 u_FogParams;\n");
|
||||
sb.append("uniform vec4 u_FogColor;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
// Unit 1 is the lightmap — uses v_TexCoord1.
|
||||
// Units 2-3 intentionally share unit 0's texture coordinates since
|
||||
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
|
||||
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
|
||||
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitTexEnvChain(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.textureEnabled()) {
|
||||
sb.append(" // No texture -- vertex color only\n");
|
||||
sb.append(" vec4 color = v_Color;\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process units in order; "previous" starts as vertex color
|
||||
boolean firstUnit = true;
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
|
||||
final String texVar = "tex" + i + "Color";
|
||||
final String envColorVar = "u_TexEnvColor" + i;
|
||||
final String prevVar = firstUnit ? "v_Color" : "color";
|
||||
final String assign = firstUnit ? " vec4 color = " : " color = ";
|
||||
|
||||
if (key.unitMode(i) == FragmentKey.TEX_ENV_COMBINE) {
|
||||
emitCombineUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
|
||||
} else {
|
||||
emitSimpleUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
|
||||
}
|
||||
firstUnit = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
|
||||
switch (key.unitMode(unit)) {
|
||||
case FragmentKey.TEX_ENV_REPLACE -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (REPLACE)\n");
|
||||
sb.append(assign).append(texVar).append(";\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_ADD -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (ADD)\n");
|
||||
sb.append(assign).append("vec4(clamp(").append(prevVar).append(".rgb + ").append(texVar).append(".rgb, 0.0, 1.0), ")
|
||||
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_DECAL -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (DECAL)\n");
|
||||
sb.append(assign).append("vec4(mix(").append(prevVar).append(".rgb, ").append(texVar).append(".rgb, ").append(texVar).append(".a), ")
|
||||
.append(prevVar).append(".a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_BLEND -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (BLEND)\n");
|
||||
sb.append(assign).append("vec4(").append(prevVar).append(".rgb * (vec3(1.0) - ").append(texVar).append(".rgb) + ")
|
||||
.append(envColorVar).append(".rgb * ").append(texVar).append(".rgb, ")
|
||||
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
|
||||
}
|
||||
default -> { // MODULATE
|
||||
sb.append(" // Unit ").append(unit).append(" (MODULATE)\n");
|
||||
sb.append(assign).append(prevVar).append(" * ").append(texVar).append(";\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Emit a GL_COMBINE texenv unit */
|
||||
private static void emitCombineUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
|
||||
sb.append(" // Unit ").append(unit).append(" (COMBINE)\n");
|
||||
|
||||
final String rgbResult = "combRgb" + unit;
|
||||
final String alphaResult = "combAlpha" + unit;
|
||||
|
||||
if (key.unitCombineRgb(unit) == FragmentKey.COMBINE_DOT3_RGBA) {
|
||||
sb.append(" vec3 ").append(rgbResult).append(";\n");
|
||||
sb.append(" {\n");
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
|
||||
sb.append(" }\n");
|
||||
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(rgbResult).append(".x);\n");
|
||||
} else {
|
||||
sb.append(" vec3 ").append(rgbResult).append(";\n");
|
||||
sb.append(" float ").append(alphaResult).append(";\n");
|
||||
sb.append(" {\n");
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, false);
|
||||
sb.append(" }\n");
|
||||
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(alphaResult).append(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit one channel (RGB or Alpha) of a GL_COMBINE unit.
|
||||
*/
|
||||
private static void emitCombineChannel(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, boolean isRgb) {
|
||||
final String suffix = isRgb ? "Rgb" : "Alpha";
|
||||
final String type = isRgb ? "vec3" : "float";
|
||||
final int combineFunc = isRgb ? key.unitCombineRgb(unit) : key.unitCombineAlpha(unit);
|
||||
final int scaleShift = isRgb ? key.unitScaleShiftRgb(unit) : key.unitScaleShiftAlpha(unit);
|
||||
|
||||
// Guard: DOT3 is invalid for alpha channel — fallback to REPLACE
|
||||
final int effectiveFunc;
|
||||
if (!isRgb && (combineFunc == FragmentKey.COMBINE_DOT3_RGB || combineFunc == FragmentKey.COMBINE_DOT3_RGBA)) {
|
||||
effectiveFunc = FragmentKey.COMBINE_REPLACE;
|
||||
} else {
|
||||
effectiveFunc = combineFunc;
|
||||
}
|
||||
|
||||
// Determine how many args we need
|
||||
final int numArgs = combineNumArgs(effectiveFunc);
|
||||
|
||||
// Emit argument resolution
|
||||
for (int a = 0; a < numArgs; a++) {
|
||||
final int source = isRgb ? key.unitSourceRgb(unit, a) : key.unitSourceAlpha(unit, a);
|
||||
final int operand = isRgb ? key.unitOperandRgb(unit, a) : key.unitOperandAlpha(unit, a);
|
||||
final String argName = "arg" + suffix + a + "_" + unit;
|
||||
final String sourceExpr = resolveSource(source, texVar, envColorVar, prevVar);
|
||||
final String operandExpr = applyOperand(sourceExpr, operand, isRgb);
|
||||
sb.append(" ").append(type).append(" ").append(argName).append(" = ").append(operandExpr).append(";\n");
|
||||
}
|
||||
|
||||
// Emit combine operation
|
||||
final String resultName = "comb" + suffix + unit;
|
||||
final String a0 = "arg" + suffix + "0_" + unit;
|
||||
final String a1 = "arg" + suffix + "1_" + unit;
|
||||
final String a2 = "arg" + suffix + "2_" + unit;
|
||||
|
||||
sb.append(" ").append(resultName).append(" = ");
|
||||
switch (effectiveFunc) {
|
||||
case FragmentKey.COMBINE_REPLACE -> sb.append(a0);
|
||||
case FragmentKey.COMBINE_MODULATE -> sb.append(a0).append(" * ").append(a1);
|
||||
case FragmentKey.COMBINE_ADD -> sb.append(a0).append(" + ").append(a1);
|
||||
case FragmentKey.COMBINE_ADD_SIGNED ->
|
||||
sb.append(a0).append(" + ").append(a1).append(" - ").append(isRgb ? "vec3(0.5)" : "0.5");
|
||||
case FragmentKey.COMBINE_SUBTRACT -> sb.append(a0).append(" - ").append(a1);
|
||||
case FragmentKey.COMBINE_INTERPOLATE ->
|
||||
sb.append("mix(").append(a1).append(", ").append(a0).append(", ").append(a2).append(")");
|
||||
case FragmentKey.COMBINE_DOT3_RGB, FragmentKey.COMBINE_DOT3_RGBA ->
|
||||
sb.append("vec3(4.0 * dot(").append(a0).append(" - vec3(0.5), ").append(a1).append(" - vec3(0.5)))");
|
||||
default -> sb.append(a0); // fallback to REPLACE
|
||||
}
|
||||
sb.append(";\n");
|
||||
|
||||
// Apply scale
|
||||
if (scaleShift > 0) {
|
||||
final String scaleFactor = scaleShift == 1 ? "2.0" : "4.0";
|
||||
sb.append(" ").append(resultName).append(" *= ").append(scaleFactor).append(";\n");
|
||||
}
|
||||
|
||||
// Clamp
|
||||
sb.append(" ").append(resultName).append(" = clamp(").append(resultName).append(", ")
|
||||
.append(isRgb ? "vec3(0.0), vec3(1.0)" : "0.0, 1.0").append(");\n");
|
||||
}
|
||||
|
||||
private static String resolveSource(int source, String texVar, String envColorVar, String prevVar) {
|
||||
return switch (source) {
|
||||
case FragmentKey.SRC_TEXTURE -> texVar;
|
||||
case FragmentKey.SRC_CONSTANT -> envColorVar;
|
||||
case FragmentKey.SRC_PRIMARY_COLOR -> "v_Color";
|
||||
case FragmentKey.SRC_PREVIOUS -> prevVar;
|
||||
default -> prevVar;
|
||||
};
|
||||
}
|
||||
|
||||
private static String applyOperand(String sourceExpr, int operand, boolean isRgb) {
|
||||
if (isRgb) {
|
||||
return switch (operand) {
|
||||
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".rgb";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(vec3(1.0) - " + sourceExpr + ".rgb)";
|
||||
case FragmentKey.OP_SRC_ALPHA -> "vec3(" + sourceExpr + ".a)";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "vec3(1.0 - " + sourceExpr + ".a)";
|
||||
default -> sourceExpr + ".rgb";
|
||||
};
|
||||
} else {
|
||||
return switch (operand) {
|
||||
case FragmentKey.OP_SRC_ALPHA -> sourceExpr + ".a";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "(1.0 - " + sourceExpr + ".a)";
|
||||
// Per GL spec, alpha channel operands are SRC_ALPHA or ONE_MINUS_SRC_ALPHA only, but handle color operands gracefully
|
||||
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".a";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(1.0 - " + sourceExpr + ".a)";
|
||||
default -> sourceExpr + ".a";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static int combineNumArgs(int func) {
|
||||
return FragmentKey.combineNumArgs(func);
|
||||
}
|
||||
|
||||
private static void emitSpecularAdd(StringBuilder sb, FragmentKey key) {
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" // Add separate specular\n");
|
||||
sb.append(" color.rgb += v_SpecularColor;\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitAlphaTest(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.alphaTestEnabled()) return;
|
||||
|
||||
sb.append(" // Alpha test\n");
|
||||
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
|
||||
|
||||
switch (func) {
|
||||
case GL11.GL_NEVER ->
|
||||
sb.append(" discard;\n");
|
||||
case GL11.GL_LESS ->
|
||||
sb.append(" if (color.a >= u_AlphaRef) discard;\n");
|
||||
case GL11.GL_EQUAL ->
|
||||
sb.append(" if (color.a != u_AlphaRef) discard;\n");
|
||||
case GL11.GL_LEQUAL ->
|
||||
sb.append(" if (color.a > u_AlphaRef) discard;\n");
|
||||
case GL11.GL_GREATER ->
|
||||
sb.append(" if (color.a <= u_AlphaRef) discard;\n");
|
||||
case GL11.GL_NOTEQUAL ->
|
||||
sb.append(" if (color.a == u_AlphaRef) discard;\n");
|
||||
case GL11.GL_GEQUAL ->
|
||||
sb.append(" if (color.a < u_AlphaRef) discard;\n");
|
||||
case GL11.GL_ALWAYS -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitFog(StringBuilder sb, FragmentKey key) {
|
||||
final int fogMode = key.fogMode();
|
||||
if (fogMode == FragmentKey.FOG_NONE) return;
|
||||
|
||||
sb.append(" // Fog\n");
|
||||
sb.append(" float f;\n");
|
||||
|
||||
switch (fogMode) {
|
||||
case FragmentKey.FOG_LINEAR ->
|
||||
sb.append(" f = v_FogCoord * u_FogParams.x + u_FogParams.y;\n");
|
||||
case FragmentKey.FOG_EXP ->
|
||||
sb.append(" f = exp2(-(v_FogCoord * u_FogParams.z));\n");
|
||||
case FragmentKey.FOG_EXP2 -> {
|
||||
sb.append(" float fogTmp = v_FogCoord * u_FogParams.w;\n");
|
||||
sb.append(" f = exp2(-(fogTmp * fogTmp));\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" f = clamp(f, 0.0, 1.0);\n");
|
||||
sb.append(" color.rgb = mix(u_FogColor.rgb, color.rgb, f);\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core geometry shaders for wide line emulation.
|
||||
*/
|
||||
public final class GeometryShaderGenerator {
|
||||
|
||||
private GeometryShaderGenerator() {}
|
||||
|
||||
public static String generate(VertexKey key) {
|
||||
final StringBuilder sb = new StringBuilder(1024);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
sb.append("layout(lines) in;\n");
|
||||
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
|
||||
|
||||
sb.append("uniform vec2 u_ViewportSize;\n");
|
||||
sb.append("uniform float u_LineWidth;\n\n");
|
||||
|
||||
sb.append("// Pass-through varyings\n");
|
||||
sb.append("in vec4 v_Color_gs[];\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("in vec3 v_SpecularColor_gs[];\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord0_gs[];\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord1_gs[];\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("in float v_FogCoord_gs[];\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
|
||||
sb.append("out vec4 v_Color;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("out float v_FogCoord;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
|
||||
sb.append("void main() {\n");
|
||||
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
|
||||
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
|
||||
|
||||
sb.append(" // NDC positions\n");
|
||||
sb.append(" vec2 n0 = c0.xy / c0.w;\n");
|
||||
sb.append(" vec2 n1 = c1.xy / c1.w;\n\n");
|
||||
|
||||
sb.append(" // Screen-space direction and perpendicular\n");
|
||||
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
|
||||
sb.append(" vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;\n\n");
|
||||
|
||||
emitVertex(sb, key, 0, "+offset");
|
||||
emitVertex(sb, key, 0, "-offset");
|
||||
emitVertex(sb, key, 1, "+offset");
|
||||
emitVertex(sb, key, 1, "-offset");
|
||||
|
||||
sb.append(" EndPrimitive();\n");
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
|
||||
final String ci = "c" + endpointIdx;
|
||||
final String ni = "n" + endpointIdx;
|
||||
final String idx = "[" + endpointIdx + "]";
|
||||
|
||||
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
|
||||
|
||||
sb.append(" v_Color = v_Color_gs").append(idx).append(";\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
|
||||
}
|
||||
|
||||
if (key.clipPlanesEnabled()) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = gl_in").append(idx).append(".gl_ClipDistance[").append(i).append("];\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(ci).append(".w, ").append(ci).append(".z, ").append(ci).append(".w);\n");
|
||||
sb.append(" EmitVertex();\n\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.KHRDebug;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* A compiled and linked FFP emulation program (vertex + fragment + optional geometry shaders). Owns the GL program handle and caches uniform locations.
|
||||
*/
|
||||
public class Program {
|
||||
|
||||
private static final AtomicInteger PROGRAM_COUNTER = new AtomicInteger();
|
||||
|
||||
@Getter private final int programId;
|
||||
@Getter private final VertexKey vertexKey;
|
||||
@Getter private final FragmentKey fragmentKey;
|
||||
|
||||
// Uniform locations (-1 = not present in this variant)
|
||||
// Matrices
|
||||
public int locModelViewMatrix = -1;
|
||||
public int locProjectionMatrix = -1;
|
||||
public int locMVPMatrix = -1;
|
||||
public int locNormalMatrix = -1;
|
||||
public int locTextureMatrix0 = -1;
|
||||
|
||||
// Current state defaults
|
||||
public int locCurrentNormal = -1;
|
||||
public int locCurrentColor = -1;
|
||||
public int locCurrentTexCoord = -1;
|
||||
public int locCurrentLightmapCoord = -1;
|
||||
public int locLightmapTextureMatrix = -1;
|
||||
|
||||
// Lighting - color material path (raw values)
|
||||
public int locLightModelAmbient = -1;
|
||||
public int locMaterialEmission = -1;
|
||||
public int locMaterialAmbient = -1;
|
||||
public int locMaterialDiffuse = -1;
|
||||
public int locMaterialSpecular = -1;
|
||||
public int locMaterialShininess = -1;
|
||||
public int locLight0Ambient = -1;
|
||||
public int locLight0Diffuse = -1;
|
||||
public int locLight0Specular = -1;
|
||||
public int locLight1Ambient = -1;
|
||||
public int locLight1Diffuse = -1;
|
||||
public int locLight1Specular = -1;
|
||||
|
||||
// Lighting - pre-computed path
|
||||
public int locSceneColor = -1;
|
||||
public int locLightProd0Ambient = -1;
|
||||
public int locLightProd0Diffuse = -1;
|
||||
public int locLightProd0Specular = -1;
|
||||
public int locLightProd1Ambient = -1;
|
||||
public int locLightProd1Diffuse = -1;
|
||||
public int locLightProd1Specular = -1;
|
||||
|
||||
// Light positions
|
||||
public int locLight0Position = -1;
|
||||
public int locLight1Position = -1;
|
||||
|
||||
// Normal scale (for rescale normals)
|
||||
public int locNormalScale = -1;
|
||||
|
||||
// TexGen plane uniforms
|
||||
public int locTexGenObjPlaneS = -1;
|
||||
public int locTexGenObjPlaneT = -1;
|
||||
public int locTexGenObjPlaneR = -1;
|
||||
public int locTexGenObjPlaneQ = -1;
|
||||
public int locTexGenEyePlaneS = -1;
|
||||
public int locTexGenEyePlaneT = -1;
|
||||
public int locTexGenEyePlaneR = -1;
|
||||
public int locTexGenEyePlaneQ = -1;
|
||||
|
||||
// Clip planes
|
||||
public int locClipPlanes = -1;
|
||||
|
||||
// Wide line emulation (geometry shader)
|
||||
public int locViewportSize = -1;
|
||||
public int locLineWidth = -1;
|
||||
|
||||
// Fragment uniforms
|
||||
public final int[] locSampler = { -1, -1, -1, -1 };
|
||||
public int locAlphaRef = -1;
|
||||
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
|
||||
public int locFogParams = -1;
|
||||
public int locFogColor = -1;
|
||||
|
||||
Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
|
||||
this.programId = programId;
|
||||
this.vertexKey = vertexKey;
|
||||
this.fragmentKey = fragmentKey;
|
||||
resolveLocations();
|
||||
}
|
||||
|
||||
private void resolveLocations() {
|
||||
locModelViewMatrix = loc("u_ModelViewMatrix");
|
||||
locProjectionMatrix = loc("u_ProjectionMatrix");
|
||||
locMVPMatrix = loc("u_MVPMatrix");
|
||||
locNormalMatrix = loc("u_NormalMatrix");
|
||||
locTextureMatrix0 = loc("u_TextureMatrix0");
|
||||
|
||||
locCurrentNormal = loc("u_CurrentNormal");
|
||||
locCurrentColor = loc("u_CurrentColor");
|
||||
locCurrentTexCoord = loc("u_CurrentTexCoord");
|
||||
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
|
||||
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
|
||||
|
||||
// Color material path
|
||||
locLightModelAmbient = loc("u_LightModelAmbient");
|
||||
locMaterialEmission = loc("u_MaterialEmission");
|
||||
locMaterialAmbient = loc("u_MaterialAmbient");
|
||||
locMaterialDiffuse = loc("u_MaterialDiffuse");
|
||||
locMaterialSpecular = loc("u_MaterialSpecular");
|
||||
locMaterialShininess = loc("u_MaterialShininess");
|
||||
locLight0Ambient = loc("u_Light0Ambient");
|
||||
locLight0Diffuse = loc("u_Light0Diffuse");
|
||||
locLight0Specular = loc("u_Light0Specular");
|
||||
locLight1Ambient = loc("u_Light1Ambient");
|
||||
locLight1Diffuse = loc("u_Light1Diffuse");
|
||||
locLight1Specular = loc("u_Light1Specular");
|
||||
|
||||
// Pre-computed path
|
||||
locSceneColor = loc("u_SceneColor");
|
||||
locLightProd0Ambient = loc("u_LightProd0Ambient");
|
||||
locLightProd0Diffuse = loc("u_LightProd0Diffuse");
|
||||
locLightProd0Specular = loc("u_LightProd0Specular");
|
||||
locLightProd1Ambient = loc("u_LightProd1Ambient");
|
||||
locLightProd1Diffuse = loc("u_LightProd1Diffuse");
|
||||
locLightProd1Specular = loc("u_LightProd1Specular");
|
||||
|
||||
// Light positions
|
||||
locLight0Position = loc("u_Light0Position");
|
||||
locLight1Position = loc("u_Light1Position");
|
||||
|
||||
locNormalScale = loc("u_NormalScale");
|
||||
|
||||
// TexGen
|
||||
locTexGenObjPlaneS = loc("u_TexGenObjPlaneS");
|
||||
locTexGenObjPlaneT = loc("u_TexGenObjPlaneT");
|
||||
locTexGenObjPlaneR = loc("u_TexGenObjPlaneR");
|
||||
locTexGenObjPlaneQ = loc("u_TexGenObjPlaneQ");
|
||||
locTexGenEyePlaneS = loc("u_TexGenEyePlaneS");
|
||||
locTexGenEyePlaneT = loc("u_TexGenEyePlaneT");
|
||||
locTexGenEyePlaneR = loc("u_TexGenEyePlaneR");
|
||||
locTexGenEyePlaneQ = loc("u_TexGenEyePlaneQ");
|
||||
|
||||
// Clip planes
|
||||
locClipPlanes = loc("u_ClipPlane[0]");
|
||||
|
||||
// Wide line emulation
|
||||
locViewportSize = loc("u_ViewportSize");
|
||||
locLineWidth = loc("u_LineWidth");
|
||||
|
||||
// Fragment
|
||||
for (int i = 0; i < 4; i++) {
|
||||
locSampler[i] = loc("u_Sampler" + i);
|
||||
}
|
||||
locAlphaRef = loc("u_AlphaRef");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
|
||||
}
|
||||
locFogParams = loc("u_FogParams");
|
||||
locFogColor = loc("u_FogColor");
|
||||
}
|
||||
|
||||
private int loc(String name) {
|
||||
return RENDER_BACKEND.getUniformLocation(programId, name);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
RENDER_BACKEND.deleteProgram(programId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile vertex + fragment + optional geometry shaders, link into a program, and return the FFPProgram.
|
||||
*/
|
||||
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc, String geomSrc) {
|
||||
final RenderBackend backend = RENDER_BACKEND;
|
||||
final int id = PROGRAM_COUNTER.getAndIncrement();
|
||||
final String vkHex = Long.toHexString(vk.pack());
|
||||
final boolean hasGeom = geomSrc != null;
|
||||
|
||||
int shaderCount = 0;
|
||||
final int[] shaders = new int[5]; // vertex, fragment, geometry, tess control, tess evaluation
|
||||
int program = 0;
|
||||
try {
|
||||
shaders[shaderCount++] = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + vkHex);
|
||||
shaders[shaderCount++] = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
|
||||
if (geomSrc != null) {
|
||||
shaders[shaderCount++] = compileShader(GL32.GL_GEOMETRY_SHADER, geomSrc, "ffp_g_" + id);
|
||||
}
|
||||
|
||||
program = backend.createProgram();
|
||||
for (int i = 0; i < shaderCount; i++) backend.attachShader(program, shaders[i]);
|
||||
backend.linkProgram(program);
|
||||
|
||||
final String log = backend.getProgramInfoLog(program, backend.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
|
||||
if (!log.isEmpty()) {
|
||||
GLStateManager.LOGGER.warn("FFP program link log (vk=0x{}, fk={}): {}", vkHex, fk, log);
|
||||
}
|
||||
|
||||
if (backend.getProgrami(program, GL20.GL_LINK_STATUS) != GL11.GL_TRUE) {
|
||||
throw new RuntimeException("FFP shader link failed (vk=0x" + vkHex + ", fk=" + fk + "): " + log);
|
||||
}
|
||||
|
||||
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
|
||||
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
|
||||
|
||||
// Detach and delete individual shaders — they're linked into the program
|
||||
for (int i = 0; i < shaderCount; i++) {
|
||||
backend.detachShader(program, shaders[i]);
|
||||
backend.deleteShader(shaders[i]);
|
||||
}
|
||||
shaderCount = 0;
|
||||
|
||||
final Program ffpProgram = new Program(program, vk, fk);
|
||||
|
||||
final int previousProgram = GLStateManager.getActiveProgram();
|
||||
backend.useProgram(program);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (ffpProgram.locSampler[i] != -1) backend.uniform1i(ffpProgram.locSampler[i], i);
|
||||
}
|
||||
backend.useProgram(previousProgram);
|
||||
|
||||
return ffpProgram;
|
||||
} catch (RuntimeException e) {
|
||||
if (program != 0) backend.deleteProgram(program);
|
||||
for (int i = 0; i < shaderCount; i++) backend.deleteShader(shaders[i]);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static int compileShader(int type, String src, String name) {
|
||||
final RenderBackend backend = RENDER_BACKEND;
|
||||
final int shader = backend.createShader(type);
|
||||
backend.shaderSource(shader, src);
|
||||
backend.compileShader(shader);
|
||||
|
||||
final String typeName = switch (type) {
|
||||
case GL20.GL_VERTEX_SHADER -> "vertex";
|
||||
case GL20.GL_FRAGMENT_SHADER -> "fragment";
|
||||
case GL32.GL_GEOMETRY_SHADER -> "geometry";
|
||||
case GL40.GL_TESS_CONTROL_SHADER -> "tess_control";
|
||||
case GL40.GL_TESS_EVALUATION_SHADER -> "tess_evaluation";
|
||||
case GL43.GL_COMPUTE_SHADER -> "compute";
|
||||
default -> "unknown(0x" + Integer.toHexString(type) + ")";
|
||||
};
|
||||
GLDebug.nameObject(KHRDebug.GL_SHADER, shader, name + "(" + typeName + ")");
|
||||
|
||||
final String log = backend.getShaderInfoLog(shader, backend.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
|
||||
if (!log.isEmpty()) {
|
||||
GLStateManager.LOGGER.warn("FFP {} shader compilation log for {}: {}", typeName, name, log);
|
||||
}
|
||||
|
||||
final int result = backend.getShaderi(shader, GL20.GL_COMPILE_STATUS);
|
||||
if (result != GL11.GL_TRUE) {
|
||||
GLStateManager.LOGGER.error("FFP {} shader source:\n{}", typeName, src);
|
||||
backend.deleteShader(shader);
|
||||
throw new RuntimeException("FFP " + typeName + " shader compilation failed for " + name + ": " + log);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import lombok.Setter;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
|
||||
|
||||
/**
|
||||
* Open-addressed cache for FFP shader program variants.
|
||||
* Keyed on (vertexKey, fragmentKey) pairs stored inline as raw longs — zero allocation on cache hits.
|
||||
* Converges on Mesa's prog_cache.c: hash+memcmp over packed bytes, open addressing.
|
||||
*
|
||||
* <p>Layout per slot in {@code keys[]}: {@link #SLOT_STRIDE}=6 longs
|
||||
* <pre>
|
||||
* [0]: vertexKeyPacked
|
||||
* [1]: (fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFF) — never 0 for valid entries (fkLen >= 1)
|
||||
* [2..5]: fk0..fk3
|
||||
* </pre>
|
||||
*/
|
||||
public class ShaderCache {
|
||||
private static final int SLOT_STRIDE = 6;
|
||||
private static final int INITIAL_CAPACITY = 64; // power of 2
|
||||
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
|
||||
|
||||
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
// Open-addressed table — parallel arrays
|
||||
private long[] keys;
|
||||
private Program[] programs;
|
||||
private String[] fragSources; // fragment source per slot; reused across VK variants via findFragSource
|
||||
private int capacity;
|
||||
private int count;
|
||||
private int uniqueFragCount;
|
||||
|
||||
// Fast-path: last-used key (raw longs, no objects)
|
||||
private long lastVK = Long.MIN_VALUE;
|
||||
private int lastFKLen;
|
||||
private long lastFK0, lastFK1, lastFK2, lastFK3;
|
||||
private Program lastProgram;
|
||||
|
||||
private int fragmentDumpCounter;
|
||||
|
||||
@Setter private Path dumpDir = null;
|
||||
|
||||
public ShaderCache() {
|
||||
capacity = INITIAL_CAPACITY;
|
||||
keys = new long[capacity * SLOT_STRIDE];
|
||||
programs = new Program[capacity];
|
||||
fragSources = new String[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create an FFP program for the given state keys.
|
||||
*
|
||||
* @param vkPacked packed vertex key
|
||||
* @param fkPacked pre-allocated scratch array with packed fragment key longs
|
||||
* @param fkLen number of significant longs in fkPacked (1..4)
|
||||
*/
|
||||
public Program getOrCreate(long vkPacked, long[] fkPacked, int fkLen) {
|
||||
// Fast-path: compare raw longs inline
|
||||
if (vkPacked == lastVK && fkLen == lastFKLen && lastProgram != null
|
||||
&& fkPacked[0] == lastFK0
|
||||
&& (fkLen < 2 || fkPacked[1] == lastFK1)
|
||||
&& (fkLen < 3 || fkPacked[2] == lastFK2)
|
||||
&& (fkLen < 4 || fkPacked[3] == lastFK3)) {
|
||||
return lastProgram;
|
||||
}
|
||||
|
||||
final long hash = hashKey(vkPacked, fkPacked, fkLen);
|
||||
final long storedWord = ((long) fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFFL);
|
||||
final int mask = capacity - 1;
|
||||
int idx = (int) (hash & mask);
|
||||
while (true) {
|
||||
final int base = idx * SLOT_STRIDE;
|
||||
final long slot1 = keys[base + 1];
|
||||
if (slot1 == EMPTY_MARKER) {
|
||||
break;
|
||||
}
|
||||
if (slot1 == storedWord && keys[base] == vkPacked && keysMatch(base + 2, fkPacked, fkLen)) {
|
||||
final Program p = programs[idx];
|
||||
setLastUsed(vkPacked, fkPacked, fkLen, p);
|
||||
return p;
|
||||
}
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
|
||||
if (count >= (capacity * 3) / 4) {
|
||||
grow();
|
||||
return getOrCreate(vkPacked, fkPacked, fkLen);
|
||||
}
|
||||
|
||||
final VertexKey vk = VertexKey.fromPacked(vkPacked);
|
||||
final String vertSrc = getOrGenerateVertex(vk);
|
||||
|
||||
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
|
||||
String fragSrc = findFragSource(fkPacked, fkLen);
|
||||
if (fragSrc == null) {
|
||||
fragSrc = FragmentShaderGenerator.generate(fk);
|
||||
uniqueFragCount++;
|
||||
}
|
||||
|
||||
final String geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
|
||||
final Program program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
|
||||
|
||||
final int insertIdx = findEmptySlot(hash);
|
||||
final int base = insertIdx * SLOT_STRIDE;
|
||||
keys[base] = vkPacked;
|
||||
keys[base + 1] = storedWord;
|
||||
for (int i = 0; i < fkLen; i++) keys[base + 2 + i] = fkPacked[i];
|
||||
programs[insertIdx] = program;
|
||||
fragSources[insertIdx] = fragSrc;
|
||||
count++;
|
||||
|
||||
setLastUsed(vkPacked, fkPacked, fkLen, program);
|
||||
|
||||
GLStateManager.LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, count);
|
||||
|
||||
if (dumpDir != null) {
|
||||
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
|
||||
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
|
||||
if (geomSrc != null) {
|
||||
dumpShader(Long.toHexString(vkPacked), ".geom.glsl", geomSrc);
|
||||
}
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
private String findFragSource(long[] fkPacked, int fkLen) {
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
final int base = i * SLOT_STRIDE;
|
||||
if (keys[base + 1] == EMPTY_MARKER) continue;
|
||||
final int storedLen = (int) (keys[base + 1] >>> 48);
|
||||
if (storedLen != fkLen) continue;
|
||||
if (keysMatch(base + 2, fkPacked, fkLen)) {
|
||||
return fragSources[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean keysMatch(int keysOffset, long[] fkPacked, int fkLen) {
|
||||
for (int i = 0; i < fkLen; i++) {
|
||||
if (keys[keysOffset + i] != fkPacked[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setLastUsed(long vkPacked, long[] fkPacked, int fkLen, Program program) {
|
||||
lastVK = vkPacked;
|
||||
lastFKLen = fkLen;
|
||||
lastFK0 = fkPacked[0];
|
||||
lastFK1 = fkLen >= 2 ? fkPacked[1] : 0;
|
||||
lastFK2 = fkLen >= 3 ? fkPacked[2] : 0;
|
||||
lastFK3 = fkLen >= 4 ? fkPacked[3] : 0;
|
||||
lastProgram = program;
|
||||
}
|
||||
|
||||
private int findEmptySlot(long hash) {
|
||||
final int mask = capacity - 1;
|
||||
int idx = (int) (hash & mask);
|
||||
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash over (vk + fk longs). Adapted from Mesa's hash_key — mix-rotate-multiply.
|
||||
*/
|
||||
private static long hashKey(long vkPacked, long[] fkPacked, int fkLen) {
|
||||
long h = vkPacked * 0x9E3779B97F4A7C15L;
|
||||
for (int i = 0; i < fkLen; i++) {
|
||||
h ^= fkPacked[i] * 0x517CC1B727220A95L;
|
||||
h = Long.rotateLeft(h, 31) * 0x9E3779B97F4A7C15L;
|
||||
}
|
||||
return h | 1L; // ensure nonzero lower bits
|
||||
}
|
||||
|
||||
private void grow() {
|
||||
final int oldCap = capacity;
|
||||
final long[] oldKeys = keys;
|
||||
final Program[] oldPrograms = programs;
|
||||
final String[] oldFragSources = fragSources;
|
||||
|
||||
capacity = oldCap * 2;
|
||||
keys = new long[capacity * SLOT_STRIDE];
|
||||
programs = new Program[capacity];
|
||||
fragSources = new String[capacity];
|
||||
count = 0;
|
||||
|
||||
final int mask = capacity - 1;
|
||||
for (int i = 0; i < oldCap; i++) {
|
||||
final int oldBase = i * SLOT_STRIDE;
|
||||
if (oldKeys[oldBase + 1] == EMPTY_MARKER) continue;
|
||||
|
||||
final long storedHash = oldKeys[oldBase + 1] & 0x0000_FFFF_FFFF_FFFFL;
|
||||
int idx = (int) (storedHash & mask);
|
||||
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
final int newBase = idx * SLOT_STRIDE;
|
||||
System.arraycopy(oldKeys, oldBase, keys, newBase, SLOT_STRIDE);
|
||||
programs[idx] = oldPrograms[i];
|
||||
fragSources[idx] = oldFragSources[i];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
private String getOrGenerateVertex(VertexKey vk) {
|
||||
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
|
||||
}
|
||||
|
||||
private String getOrGenerateGeometry(VertexKey vk) {
|
||||
return geometrySourceCache.computeIfAbsent(vk.pack(), k -> GeometryShaderGenerator.generate(vk));
|
||||
}
|
||||
|
||||
private void dumpShader(String name, String suffix, String source) {
|
||||
if (dumpDir == null) return;
|
||||
try {
|
||||
Files.createDirectories(dumpDir);
|
||||
final Path file = dumpDir.resolve("ffp_" + name + suffix);
|
||||
if (!Files.exists(file)) {
|
||||
Files.writeString(file, source, StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
GLStateManager.LOGGER.warn("Failed to dump FFP shader to {}: {}", dumpDir, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Dump all cached shaders to disk. */
|
||||
public void dumpAll(Path outputDir) {
|
||||
try {
|
||||
Files.createDirectories(outputDir);
|
||||
for (var entry : vertexSourceCache.entrySet()) {
|
||||
dumpShader(Long.toHexString(entry.getKey()), ".vert.glsl", entry.getValue());
|
||||
}
|
||||
final IdentityHashMap<String, Integer> seen = new IdentityHashMap<>();
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
if (fragSources[i] != null && !seen.containsKey(fragSources[i])) {
|
||||
final int idx = seen.size();
|
||||
seen.put(fragSources[i], idx);
|
||||
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
|
||||
}
|
||||
}
|
||||
for (var entry : geometrySourceCache.entrySet()) {
|
||||
dumpShader(Long.toHexString(entry.getKey()), ".geom.glsl", entry.getValue());
|
||||
}
|
||||
GLStateManager.LOGGER.info("Dumped {} vertex, {} geometry, and {} fragment FFP shader sources to {}",
|
||||
vertexSourceCache.size(), geometrySourceCache.size(), seen.size(), outputDir);
|
||||
} catch (IOException e) {
|
||||
GLStateManager.LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Destroy all cached programs and clear caches. */
|
||||
public void destroy() {
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
if (programs[i] != null) {
|
||||
programs[i].destroy();
|
||||
}
|
||||
}
|
||||
Arrays.fill(keys, 0);
|
||||
Arrays.fill(programs, null);
|
||||
Arrays.fill(fragSources, null);
|
||||
count = 0;
|
||||
uniqueFragCount = 0;
|
||||
vertexSourceCache.clear();
|
||||
geometrySourceCache.clear();
|
||||
lastProgram = null;
|
||||
lastVK = Long.MIN_VALUE;
|
||||
GLStateManager.LOGGER.debug("FFP shader cache cleared");
|
||||
}
|
||||
|
||||
public int getProgramCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getVertexVariantCount() {
|
||||
return vertexSourceCache.size();
|
||||
}
|
||||
|
||||
public int getFragmentVariantCount() {
|
||||
return uniqueFragCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* FFP shader manager
|
||||
*/
|
||||
public class ShaderManager {
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
static final ShaderManager INSTANCE = new ShaderManager();
|
||||
}
|
||||
|
||||
private final ShaderCache cache = new ShaderCache();
|
||||
private final Uniforms uniforms = new Uniforms();
|
||||
private final Int2IntOpenHashMap vaoVertexFlags = new Int2IntOpenHashMap();
|
||||
|
||||
@Getter
|
||||
private boolean active = false;
|
||||
private Program currentProgram = null;
|
||||
private long currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
|
||||
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
|
||||
private int currentFKLen = 0;
|
||||
|
||||
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
|
||||
@Getter private static final Vector4f currentTexCoord = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
|
||||
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoord);
|
||||
@Getter private static int normalGeneration;
|
||||
@Getter private static int texCoordGeneration;
|
||||
@Getter private boolean enabled = false;
|
||||
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
|
||||
|
||||
private ShaderManager() {
|
||||
vaoVertexFlags.defaultReturnValue(-1);
|
||||
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) {
|
||||
cache.setDumpDir(Paths.get("ffp_shaders"));
|
||||
}
|
||||
}
|
||||
|
||||
public static ShaderManager getInstance() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
|
||||
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
|
||||
currentVertexFlags = format.getVertexFlags();
|
||||
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
|
||||
return false;
|
||||
});
|
||||
|
||||
GLStateManager.LOGGER.info("FFP shader emulation enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
active = true;
|
||||
updateVariant(true, true, true, true);
|
||||
uploadUniforms();
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
currentFKLen = 0;
|
||||
}
|
||||
|
||||
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
GLStateManager.flushDeferredVertexAttribs();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null) bh.flushDeferredBlend();
|
||||
|
||||
if (!active) {
|
||||
if (enabled) {
|
||||
final int currentProgramId = GLStateManager.getActiveProgram();
|
||||
if (currentProgramId != 0) {
|
||||
CompatUniformManager.onUseProgram(currentProgramId);
|
||||
return;
|
||||
}
|
||||
active = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
|
||||
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
|
||||
commitVariant(vkPacked, fkLen);
|
||||
}
|
||||
|
||||
uploadUniforms();
|
||||
}
|
||||
|
||||
public void preDraw(int vertexFlags) {
|
||||
currentVertexFlags = vertexFlags;
|
||||
preDraw(
|
||||
(vertexFlags & VertexFlags.COLOR_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.NORMAL_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.TEXTURE_BIT) != 0,
|
||||
(vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0
|
||||
);
|
||||
}
|
||||
|
||||
public void preDraw() {
|
||||
preDraw(currentVertexFlags);
|
||||
}
|
||||
|
||||
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
commitVariant(vkPacked, fkLen);
|
||||
}
|
||||
|
||||
private void commitVariant(long vkPacked, int fkLen) {
|
||||
currentVertexKeyPacked = vkPacked;
|
||||
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
|
||||
currentFKLen = fkLen;
|
||||
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
|
||||
RENDER_BACKEND.useProgram(currentProgram.getProgramId());
|
||||
}
|
||||
|
||||
private void uploadUniforms() {
|
||||
if (currentProgram != null) {
|
||||
uniforms.upload(currentProgram);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setCurrentNormal(float x, float y, float z) {
|
||||
currentNormal.set(x, y, z);
|
||||
normalGeneration++;
|
||||
}
|
||||
|
||||
public static void setCurrentTexCoord(float s, float t, float r, float q) {
|
||||
currentTexCoord.set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
public static void bumpNormalGeneration() { normalGeneration++; }
|
||||
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
|
||||
|
||||
public void enableClientVertexFlag(int flag) {currentVertexFlags |= flag;}
|
||||
public void disableClientVertexFlag(int flag) {currentVertexFlags &= ~flag;}
|
||||
public int getCurrentVertexFlags() {return currentVertexFlags;}
|
||||
public void setCurrentVertexFlags(int flags) {currentVertexFlags = flags;}
|
||||
|
||||
public void onBindVertexArray(int vaoId) {
|
||||
final int flags = vaoVertexFlags.get(vaoId);
|
||||
if (flags != -1) {
|
||||
currentVertexFlags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeleteVertexArray(int vaoId) {
|
||||
vaoVertexFlags.remove(vaoId);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
cache.destroy();
|
||||
uniforms.destroy();
|
||||
final GLSMInitConfig config = GLStateManager.getInitConfig();
|
||||
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
|
||||
com.gtnewhorizons.angelica.glsm.QuadConverter.destroy();
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
}
|
||||
|
||||
public String getDebugInfo() {
|
||||
return String.format("FFP: %d programs (%d vert, %d frag variants)",
|
||||
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Uploads GLSM cached state to the active FFP program's uniforms.
|
||||
*
|
||||
* Follows Mesa's prog_statevars.c patterns:
|
||||
* - Pre-computed derived state (light products, scene color) when material is static
|
||||
* - Raw light/material values when color material is active
|
||||
* - Optimized fog params (pre-divided for single MAD in shader)
|
||||
*/
|
||||
public class Uniforms {
|
||||
// Reusable temp buffers for uniform upload
|
||||
private final FloatBuffer mat4Buf = memAllocFloat(16);
|
||||
private final FloatBuffer mat3Buf = memAllocFloat(9);
|
||||
private final FloatBuffer vec4Buf = memAllocFloat(4);
|
||||
private final FloatBuffer vec3Buf = memAllocFloat(3);
|
||||
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
|
||||
|
||||
// Derived matrices (computed on CPU)
|
||||
private final Matrix4f mvpMatrix = new Matrix4f();
|
||||
private final Matrix3f normalMatrix = new Matrix3f();
|
||||
|
||||
// Temp vectors for light product and normal scale computation
|
||||
private final Vector4f tempVec4 = new Vector4f();
|
||||
private final org.joml.Vector3f tempVec3 = new org.joml.Vector3f();
|
||||
|
||||
// Pre-computed constants for fog params (Mesa STATE_FOG_PARAMS_OPTIMIZED)
|
||||
private static final double LN2 = Math.log(2.0);
|
||||
private static final double SQRT_LN2 = Math.sqrt(LN2);
|
||||
|
||||
// Dirty tracking: last-uploaded generation per category + program ID.
|
||||
// Program change forces full re-upload since uniform locations differ.
|
||||
private int lastProgramId = -1;
|
||||
private int lastMvGen = -1;
|
||||
private int lastProjGen = -1;
|
||||
private int lastTexMatGen = -1;
|
||||
private int lastLightingGen = -1;
|
||||
private int lastFragmentGen = -1;
|
||||
private int lastColorGen = -1;
|
||||
private int lastNormalGen = -1;
|
||||
private int lastTexCoordGen = -1;
|
||||
private float lastLightmapX = Float.NaN;
|
||||
private float lastLightmapY = Float.NaN;
|
||||
private int lastTexGenGen = -1;
|
||||
private int lastClipPlaneGen = -1;
|
||||
private float lastLineWidth = Float.NaN;
|
||||
private int lastViewportWidth = -1;
|
||||
private int lastViewportHeight = -1;
|
||||
|
||||
/**
|
||||
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
|
||||
* Uses generation counters to skip categories that haven't changed since last upload.
|
||||
*/
|
||||
public void upload(Program program) {
|
||||
final boolean programChanged = program.getProgramId() != lastProgramId;
|
||||
lastProgramId = program.getProgramId();
|
||||
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
if (program.getVertexKey().lightingEnabled()) {
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
uploadLighting(program);
|
||||
lastLightingGen = litGen;
|
||||
}
|
||||
}
|
||||
|
||||
// Current color/normal/texcoord — skip if generation unchanged
|
||||
if (!program.getVertexKey().hasVertexColor()) {
|
||||
final int colorGen = GLStateManager.colorGeneration;
|
||||
if (programChanged || colorGen != lastColorGen) {
|
||||
uploadCurrentColor(program);
|
||||
lastColorGen = colorGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
|
||||
final int normalGen = ShaderManager.getNormalGeneration();
|
||||
if (programChanged || normalGen != lastNormalGen) {
|
||||
uploadCurrentNormal(program);
|
||||
lastNormalGen = normalGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
|
||||
final int texGen = ShaderManager.getTexCoordGeneration();
|
||||
if (programChanged || texGen != lastTexCoordGen) {
|
||||
uploadCurrentTexCoord(program);
|
||||
lastTexCoordGen = texGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
|
||||
if (programChanged || GLSMConfig.lastBrightnessX != lastLightmapX || GLSMConfig.lastBrightnessY != lastLightmapY) {
|
||||
uploadCurrentLightmapCoord(program);
|
||||
lastLightmapX = GLSMConfig.lastBrightnessX;
|
||||
lastLightmapY = GLSMConfig.lastBrightnessY;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().texGenEnabled()) {
|
||||
final int tgGen = GLStateManager.texGenGeneration;
|
||||
if (programChanged || tgGen != lastTexGenGen) {
|
||||
uploadTexGen(program);
|
||||
lastTexGenGen = tgGen;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.getVertexKey().clipPlanesEnabled()) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
uploadClipPlanes(program);
|
||||
lastClipPlaneGen = cpGen;
|
||||
}
|
||||
}
|
||||
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
uploadFragmentUniforms(program);
|
||||
lastFragmentGen = fragGen;
|
||||
}
|
||||
|
||||
// Wide line emulation uniforms
|
||||
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
|
||||
uploadWideLineUniforms(program, programChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
// ModelView + derived (normal matrix, normal scale)
|
||||
if (mvChanged) {
|
||||
if (program.locModelViewMatrix != -1) {
|
||||
mv.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
|
||||
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
}
|
||||
if (program.locNormalMatrix != -1) {
|
||||
normalMatrix.get(mat3Buf);
|
||||
RENDER_BACKEND.uniformMatrix3(program.locNormalMatrix, false, mat3Buf);
|
||||
}
|
||||
|
||||
// Normal scale (for GL_RESCALE_NORMAL without GL_NORMALIZE)
|
||||
if (program.locNormalScale != -1) {
|
||||
// Scale factor = 1/length of first column of normal matrix
|
||||
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
|
||||
RENDER_BACKEND.uniform1f(program.locNormalScale, scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Projection
|
||||
if (projChanged) {
|
||||
if (program.locProjectionMatrix != -1) {
|
||||
proj.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(program.locProjectionMatrix, false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// MVP = Projection * ModelView — needs recompute if either input changed
|
||||
if ((mvChanged || projChanged) && program.locMVPMatrix != -1) {
|
||||
proj.mul(mv, mvpMatrix);
|
||||
mvpMatrix.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Texture matrices
|
||||
if (texMatChanged) {
|
||||
// Texture matrix unit 0
|
||||
if (program.locTextureMatrix0 != -1) {
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
texMat.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix0, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Texture matrix unit 1 (lightmap)
|
||||
if (program.locLightmapTextureMatrix != -1) {
|
||||
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
|
||||
lmTexMat.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(program.locLightmapTextureMatrix, false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadLighting(Program program) {
|
||||
final VertexKey vk = program.getVertexKey();
|
||||
|
||||
if (vk.colorMaterialEnabled()) {
|
||||
uploadLightingColorMaterial(program, vk);
|
||||
} else {
|
||||
uploadLightingPreComputed(program, vk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Color material active: upload raw light and material values.
|
||||
* The shader multiplies them with vertex color at runtime.
|
||||
*/
|
||||
private void uploadLightingColorMaterial(Program program, VertexKey vk) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
|
||||
uploadVec4(program.locLightModelAmbient, GLStateManager.getLightModel().ambient);
|
||||
uploadVec4(program.locMaterialEmission, mat.emission);
|
||||
uploadVec4(program.locMaterialAmbient, mat.ambient);
|
||||
uploadVec4(program.locMaterialDiffuse, mat.diffuse);
|
||||
uploadVec4(program.locMaterialSpecular, mat.specular);
|
||||
if (program.locMaterialShininess != -1) {
|
||||
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
|
||||
}
|
||||
|
||||
if (vk.light0Enabled()) {
|
||||
final LightState light0 = GLStateManager.getLightDataStates()[0];
|
||||
uploadVec4(program.locLight0Ambient, light0.ambient);
|
||||
uploadVec4(program.locLight0Diffuse, light0.diffuse);
|
||||
uploadVec4(program.locLight0Specular, light0.specular);
|
||||
uploadVec4(program.locLight0Position, light0.position);
|
||||
}
|
||||
if (vk.light1Enabled()) {
|
||||
final LightState light1 = GLStateManager.getLightDataStates()[1];
|
||||
uploadVec4(program.locLight1Ambient, light1.ambient);
|
||||
uploadVec4(program.locLight1Diffuse, light1.diffuse);
|
||||
uploadVec4(program.locLight1Specular, light1.specular);
|
||||
uploadVec4(program.locLight1Position, light1.position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No color material: upload pre-computed light products and scene color.
|
||||
* Fewer shader multiplies since material is static.
|
||||
*/
|
||||
private void uploadLightingPreComputed(Program program, VertexKey vk) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
|
||||
// Scene color = emission + ambient * lightModel.ambient
|
||||
// Alpha = material.diffuse.a
|
||||
if (program.locSceneColor != -1) {
|
||||
tempVec4.set(
|
||||
mat.emission.x + mat.ambient.x * GLStateManager.getLightModel().ambient.x,
|
||||
mat.emission.y + mat.ambient.y * GLStateManager.getLightModel().ambient.y,
|
||||
mat.emission.z + mat.ambient.z * GLStateManager.getLightModel().ambient.z,
|
||||
mat.diffuse.w // alpha from diffuse
|
||||
);
|
||||
uploadVec4(program.locSceneColor, tempVec4);
|
||||
}
|
||||
|
||||
if (program.locMaterialShininess != -1) {
|
||||
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
|
||||
}
|
||||
|
||||
// Per-light products
|
||||
if (vk.light0Enabled()) {
|
||||
final LightState light0 = GLStateManager.getLightDataStates()[0];
|
||||
uploadVec4(program.locLight0Position, light0.position);
|
||||
uploadLightProduct(program.locLightProd0Ambient, light0.ambient, mat.ambient);
|
||||
uploadLightProduct(program.locLightProd0Diffuse, light0.diffuse, mat.diffuse);
|
||||
uploadLightProduct(program.locLightProd0Specular, light0.specular, mat.specular);
|
||||
}
|
||||
if (vk.light1Enabled()) {
|
||||
final LightState light1 = GLStateManager.getLightDataStates()[1];
|
||||
uploadVec4(program.locLight1Position, light1.position);
|
||||
uploadLightProduct(program.locLightProd1Ambient, light1.ambient, mat.ambient);
|
||||
uploadLightProduct(program.locLightProd1Diffuse, light1.diffuse, mat.diffuse);
|
||||
uploadLightProduct(program.locLightProd1Specular, light1.specular, mat.specular);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload pre-multiplied light product: light.X * material.X (RGB only).
|
||||
*/
|
||||
private void uploadLightProduct(int loc, Vector4f lightVal, Vector4f materialVal) {
|
||||
if (loc == -1) return;
|
||||
vec3Buf.clear();
|
||||
vec3Buf.put(lightVal.x * materialVal.x);
|
||||
vec3Buf.put(lightVal.y * materialVal.y);
|
||||
vec3Buf.put(lightVal.z * materialVal.z);
|
||||
vec3Buf.flip();
|
||||
RENDER_BACKEND.uniform3(loc, vec3Buf);
|
||||
}
|
||||
|
||||
private void uploadCurrentColor(Program program) {
|
||||
if (program.locCurrentColor == -1) return;
|
||||
// Upload the current color from GLSM
|
||||
final var color = GLStateManager.getColor();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(color.getRed());
|
||||
vec4Buf.put(color.getGreen());
|
||||
vec4Buf.put(color.getBlue());
|
||||
vec4Buf.put(color.getAlpha());
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
|
||||
}
|
||||
|
||||
private void uploadCurrentTexCoord(Program program) {
|
||||
if (program.locCurrentTexCoord == -1) return;
|
||||
final var tc = ShaderManager.getCurrentTexCoord();
|
||||
uploadVec4(program.locCurrentTexCoord, tc);
|
||||
}
|
||||
|
||||
private void uploadCurrentLightmapCoord(Program program) {
|
||||
if (program.locCurrentLightmapCoord == -1) return;
|
||||
RENDER_BACKEND.uniform2f(program.locCurrentLightmapCoord,
|
||||
GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY);
|
||||
}
|
||||
|
||||
private void uploadCurrentNormal(Program program) {
|
||||
if (program.locCurrentNormal == -1) return;
|
||||
// Upload current normal from FFPShaderManager's tracked normal
|
||||
final var normal = ShaderManager.getCurrentNormal();
|
||||
vec3Buf.clear();
|
||||
vec3Buf.put(normal.x);
|
||||
vec3Buf.put(normal.y);
|
||||
vec3Buf.put(normal.z);
|
||||
vec3Buf.flip();
|
||||
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
|
||||
}
|
||||
|
||||
private void uploadTexGen(Program program) {
|
||||
final VertexKey vk = program.getVertexKey();
|
||||
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
|
||||
|
||||
if (vk.texGenModeS() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneS, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_S));
|
||||
} else if (vk.texGenModeS() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneS, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_S));
|
||||
}
|
||||
|
||||
if (vk.texGenModeT() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneT, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_T));
|
||||
} else if (vk.texGenModeT() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneT, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_T));
|
||||
}
|
||||
|
||||
if (vk.texGenModeR() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneR, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_R));
|
||||
} else if (vk.texGenModeR() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneR, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_R));
|
||||
}
|
||||
|
||||
if (vk.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) {
|
||||
uploadPlane(program.locTexGenObjPlaneQ, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_Q));
|
||||
} else if (vk.texGenModeQ() == VertexKey.TG_EYE_LINEAR) {
|
||||
uploadPlane(program.locTexGenEyePlaneQ, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_Q));
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadPlane(int loc, float[] plane) {
|
||||
if (loc == -1) return;
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(plane[0]);
|
||||
vec4Buf.put(plane[1]);
|
||||
vec4Buf.put(plane[2]);
|
||||
vec4Buf.put(plane[3]);
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(loc, vec4Buf);
|
||||
}
|
||||
|
||||
private void uploadClipPlanes(Program program) {
|
||||
if (program.locClipPlanes == -1) return;
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
clipPlaneBuf.flip();
|
||||
RENDER_BACKEND.uniform4(program.locClipPlanes, clipPlaneBuf);
|
||||
}
|
||||
|
||||
private void uploadFragmentUniforms(Program program) {
|
||||
final FragmentKey fk = program.getFragmentKey();
|
||||
|
||||
// Alpha test reference
|
||||
if (fk.alphaTestEnabled() && program.locAlphaRef != -1) {
|
||||
RENDER_BACKEND.uniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
|
||||
}
|
||||
|
||||
// Per-unit tex env color
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (program.locTexEnvColor[i] != -1) {
|
||||
final var envState = GLStateManager.getTextures().getTexEnvState(i);
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(envState.envColorR);
|
||||
vec4Buf.put(envState.envColorG);
|
||||
vec4Buf.put(envState.envColorB);
|
||||
vec4Buf.put(envState.envColorA);
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(program.locTexEnvColor[i], vec4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Fog
|
||||
if (fk.fogMode() != FragmentKey.FOG_NONE) {
|
||||
uploadFog(program);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload optimized fog params (following Mesa's STATE_FOG_PARAMS_OPTIMIZED):
|
||||
*/
|
||||
private void uploadFog(Program program) {
|
||||
final FogState fog = GLStateManager.getFogState();
|
||||
|
||||
if (program.locFogParams != -1) {
|
||||
final float start = fog.getStart();
|
||||
final float end = fog.getEnd();
|
||||
final float density = fog.getDensity();
|
||||
final float range = end - start;
|
||||
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(range != 0.0f ? -1.0f / range : 0.0f); // [0]: -1/(end-start)
|
||||
vec4Buf.put(range != 0.0f ? end / range : 1.0f); // [1]: end/(end-start)
|
||||
vec4Buf.put((float)(density / LN2)); // [2]: density/ln(2)
|
||||
vec4Buf.put((float)(density / SQRT_LN2)); // [3]: density/sqrt(ln(2))
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(program.locFogParams, vec4Buf);
|
||||
}
|
||||
|
||||
if (program.locFogColor != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put((float) fog.getFogColor().x);
|
||||
vec4Buf.put((float) fog.getFogColor().y);
|
||||
vec4Buf.put((float) fog.getFogColor().z);
|
||||
vec4Buf.put(fog.getFogAlpha());
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(program.locFogColor, vec4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadWideLineUniforms(Program program, boolean programChanged) {
|
||||
final float lineWidth = GLStateManager.getLineState().getWidth();
|
||||
if (programChanged || lineWidth != lastLineWidth) {
|
||||
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth);
|
||||
lastLineWidth = lineWidth;
|
||||
}
|
||||
final int vw = GLStateManager.getViewportState().width;
|
||||
final int vh = GLStateManager.getViewportState().height;
|
||||
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
|
||||
RENDER_BACKEND.uniform2f(program.locViewportSize, vw, vh);
|
||||
lastViewportWidth = vw;
|
||||
lastViewportHeight = vh;
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadVec4(int loc, Vector4f v) {
|
||||
if (loc == -1) return;
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(v.x);
|
||||
vec4Buf.put(v.y);
|
||||
vec4Buf.put(v.z);
|
||||
vec4Buf.put(v.w);
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(loc, vec4Buf);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
memFree(mat4Buf);
|
||||
memFree(mat3Buf);
|
||||
memFree(vec4Buf);
|
||||
memFree(vec3Buf);
|
||||
memFree(clipPlaneBuf);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
/**
|
||||
* Compact key capturing the vertex shader permutation axes for FFP emulation.
|
||||
* Packed into a single long for fast hash/compare.
|
||||
*/
|
||||
public final class VertexKey {
|
||||
|
||||
private final long packed;
|
||||
|
||||
private static final int BIT_LIGHTING = 0;
|
||||
private static final int BIT_LIGHT0 = 1;
|
||||
private static final int BIT_LIGHT1 = 2;
|
||||
private static final int BIT_LIGHT0_DIR = 3;
|
||||
private static final int BIT_LIGHT1_DIR = 4;
|
||||
private static final int BIT_COLOR_MATERIAL = 5;
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6;
|
||||
private static final int BIT_FOG = 7;
|
||||
private static final int BIT_FOG_DIST_MODE = 8;
|
||||
private static final int BIT_NORMALIZE = 10;
|
||||
private static final int BIT_RESCALE_NORMAL = 11;
|
||||
private static final int BIT_TEXTURE = 12;
|
||||
private static final int BIT_LIGHTMAP = 13;
|
||||
private static final int BIT_TEX_MATRIX = 14;
|
||||
private static final int BIT_HAS_VERTEX_COLOR = 15;
|
||||
private static final int BIT_HAS_VERTEX_NORMAL = 16;
|
||||
private static final int BIT_HAS_VERTEX_TEX = 17;
|
||||
private static final int BIT_HAS_VERTEX_LIGHTMAP = 18;
|
||||
private static final int BIT_COLOR_MAT_MODE = 19;
|
||||
// bits 19-21: colorMaterialMode (3 bits)
|
||||
// TexGen modes: 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR)
|
||||
private static final int BIT_TEXGEN_S = 22;
|
||||
private static final int BIT_TEXGEN_T = 25;
|
||||
private static final int BIT_TEXGEN_R = 28;
|
||||
private static final int BIT_TEXGEN_Q = 31;
|
||||
// bits 31-33: texGenModeQ (3 bits)
|
||||
private static final int BIT_CLIP_PLANES = 34;
|
||||
private static final int BIT_WIDE_LINE = 35;
|
||||
|
||||
public static final int TG_NONE = 0;
|
||||
public static final int TG_OBJ_LINEAR = 1;
|
||||
public static final int TG_EYE_LINEAR = 2;
|
||||
|
||||
public static final int CM_AMBIENT = 0;
|
||||
public static final int CM_DIFFUSE = 1;
|
||||
public static final int CM_SPECULAR = 2;
|
||||
public static final int CM_EMISSION = 3;
|
||||
public static final int CM_AMBIENT_AND_DIFFUSE = 4;
|
||||
|
||||
private VertexKey(long packed) {
|
||||
this.packed = packed;
|
||||
}
|
||||
|
||||
public long pack() { return packed; }
|
||||
|
||||
public boolean lightingEnabled() { return bit(BIT_LIGHTING); }
|
||||
public boolean light0Enabled() { return bit(BIT_LIGHT0); }
|
||||
public boolean light1Enabled() { return bit(BIT_LIGHT1); }
|
||||
public boolean light0Directional() { return bit(BIT_LIGHT0_DIR); }
|
||||
public boolean light1Directional() { return bit(BIT_LIGHT1_DIR); }
|
||||
public boolean colorMaterialEnabled() { return bit(BIT_COLOR_MATERIAL); }
|
||||
public boolean separateSpecular() { return bit(BIT_SEPARATE_SPECULAR); }
|
||||
public boolean fogEnabled() { return bit(BIT_FOG); }
|
||||
public int fogDistanceMode() { return (int)((packed >> BIT_FOG_DIST_MODE) & 0x3); }
|
||||
public boolean normalizeEnabled() { return bit(BIT_NORMALIZE); }
|
||||
public boolean rescaleNormalsEnabled() { return bit(BIT_RESCALE_NORMAL); }
|
||||
public boolean textureEnabled() { return bit(BIT_TEXTURE); }
|
||||
public boolean lightmapEnabled() { return bit(BIT_LIGHTMAP); }
|
||||
public boolean textureMatrixEnabled() { return bit(BIT_TEX_MATRIX); }
|
||||
public boolean hasVertexColor() { return bit(BIT_HAS_VERTEX_COLOR); }
|
||||
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
|
||||
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
|
||||
public boolean hasVertexLightmap() { return bit(BIT_HAS_VERTEX_LIGHTMAP); }
|
||||
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
|
||||
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
|
||||
|
||||
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). */
|
||||
public int texGenModeS() { return (int)((packed >> BIT_TEXGEN_S) & 0x7); }
|
||||
/** TexGen mode for T coordinate. */
|
||||
public int texGenModeT() { return (int)((packed >> BIT_TEXGEN_T) & 0x7); }
|
||||
/** TexGen mode for R coordinate. */
|
||||
public int texGenModeR() { return (int)((packed >> BIT_TEXGEN_R) & 0x7); }
|
||||
/** TexGen mode for Q coordinate. */
|
||||
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
|
||||
/** Whether any texgen coordinate is enabled. */
|
||||
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
|
||||
/** Whether any clip plane is enabled. */
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
/** Whether wide line GS emulation is active for this variant. */
|
||||
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
|
||||
|
||||
/** Whether vertex color replaces material ambient in this variant. */
|
||||
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
/** Whether vertex color replaces material diffuse in this variant. */
|
||||
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
/** Whether vertex color replaces material specular in this variant. */
|
||||
public boolean cmReplacesSpecular() { return colorMaterialMode() == CM_SPECULAR; }
|
||||
/** Whether vertex color replaces material emission in this variant. */
|
||||
public boolean cmReplacesEmission() { return colorMaterialMode() == CM_EMISSION; }
|
||||
|
||||
private boolean bit(int pos) { return ((packed >> pos) & 1) != 0; }
|
||||
|
||||
static int encodeTexGenMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_OBJECT_LINEAR -> TG_OBJ_LINEAR;
|
||||
case GL11.GL_EYE_LINEAR -> TG_EYE_LINEAR;
|
||||
default -> TG_NONE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeColorMaterialMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_AMBIENT -> CM_AMBIENT;
|
||||
case GL11.GL_DIFFUSE -> CM_DIFFUSE;
|
||||
case GL11.GL_SPECULAR -> CM_SPECULAR;
|
||||
case GL11.GL_EMISSION -> CM_EMISSION;
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> CM_AMBIENT_AND_DIFFUSE;
|
||||
default -> CM_AMBIENT_AND_DIFFUSE;
|
||||
};
|
||||
}
|
||||
|
||||
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
long bits = 0;
|
||||
|
||||
final boolean lighting = GLStateManager.getLightingState().isEnabled();
|
||||
if (lighting) {
|
||||
bits |= (1L << BIT_LIGHTING);
|
||||
|
||||
if (GLStateManager.getLightStates()[0].isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHT0);
|
||||
// position.w == 0 means directional
|
||||
if (GLStateManager.getLightDataStates()[0].position.w == 0.0f) {
|
||||
bits |= (1L << BIT_LIGHT0_DIR);
|
||||
}
|
||||
}
|
||||
if (GLStateManager.getLightStates()[1].isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHT1);
|
||||
if (GLStateManager.getLightDataStates()[1].position.w == 0.0f) {
|
||||
bits |= (1L << BIT_LIGHT1_DIR);
|
||||
}
|
||||
}
|
||||
|
||||
if (GLStateManager.getColorMaterial().isEnabled()) {
|
||||
bits |= (1L << BIT_COLOR_MATERIAL);
|
||||
final int cmMode = encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue());
|
||||
bits |= ((long) cmMode & 0x7) << BIT_COLOR_MAT_MODE;
|
||||
}
|
||||
|
||||
if (GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
bits |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
}
|
||||
}
|
||||
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
bits |= (1L << BIT_FOG);
|
||||
final int fogDistMode = GLStateManager.getFogState().getFogDistanceMode();
|
||||
bits |= ((long) fogDistMode & 0x3) << BIT_FOG_DIST_MODE;
|
||||
}
|
||||
|
||||
if (GLStateManager.getNormalizeState().isEnabled()) {
|
||||
bits |= (1L << BIT_NORMALIZE);
|
||||
}
|
||||
if (GLStateManager.getRescaleNormalState().isEnabled()) {
|
||||
bits |= (1L << BIT_RESCALE_NORMAL);
|
||||
}
|
||||
|
||||
// Texture unit 0 (base texture)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
|
||||
bits |= (1L << BIT_TEXTURE);
|
||||
}
|
||||
// Texture unit 1 (lightmap)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHTMAP);
|
||||
}
|
||||
|
||||
// Check if texture matrix unit 0 is non-identity
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
if (!DisplayListManager.isIdentity(texMat)) {
|
||||
bits |= (1L << BIT_TEX_MATRIX);
|
||||
}
|
||||
|
||||
// TexGen (unit 0 only) — encode per-coordinate mode if enabled
|
||||
final var texUnit = GLStateManager.getTextures();
|
||||
final TexGenState tg = texUnit.getTexGenState(0);
|
||||
boolean anyTexGen = false;
|
||||
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_S));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_S;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenTStates(0).isEnabled() && tg.getMode(GL11.GL_T) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_T));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_T;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenRStates(0).isEnabled() && tg.getMode(GL11.GL_R) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_R));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_R;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
if (texUnit.getTexGenQStates(0).isEnabled() && tg.getMode(GL11.GL_Q) != 0) {
|
||||
final int m = encodeTexGenMode(tg.getMode(GL11.GL_Q));
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_Q;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
// Force texture matrix on when texgen is active (end portal pattern always uses it)
|
||||
if (anyTexGen) {
|
||||
bits |= (1L << BIT_TEX_MATRIX);
|
||||
}
|
||||
|
||||
// Vertex format flags
|
||||
if (hasColor) bits |= (1L << BIT_HAS_VERTEX_COLOR);
|
||||
if (hasNormal) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
|
||||
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
|
||||
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
|
||||
|
||||
// Clip planes
|
||||
if (GLStateManager.anyClipPlaneEnabled()) {
|
||||
bits |= (1L << BIT_CLIP_PLANES);
|
||||
}
|
||||
|
||||
// Wide line GS emulation
|
||||
if (GLStateManager.wideLineEmulationActive) {
|
||||
bits |= (1L << BIT_WIDE_LINE);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap));
|
||||
}
|
||||
|
||||
static VertexKey fromPacked(long packed) {
|
||||
return new VertexKey(packed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(packed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof VertexKey other)) return false;
|
||||
return packed == other.packed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
|
||||
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
|
||||
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
|
||||
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
|
||||
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core vertex shaders for FFP emulation.
|
||||
* Follows Mesa's ffvertex_prog.c build order.
|
||||
*
|
||||
* Attribute locations are sourced from {@link VertexFormatElement.Usage} to stay in sync with GTNHLib's vertex format setup.
|
||||
*/
|
||||
public final class VertexShaderGenerator {
|
||||
|
||||
private VertexShaderGenerator() {}
|
||||
|
||||
public static String generate(VertexKey key) {
|
||||
final StringBuilder sb = new StringBuilder(2048);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
emitAttributes(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
emitOutputs(sb, key);
|
||||
|
||||
sb.append("void main() {\n");
|
||||
emitPositionTransform(sb, key);
|
||||
if (key.lightingEnabled()) {
|
||||
emitNormalTransform(sb, key);
|
||||
emitLighting(sb, key);
|
||||
} else {
|
||||
emitColorPassthrough(sb, key);
|
||||
}
|
||||
emitTexCoordPassthrough(sb, key);
|
||||
if (key.fogEnabled()) {
|
||||
emitFogDistance(sb, key);
|
||||
}
|
||||
if (key.clipPlanesEnabled()) {
|
||||
emitClipDistances(sb);
|
||||
}
|
||||
sb.append("}\n");
|
||||
|
||||
String source = sb.toString();
|
||||
if (key.wideLineEmulation()) {
|
||||
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
|
||||
.replace("v_TexCoord0", "v_TexCoord0_gs")
|
||||
.replace("v_TexCoord1", "v_TexCoord1_gs")
|
||||
.replace("v_FogCoord", "v_FogCoord_gs")
|
||||
.replace("v_Color", "v_Color_gs");
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private static void emitAttributes(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Vertex attributes\n");
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
|
||||
if (key.hasVertexColor()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
|
||||
}
|
||||
if (key.hasVertexTexCoord()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
|
||||
}
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
|
||||
}
|
||||
if (key.hasVertexNormal()) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitUniforms(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Matrices\n");
|
||||
sb.append("uniform mat4 u_ModelViewMatrix;\n");
|
||||
sb.append("uniform mat4 u_ProjectionMatrix;\n");
|
||||
sb.append("uniform mat4 u_MVPMatrix;\n");
|
||||
|
||||
if (key.lightingEnabled()) {
|
||||
sb.append("uniform mat3 u_NormalMatrix;\n");
|
||||
}
|
||||
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append("uniform mat4 u_TextureMatrix0;\n");
|
||||
}
|
||||
|
||||
// TexGen plane uniforms
|
||||
if (key.texGenModeS() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneS;\n");
|
||||
if (key.texGenModeS() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneS;\n");
|
||||
if (key.texGenModeT() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneT;\n");
|
||||
if (key.texGenModeT() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneT;\n");
|
||||
if (key.texGenModeR() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneR;\n");
|
||||
if (key.texGenModeR() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneR;\n");
|
||||
if (key.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneQ;\n");
|
||||
if (key.texGenModeQ() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneQ;\n");
|
||||
|
||||
if (!key.hasVertexNormal() && key.lightingEnabled()) {
|
||||
sb.append("uniform vec3 u_CurrentNormal;\n");
|
||||
}
|
||||
|
||||
if (!key.hasVertexColor()) {
|
||||
sb.append("uniform vec4 u_CurrentColor;\n");
|
||||
}
|
||||
|
||||
if (!key.hasVertexTexCoord() && key.textureEnabled()) {
|
||||
sb.append("uniform vec4 u_CurrentTexCoord;\n");
|
||||
}
|
||||
|
||||
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
|
||||
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
|
||||
}
|
||||
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("uniform mat4 u_LightmapTextureMatrix;\n");
|
||||
}
|
||||
|
||||
if (key.lightingEnabled()) {
|
||||
emitLightingUniforms(sb, key);
|
||||
}
|
||||
|
||||
if (key.lightingEnabled() && (key.normalizeEnabled() || key.rescaleNormalsEnabled())) {
|
||||
// normalize is done in shader; rescale uses normalScale
|
||||
if (key.rescaleNormalsEnabled() && !key.normalizeEnabled()) {
|
||||
sb.append("uniform float u_NormalScale;\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (key.clipPlanesEnabled()) {
|
||||
sb.append("uniform vec4 u_ClipPlane[8];\n");
|
||||
}
|
||||
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLightingUniforms(StringBuilder sb, VertexKey key) {
|
||||
sb.append("\n// Lighting\n");
|
||||
|
||||
if (key.colorMaterialEnabled()) {
|
||||
// When color material is active, we can't pre-compute light products so we upload raw light and material values
|
||||
sb.append("uniform vec4 u_LightModelAmbient;\n");
|
||||
sb.append("uniform vec4 u_MaterialEmission;\n");
|
||||
sb.append("uniform vec4 u_MaterialAmbient;\n");
|
||||
sb.append("uniform vec4 u_MaterialDiffuse;\n");
|
||||
sb.append("uniform vec4 u_MaterialSpecular;\n");
|
||||
sb.append("uniform float u_MaterialShininess;\n");
|
||||
|
||||
if (key.light0Enabled()) {
|
||||
sb.append("uniform vec4 u_Light0Ambient;\n");
|
||||
sb.append("uniform vec4 u_Light0Diffuse;\n");
|
||||
sb.append("uniform vec4 u_Light0Specular;\n");
|
||||
sb.append("uniform vec4 u_Light0Position;\n");
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
sb.append("uniform vec4 u_Light1Ambient;\n");
|
||||
sb.append("uniform vec4 u_Light1Diffuse;\n");
|
||||
sb.append("uniform vec4 u_Light1Specular;\n");
|
||||
sb.append("uniform vec4 u_Light1Position;\n");
|
||||
}
|
||||
} else {
|
||||
// Pre-computed: sceneColor + lightProducts
|
||||
sb.append("uniform vec4 u_SceneColor;\n");
|
||||
|
||||
if (key.light0Enabled()) {
|
||||
sb.append("uniform vec4 u_Light0Position;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Ambient;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Diffuse;\n");
|
||||
sb.append("uniform vec3 u_LightProd0Specular;\n");
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
sb.append("uniform vec4 u_Light1Position;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Ambient;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Diffuse;\n");
|
||||
sb.append("uniform vec3 u_LightProd1Specular;\n");
|
||||
}
|
||||
sb.append("uniform float u_MaterialShininess;\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitOutputs(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Outputs\n");
|
||||
sb.append("out vec4 v_Color;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("out float v_FogCoord;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Position transform\n");
|
||||
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
|
||||
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
|
||||
|
||||
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
|
||||
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
|
||||
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Normal transform\n");
|
||||
if (key.hasVertexNormal()) {
|
||||
sb.append(" vec3 normal = u_NormalMatrix * a_Normal;\n");
|
||||
} else {
|
||||
sb.append(" vec3 normal = u_NormalMatrix * u_CurrentNormal;\n");
|
||||
}
|
||||
|
||||
if (key.normalizeEnabled()) {
|
||||
sb.append(" normal = normalize(normal);\n");
|
||||
} else if (key.rescaleNormalsEnabled()) {
|
||||
sb.append(" normal *= u_NormalScale;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLighting(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Gouraud lighting\n");
|
||||
|
||||
if (key.colorMaterialEnabled()) {
|
||||
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
|
||||
sb.append(" vec4 matColor = ");
|
||||
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
|
||||
sb.append(";\n");
|
||||
|
||||
// Emission source: vertex color for CM_EMISSION, else material uniform
|
||||
final String emSrc = key.cmReplacesEmission() ? "matColor.rgb" : "u_MaterialEmission.rgb";
|
||||
// Ambient source for scene color: vertex color for CM_AMBIENT / CM_AMBIENT_AND_DIFFUSE
|
||||
final String amSrc = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
|
||||
sb.append(" vec3 sceneColor = ").append(emSrc).append(" + ").append(amSrc).append(" * u_LightModelAmbient.rgb;\n");
|
||||
|
||||
// Alpha from diffuse source (per GL spec)
|
||||
sb.append(" float outAlpha = ").append(key.cmReplacesDiffuse() ? "matColor.a" : "u_MaterialDiffuse.a").append(";\n");
|
||||
} else {
|
||||
// Pre-computed scene color
|
||||
sb.append(" vec3 sceneColor = u_SceneColor.rgb;\n");
|
||||
sb.append(" float outAlpha = u_SceneColor.a;\n");
|
||||
}
|
||||
|
||||
sb.append(" vec3 color0 = sceneColor;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" vec3 specAccum = vec3(0.0);\n");
|
||||
}
|
||||
|
||||
// Per-light accumulation
|
||||
if (key.light0Enabled()) {
|
||||
emitLightContribution(sb, key, 0);
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
emitLightContribution(sb, key, 1);
|
||||
}
|
||||
|
||||
sb.append(" v_Color = vec4(clamp(color0, 0.0, 1.0), outAlpha);\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" v_SpecularColor = clamp(specAccum, 0.0, 1.0);\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLightContribution(StringBuilder sb, VertexKey key, int lightIndex) {
|
||||
final String li = String.valueOf(lightIndex);
|
||||
final boolean directional = (lightIndex == 0) ? key.light0Directional() : key.light1Directional();
|
||||
final String posUniform = "u_Light" + li + "Position";
|
||||
|
||||
sb.append(" { // Light ").append(li).append('\n');
|
||||
|
||||
// Light vector
|
||||
if (directional) {
|
||||
// Directional: position is already normalized direction
|
||||
sb.append(" vec3 VPpli = normalize(").append(posUniform).append(".xyz);\n");
|
||||
} else {
|
||||
// Positional light
|
||||
sb.append(" vec3 VPpli = ").append(posUniform).append(".xyz - eyePos.xyz;\n");
|
||||
sb.append(" VPpli = normalize(VPpli);\n");
|
||||
}
|
||||
|
||||
// Dot products
|
||||
sb.append(" float NdotVP = max(dot(normal, VPpli), 0.0);\n");
|
||||
|
||||
// Half vector for specular (infinite viewer: H = normalize(L + (0,0,1)))
|
||||
sb.append(" vec3 halfVec = normalize(VPpli + vec3(0.0, 0.0, 1.0));\n");
|
||||
sb.append(" float NdotHV = max(dot(normal, halfVec), 0.0);\n");
|
||||
|
||||
// Specular power
|
||||
sb.append(" float spec = (NdotVP > 0.0 && u_MaterialShininess > 0.0) ? pow(NdotHV, u_MaterialShininess) : 0.0;\n");
|
||||
|
||||
// Accumulate — per-property source depends on color material mode
|
||||
if (key.colorMaterialEnabled()) {
|
||||
final String ambU = "u_Light" + li + "Ambient";
|
||||
final String difU = "u_Light" + li + "Diffuse";
|
||||
final String speU = "u_Light" + li + "Specular";
|
||||
// Ambient product: light.ambient * (vertex color or material)
|
||||
final String ambMat = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
|
||||
sb.append(" vec3 ambient = ").append(ambU).append(".rgb * ").append(ambMat).append(";\n");
|
||||
// Diffuse product
|
||||
final String difMat = key.cmReplacesDiffuse() ? "matColor.rgb" : "u_MaterialDiffuse.rgb";
|
||||
sb.append(" vec3 diffuse = ").append(difU).append(".rgb * ").append(difMat).append(";\n");
|
||||
// Specular product
|
||||
final String speMat = key.cmReplacesSpecular() ? "matColor.rgb" : "u_MaterialSpecular.rgb";
|
||||
sb.append(" vec3 specular = ").append(speU).append(".rgb * ").append(speMat).append(";\n");
|
||||
} else {
|
||||
sb.append(" vec3 ambient = u_LightProd").append(li).append("Ambient;\n");
|
||||
sb.append(" vec3 diffuse = u_LightProd").append(li).append("Diffuse;\n");
|
||||
sb.append(" vec3 specular = u_LightProd").append(li).append("Specular;\n");
|
||||
}
|
||||
|
||||
sb.append(" color0 += ambient + NdotVP * diffuse;\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" specAccum += spec * specular;\n");
|
||||
} else {
|
||||
sb.append(" color0 += spec * specular;\n");
|
||||
}
|
||||
|
||||
sb.append(" }\n");
|
||||
}
|
||||
|
||||
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Color passthrough (no lighting)\n");
|
||||
if (key.hasVertexColor()) {
|
||||
sb.append(" v_Color = a_Color;\n");
|
||||
} else {
|
||||
sb.append(" v_Color = u_CurrentColor;\n");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
|
||||
if (key.texGenEnabled()) {
|
||||
emitTexGenCoordGeneration(sb, key);
|
||||
} else if (key.textureEnabled() || key.hasVertexTexCoord()) {
|
||||
sb.append(" // Texture coordinates\n");
|
||||
if (key.hasVertexTexCoord()) {
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0);\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord0 = vec4(a_TexCoord0, 0.0, 1.0);\n");
|
||||
}
|
||||
} else {
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
|
||||
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
|
||||
* Coordinates without texgen use vertex attrib or default (0,0,0,1).
|
||||
*/
|
||||
private static void emitTexGenCoordGeneration(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // TexGen coordinate generation\n");
|
||||
sb.append(" vec4 texGenCoord = vec4(0.0, 0.0, 0.0, 1.0);\n");
|
||||
|
||||
emitTexGenComponent(sb, key.texGenModeS(), "s", "S", key);
|
||||
emitTexGenComponent(sb, key.texGenModeT(), "t", "T", key);
|
||||
emitTexGenComponent(sb, key.texGenModeR(), "r", "R", key);
|
||||
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
|
||||
|
||||
// Always apply texture matrix when texgen is active (forced on in VertexKey)
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * texGenCoord;\n");
|
||||
}
|
||||
|
||||
private static void emitTexGenComponent(StringBuilder sb, int mode, String swizzle, String coordName, VertexKey key) {
|
||||
switch (mode) {
|
||||
case VertexKey.TG_OBJ_LINEAR ->
|
||||
sb.append(" texGenCoord.").append(swizzle).append(" = dot(pos4, u_TexGenObjPlane").append(coordName).append(");\n");
|
||||
case VertexKey.TG_EYE_LINEAR ->
|
||||
sb.append(" texGenCoord.").append(swizzle).append(" = dot(eyePos, u_TexGenEyePlane").append(coordName).append(");\n");
|
||||
// TG_NONE: keep default (0 for s/t/r, 1 for q) — already set in texGenCoord init
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean texGenNeedsEyePos(VertexKey key) {
|
||||
return key.texGenModeS() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeT() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeR() == VertexKey.TG_EYE_LINEAR
|
||||
|| key.texGenModeQ() == VertexKey.TG_EYE_LINEAR;
|
||||
}
|
||||
|
||||
private static void emitFogDistance(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Fog distance\n");
|
||||
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
|
||||
final int fogDistMode = key.fogDistanceMode();
|
||||
switch (fogDistMode) {
|
||||
case 0 -> // FDM_EYE_RADIAL: Euclidean distance
|
||||
sb.append(" v_FogCoord = length(eyePos.xyz);\n");
|
||||
case 1 -> // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
|
||||
sb.append(" v_FogCoord = eyePos.z;\n");
|
||||
case 2 -> // FDM_EYE_PLANE_ABS: absolute Z
|
||||
sb.append(" v_FogCoord = abs(eyePos.z);\n");
|
||||
default ->
|
||||
sb.append(" v_FogCoord = abs(eyePos.z);\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitClipDistances(StringBuilder sb) {
|
||||
sb.append(" // Clip distances\n");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = dot(u_ClipPlane[").append(i).append("], eyePos);\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface DeferredAlphaHandler {
|
||||
boolean isAlphaTestLocked();
|
||||
void deferAlphaTestToggle(boolean enabled);
|
||||
void deferAlphaFunc(int function, float reference);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface DeferredBlendHandler {
|
||||
boolean isBlendLocked();
|
||||
void deferBlendModeToggle(boolean enabled);
|
||||
void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha);
|
||||
void flushDeferredBlend();
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface DeferredDepthColorHandler {
|
||||
boolean isDepthColorLocked();
|
||||
void deferDepthEnable(boolean enabled);
|
||||
void deferColorMask(boolean r, boolean g, boolean b, boolean a);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public final class GLSMConfig {
|
||||
|
||||
// Lightmap (replaces OpenGlHelper.lastBrightnessX/Y)
|
||||
public static float lastBrightnessX;
|
||||
public static float lastBrightnessY;
|
||||
|
||||
public static boolean hudCacheOverride;
|
||||
|
||||
private GLSMConfig() {}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.BlendFuncChangeEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.FogStateChangeEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.LightmapCoordsEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.ProgramChangeEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureBindEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureDeleteEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureUnitStateEvent;
|
||||
import net.minecraftforge.eventbus.api.bus.EventBus;
|
||||
|
||||
public final class GLSMHooks {
|
||||
|
||||
public static DeferredBlendHandler blendHandler;
|
||||
public static DeferredAlphaHandler alphaHandler;
|
||||
public static DeferredDepthColorHandler depthColorHandler;
|
||||
|
||||
// EventBus instances
|
||||
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
|
||||
public static final EventBus<TextureDeleteEvent> TEXTURE_DELETE = EventBus.create(TextureDeleteEvent.class);
|
||||
public static final EventBus<TextureUnitStateEvent> TEXTURE_UNIT_STATE = EventBus.create(TextureUnitStateEvent.class);
|
||||
public static final EventBus<ProgramChangeEvent> PROGRAM_CHANGE = EventBus.create(ProgramChangeEvent.class);
|
||||
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
|
||||
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
|
||||
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
|
||||
|
||||
// Reusable event instances
|
||||
public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
|
||||
public static final TextureDeleteEvent textureDeleteEvent = new TextureDeleteEvent();
|
||||
public static final TextureUnitStateEvent textureUnitStateEvent = new TextureUnitStateEvent();
|
||||
public static final ProgramChangeEvent programChangeEvent = new ProgramChangeEvent();
|
||||
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
|
||||
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
|
||||
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
|
||||
|
||||
private GLSMHooks() {}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class GLSMInitConfig {
|
||||
private final boolean lwjglDebug;
|
||||
private final StreamingUploader.UploadStrategy streamingUploadStrategy;
|
||||
private final boolean framebufferSupported;
|
||||
private final boolean fboEnabled;
|
||||
private final Consumer<DirectTessellator> directDrawer;
|
||||
private final Runnable streamingDrawerDestroy;
|
||||
private final int displayWidth;
|
||||
private final int displayHeight;
|
||||
private final Runnable postInitCallback;
|
||||
|
||||
private GLSMInitConfig(Builder builder) {
|
||||
this.lwjglDebug = builder.lwjglDebug;
|
||||
this.streamingUploadStrategy = builder.streamingUploadStrategy;
|
||||
this.framebufferSupported = builder.framebufferSupported;
|
||||
this.fboEnabled = builder.fboEnabled;
|
||||
this.directDrawer = builder.directDrawer;
|
||||
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
|
||||
this.displayWidth = builder.displayWidth;
|
||||
this.displayHeight = builder.displayHeight;
|
||||
this.postInitCallback = builder.postInitCallback;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public boolean isLwjglDebug() { return lwjglDebug; }
|
||||
public StreamingUploader.UploadStrategy getStreamingUploadStrategy() { return streamingUploadStrategy; }
|
||||
public boolean isFramebufferSupported() { return framebufferSupported; }
|
||||
public boolean isFboEnabled() { return fboEnabled; }
|
||||
public Consumer<DirectTessellator> getDirectDrawer() { return directDrawer; }
|
||||
public Runnable getStreamingDrawerDestroy() { return streamingDrawerDestroy; }
|
||||
public int getDisplayWidth() { return displayWidth; }
|
||||
public int getDisplayHeight() { return displayHeight; }
|
||||
public Runnable getPostInitCallback() { return postInitCallback; }
|
||||
|
||||
public static final class Builder {
|
||||
private boolean lwjglDebug = false;
|
||||
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
|
||||
private boolean framebufferSupported = false;
|
||||
private boolean fboEnabled = false;
|
||||
private Consumer<DirectTessellator> directDrawer = null;
|
||||
private Runnable streamingDrawerDestroy = null;
|
||||
private int displayWidth = 0;
|
||||
private int displayHeight = 0;
|
||||
private Runnable postInitCallback = null;
|
||||
|
||||
private Builder() {}
|
||||
|
||||
public Builder lwjglDebug(boolean lwjglDebug) {
|
||||
this.lwjglDebug = lwjglDebug;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder streamingUploadStrategy(StreamingUploader.UploadStrategy strategy) {
|
||||
this.streamingUploadStrategy = strategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder framebufferSupported(boolean framebufferSupported) {
|
||||
this.framebufferSupported = framebufferSupported;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fboEnabled(boolean fboEnabled) {
|
||||
this.fboEnabled = fboEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder directDrawer(Consumer<DirectTessellator> directDrawer) {
|
||||
this.directDrawer = directDrawer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
|
||||
this.streamingDrawerDestroy = streamingDrawerDestroy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder displaySize(int width, int height) {
|
||||
this.displayWidth = width;
|
||||
this.displayHeight = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder postInitCallback(Runnable callback) {
|
||||
this.postInitCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GLSMInitConfig build() {
|
||||
return new GLSMInitConfig(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class BlendFuncChangeEvent extends MutableEvent {
|
||||
public int srcRgb;
|
||||
public int dstRgb;
|
||||
public int srcAlpha;
|
||||
public int dstAlpha;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
/** FogState changed - Listeners should query GLSM fog state directly if they care what changed. */
|
||||
public final class FogStateChangeEvent extends MutableEvent {}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class LightmapCoordsEvent extends MutableEvent {
|
||||
public float x;
|
||||
public float y;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class ProgramChangeEvent extends MutableEvent {
|
||||
public int previousProgram;
|
||||
public int newProgram;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class TextureBindEvent extends MutableEvent {
|
||||
public int textureId;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class TextureDeleteEvent extends MutableEvent {
|
||||
public int textureId;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class TextureUnitStateEvent extends MutableEvent {
|
||||
public int unit;
|
||||
public boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an accumulated draw call during display list compilation.
|
||||
* Matrix-as-Data Architecture: Stores untransformed vertices + captured matrix.
|
||||
* The matrix will be applied at runtime, not baked into vertices.
|
||||
*/
|
||||
public final class AccumulatedDraw {
|
||||
public final VertexFormat format;
|
||||
public final List<ByteBuffer> drawBuffers;
|
||||
public final int drawMode;
|
||||
public final int commandIndex; // Position in command list for state tracking
|
||||
public final int stateGeneration; // Position in command list for state tracking
|
||||
public RestoreData restoreData;
|
||||
|
||||
public AccumulatedDraw(DirectTessellator tessellator, int commandIndex, int stateGeneration, boolean copyLast) {
|
||||
this.format = tessellator.getVertexFormat();
|
||||
this.drawMode = tessellator.drawMode;
|
||||
this.drawBuffers = new ArrayList<>();
|
||||
this.drawBuffers.add(tessellator.allocateBufferCopy());
|
||||
this.commandIndex = commandIndex;
|
||||
this.stateGeneration = stateGeneration;
|
||||
|
||||
if (copyLast) {
|
||||
this.restoreData = new RestoreData(tessellator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void mergeDraw(DirectTessellator tessellator, boolean copyLast) {
|
||||
this.drawBuffers.add(tessellator.allocateBufferCopy());
|
||||
|
||||
if (copyLast) {
|
||||
this.restoreData = new RestoreData(tessellator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds last vertex attribute values for GL state restoration after VBO draw.
|
||||
* Used to sync GLSM cache with actual GL state after VBO rendering.
|
||||
*/
|
||||
public static final class RestoreData {
|
||||
public final int lastColor;
|
||||
public final int lastNormal;
|
||||
public final float lastTexCoordU;
|
||||
public final float lastTexCoordV;
|
||||
public final int flags;
|
||||
|
||||
public RestoreData(DirectTessellator tessellator) {
|
||||
this.lastColor = tessellator.getPackedColor();
|
||||
this.lastNormal = tessellator.getPackedNormal();
|
||||
this.lastTexCoordU = (float) tessellator.getLastTextureU();
|
||||
this.lastTexCoordV = (float) tessellator.getLastTextureV();
|
||||
this.flags = tessellator.getVertexFormat().getVertexFlags();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,784 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
|
||||
public final class CommandBuffer {
|
||||
private static final int DEFAULT_CAPACITY = 4096; // Guess at typical display list size
|
||||
|
||||
private ByteBuffer buffer;
|
||||
private long basePointer;
|
||||
private long writePointer;
|
||||
private final List<Object> complexObjects = new ArrayList<>();
|
||||
|
||||
public CommandBuffer() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
public CommandBuffer(int initialCapacity) {
|
||||
this.buffer = memAlloc(initialCapacity);
|
||||
this.basePointer = memAddress(buffer);
|
||||
this.writePointer = basePointer;
|
||||
}
|
||||
|
||||
// === Low-level write operations ===
|
||||
|
||||
private void writeInt(int value) {
|
||||
memPutInt(writePointer, value);
|
||||
writePointer += 4;
|
||||
}
|
||||
|
||||
private void writeLong(long value) {
|
||||
memPutLong(writePointer, value);
|
||||
writePointer += 8;
|
||||
}
|
||||
|
||||
private void writeFloat(float value) {
|
||||
memPutFloat(writePointer, value);
|
||||
writePointer += 4;
|
||||
}
|
||||
|
||||
private void writeDouble(double value) {
|
||||
memPutDouble(writePointer, value);
|
||||
writePointer += 8;
|
||||
}
|
||||
|
||||
private void ensureCapacity(int needed) {
|
||||
final int size = size();
|
||||
final int remaining = buffer.capacity() - size;
|
||||
if (remaining < needed) {
|
||||
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
|
||||
buffer = memRealloc(buffer, newCapacity);
|
||||
basePointer = memAddress(buffer);
|
||||
writePointer = basePointer + size;
|
||||
}
|
||||
}
|
||||
|
||||
// === Single int commands ===
|
||||
|
||||
public void writeEnable(int cap) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.ENABLE);
|
||||
writeInt(cap);
|
||||
}
|
||||
|
||||
public void writeDisable(int cap) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DISABLE);
|
||||
writeInt(cap);
|
||||
}
|
||||
|
||||
public void writeClear(int mask) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CLEAR);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeClearStencil(int s) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CLEAR_STENCIL);
|
||||
writeInt(s);
|
||||
}
|
||||
|
||||
public void writeCullFace(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CULL_FACE);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeDepthFunc(int func) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DEPTH_FUNC);
|
||||
writeInt(func);
|
||||
}
|
||||
|
||||
public void writeShadeModel(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.SHADE_MODEL);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeLogicOp(int opcode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.LOGIC_OP);
|
||||
writeInt(opcode);
|
||||
}
|
||||
|
||||
public void writeMatrixMode(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.MATRIX_MODE);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeActiveTexture(int texture) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.ACTIVE_TEXTURE);
|
||||
writeInt(texture);
|
||||
}
|
||||
|
||||
public void writeUseProgram(int program) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.USE_PROGRAM);
|
||||
writeInt(program);
|
||||
}
|
||||
|
||||
public void writePushAttrib(int mask) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.PUSH_ATTRIB);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writePopAttrib() {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.POP_ATTRIB);
|
||||
writeInt(0); // unused padding
|
||||
}
|
||||
|
||||
public void writeLoadIdentity() {
|
||||
ensureCapacity(4);
|
||||
writeInt(GLCommand.LOAD_IDENTITY);
|
||||
}
|
||||
|
||||
public void writePushMatrix() {
|
||||
ensureCapacity(4);
|
||||
writeInt(GLCommand.PUSH_MATRIX);
|
||||
}
|
||||
|
||||
public void writePopMatrix() {
|
||||
ensureCapacity(4);
|
||||
writeInt(GLCommand.POP_MATRIX);
|
||||
}
|
||||
|
||||
public void writeStencilMask(int mask) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.STENCIL_MASK);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeDepthMask(boolean flag) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DEPTH_MASK);
|
||||
writeInt(flag ? 1 : 0);
|
||||
}
|
||||
|
||||
public void writeFrontFace(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.FRONT_FACE);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
// === Two int commands ===
|
||||
|
||||
public void writeBindTexture(int target, int texture) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.BIND_TEXTURE);
|
||||
writeInt(target);
|
||||
writeInt(texture);
|
||||
}
|
||||
|
||||
public void writePolygonMode(int face, int mode) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.POLYGON_MODE);
|
||||
writeInt(face);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeColorMaterial(int face, int mode) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.COLOR_MATERIAL);
|
||||
writeInt(face);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
public void writeLineStipple(int factor, int pattern) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.LINE_STIPPLE);
|
||||
writeInt(factor);
|
||||
writeInt(pattern);
|
||||
}
|
||||
|
||||
public void writeStencilMaskSeparate(int face, int mask) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
|
||||
writeInt(face);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeFogi(int pname, int param) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.FOGI);
|
||||
writeInt(pname);
|
||||
writeInt(param);
|
||||
}
|
||||
|
||||
public void writeHint(int target, int mode) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.HINT);
|
||||
writeInt(target);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
// === Three int commands ===
|
||||
|
||||
public void writeStencilFunc(int func, int ref, int mask) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.STENCIL_FUNC);
|
||||
writeInt(func);
|
||||
writeInt(ref);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeStencilOp(int fail, int zfail, int zpass) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.STENCIL_OP);
|
||||
writeInt(fail);
|
||||
writeInt(zfail);
|
||||
writeInt(zpass);
|
||||
}
|
||||
|
||||
public void writeTexParameteri(int target, int pname, int param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.TEX_PARAMETERI);
|
||||
writeInt(target);
|
||||
writeInt(pname);
|
||||
writeInt(param);
|
||||
}
|
||||
|
||||
// === Four int commands ===
|
||||
|
||||
public void writeViewport(int x, int y, int width, int height) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.VIEWPORT);
|
||||
writeInt(x);
|
||||
writeInt(y);
|
||||
writeInt(width);
|
||||
writeInt(height);
|
||||
}
|
||||
|
||||
public void writeScissor(int x, int y, int width, int height) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.SCISSOR);
|
||||
writeInt(x);
|
||||
writeInt(y);
|
||||
writeInt(width);
|
||||
writeInt(height);
|
||||
}
|
||||
|
||||
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.BLEND_FUNC);
|
||||
writeInt(srcRgb);
|
||||
writeInt(dstRgb);
|
||||
writeInt(srcAlpha);
|
||||
writeInt(dstAlpha);
|
||||
}
|
||||
|
||||
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.COLOR_MASK);
|
||||
writeInt(red ? 1 : 0);
|
||||
writeInt(green ? 1 : 0);
|
||||
writeInt(blue ? 1 : 0);
|
||||
writeInt(alpha ? 1 : 0);
|
||||
}
|
||||
|
||||
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
|
||||
writeInt(face);
|
||||
writeInt(func);
|
||||
writeInt(ref);
|
||||
writeInt(mask);
|
||||
}
|
||||
|
||||
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.STENCIL_OP_SEPARATE);
|
||||
writeInt(face);
|
||||
writeInt(sfail);
|
||||
writeInt(dpfail);
|
||||
writeInt(dppass);
|
||||
}
|
||||
|
||||
// === Float commands ===
|
||||
|
||||
public void writePointSize(float size) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.POINT_SIZE);
|
||||
writeFloat(size);
|
||||
}
|
||||
|
||||
public void writeLineWidth(float width) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.LINE_WIDTH);
|
||||
writeFloat(width);
|
||||
}
|
||||
|
||||
public void writePolygonOffset(float factor, float units) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.POLYGON_OFFSET);
|
||||
writeFloat(factor);
|
||||
writeFloat(units);
|
||||
}
|
||||
|
||||
public void writeColor(float r, float g, float b, float a) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.COLOR);
|
||||
writeFloat(r);
|
||||
writeFloat(g);
|
||||
writeFloat(b);
|
||||
writeFloat(a);
|
||||
}
|
||||
|
||||
public void writeClearColor(float r, float g, float b, float a) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.CLEAR_COLOR);
|
||||
writeFloat(r);
|
||||
writeFloat(g);
|
||||
writeFloat(b);
|
||||
writeFloat(a);
|
||||
}
|
||||
|
||||
public void writeClearDepth(double depth) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.CLEAR_DEPTH);
|
||||
writeDouble(depth);
|
||||
}
|
||||
|
||||
public void writeBlendColor(float r, float g, float b, float a) {
|
||||
ensureCapacity(20);
|
||||
writeInt(GLCommand.BLEND_COLOR);
|
||||
writeFloat(r);
|
||||
writeFloat(g);
|
||||
writeFloat(b);
|
||||
writeFloat(a);
|
||||
}
|
||||
|
||||
// === Mixed int+float commands ===
|
||||
|
||||
public void writeAlphaFunc(int func, float ref) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.ALPHA_FUNC);
|
||||
writeInt(func);
|
||||
writeFloat(ref);
|
||||
}
|
||||
|
||||
public void writeFogf(int pname, float param) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.FOGF);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
public void writeLightf(int light, int pname, float param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.LIGHTF);
|
||||
writeInt(light);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
public void writeLightModelf(int pname, float param) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.LIGHT_MODELF);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
public void writeLighti(int light, int pname, int param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.LIGHTI);
|
||||
writeInt(light);
|
||||
writeInt(pname);
|
||||
writeInt(param);
|
||||
}
|
||||
|
||||
public void writeLightModeli(int pname, int param) {
|
||||
ensureCapacity(12);
|
||||
writeInt(GLCommand.LIGHT_MODELI);
|
||||
writeInt(pname);
|
||||
writeInt(param);
|
||||
}
|
||||
|
||||
public void writeMaterialf(int face, int pname, float param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.MATERIALF);
|
||||
writeInt(face);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
public void writeTexParameterf(int target, int pname, float param) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.TEX_PARAMETERF);
|
||||
writeInt(target);
|
||||
writeInt(pname);
|
||||
writeFloat(param);
|
||||
}
|
||||
|
||||
// === Double commands ===
|
||||
|
||||
public void writeTranslate(double x, double y, double z) {
|
||||
ensureCapacity(28);
|
||||
writeInt(GLCommand.TRANSLATE);
|
||||
writeDouble(x);
|
||||
writeDouble(y);
|
||||
writeDouble(z);
|
||||
}
|
||||
|
||||
public void writeRotate(double angle, double x, double y, double z) {
|
||||
ensureCapacity(36);
|
||||
writeInt(GLCommand.ROTATE);
|
||||
writeDouble(angle);
|
||||
writeDouble(x);
|
||||
writeDouble(y);
|
||||
writeDouble(z);
|
||||
}
|
||||
|
||||
public void writeScale(double x, double y, double z) {
|
||||
ensureCapacity(28);
|
||||
writeInt(GLCommand.SCALE);
|
||||
writeDouble(x);
|
||||
writeDouble(y);
|
||||
writeDouble(z);
|
||||
}
|
||||
|
||||
public void writeOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
ensureCapacity(52);
|
||||
writeInt(GLCommand.ORTHO);
|
||||
writeDouble(left);
|
||||
writeDouble(right);
|
||||
writeDouble(bottom);
|
||||
writeDouble(top);
|
||||
writeDouble(zNear);
|
||||
writeDouble(zFar);
|
||||
}
|
||||
|
||||
public void writeFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
ensureCapacity(52);
|
||||
writeInt(GLCommand.FRUSTUM);
|
||||
writeDouble(left);
|
||||
writeDouble(right);
|
||||
writeDouble(bottom);
|
||||
writeDouble(top);
|
||||
writeDouble(zNear);
|
||||
writeDouble(zFar);
|
||||
}
|
||||
|
||||
// === Matrix commands ===
|
||||
|
||||
public void writeMultMatrix(Matrix4f matrix) {
|
||||
ensureCapacity(68);
|
||||
writeInt(GLCommand.MULT_MATRIX);
|
||||
writeMatrix4f(matrix);
|
||||
}
|
||||
|
||||
public void writeLoadMatrix(Matrix4f matrix) {
|
||||
ensureCapacity(68);
|
||||
writeInt(GLCommand.LOAD_MATRIX);
|
||||
writeMatrix4f(matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
|
||||
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
|
||||
*/
|
||||
private void writeMatrix4f(Matrix4f m) {
|
||||
// Column 0
|
||||
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
|
||||
// Column 1
|
||||
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
|
||||
// Column 2
|
||||
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
|
||||
// Column 3
|
||||
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
|
||||
}
|
||||
|
||||
// === Buffer commands (inline 4 floats) ===
|
||||
|
||||
public void writeFog(int pname, FloatBuffer params) {
|
||||
final int count = Math.min(params.remaining(), 4);
|
||||
ensureCapacity(12 + count * 4);
|
||||
writeInt(GLCommand.FOG);
|
||||
writeInt(pname);
|
||||
writeInt(count);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeFloat(params.get(pos + i));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeLight(int light, int pname, FloatBuffer params) {
|
||||
final int count = Math.min(params.remaining(), 4);
|
||||
ensureCapacity(16 + count * 4);
|
||||
writeInt(GLCommand.LIGHT);
|
||||
writeInt(light);
|
||||
writeInt(pname);
|
||||
writeInt(count);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeFloat(params.get(pos + i));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeLightModel(int pname, FloatBuffer params) {
|
||||
final int count = Math.min(params.remaining(), 4);
|
||||
ensureCapacity(12 + count * 4);
|
||||
writeInt(GLCommand.LIGHT_MODEL);
|
||||
writeInt(pname);
|
||||
writeInt(count);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeFloat(params.get(pos + i));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeMaterial(int face, int pname, FloatBuffer params) {
|
||||
final int count = Math.min(params.remaining(), 4);
|
||||
ensureCapacity(16 + count * 4);
|
||||
writeInt(GLCommand.MATERIAL);
|
||||
writeInt(face);
|
||||
writeInt(pname);
|
||||
writeInt(count);
|
||||
final int pos = params.position();
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeFloat(params.get(pos + i));
|
||||
}
|
||||
}
|
||||
|
||||
public void writeClipPlane(int plane, double a, double b, double c, double d) {
|
||||
ensureCapacity(40);
|
||||
writeInt(GLCommand.CLIP_PLANE);
|
||||
writeInt(plane);
|
||||
writeDouble(a);
|
||||
writeDouble(b);
|
||||
writeDouble(c);
|
||||
writeDouble(d);
|
||||
}
|
||||
|
||||
// === Draw commands ===
|
||||
|
||||
public void writeDrawRange(int vboIndex) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DRAW_RANGE);
|
||||
writeInt(vboIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a DRAW_RANGE_RESTORE command that draws a VBO range and restores GL current state after.
|
||||
* Used for immediate mode VBOs to restore GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS.
|
||||
*
|
||||
* @param vboIndex Index into ownedVbos array
|
||||
*/
|
||||
public void writeDrawRangeRestore(
|
||||
int vboIndex, AccumulatedDraw.RestoreData restoreData
|
||||
) {
|
||||
ensureCapacity(28);
|
||||
writeInt(GLCommand.DRAW_RANGE_RESTORE);
|
||||
writeInt(vboIndex);
|
||||
writeInt(restoreData.flags);
|
||||
// Last color (4 bytes)
|
||||
writeInt(restoreData.lastColor);
|
||||
// Last normal (4 bytes)
|
||||
writeInt(restoreData.lastNormal);
|
||||
// Last texcoord (2 floats = 8 bytes)
|
||||
writeFloat(restoreData.lastTexCoordU);
|
||||
writeFloat(restoreData.lastTexCoordV);
|
||||
}
|
||||
|
||||
public void writeCallList(int listId) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.CALL_LIST);
|
||||
writeInt(listId);
|
||||
}
|
||||
|
||||
public void writeDrawArrays(int mode, int start, int count) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.DRAW_ARRAYS);
|
||||
writeInt(mode);
|
||||
writeInt(start);
|
||||
writeInt(count);
|
||||
}
|
||||
|
||||
public void writeDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
|
||||
ensureCapacity(24);
|
||||
writeInt(GLCommand.DRAW_ELEMENTS);
|
||||
writeInt(mode);
|
||||
writeInt(indices_count);
|
||||
writeInt(type);
|
||||
writeLong(indices_buffer_offset);
|
||||
}
|
||||
|
||||
public void writeBindVBO(int vbo) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.BIND_VBO);
|
||||
writeInt(vbo);
|
||||
}
|
||||
|
||||
public void writeBindVAO(int vao) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.BIND_VAO);
|
||||
writeInt(vao);
|
||||
}
|
||||
|
||||
public void writeDrawBuffer(int mode) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.DRAW_BUFFER);
|
||||
writeInt(mode);
|
||||
}
|
||||
|
||||
private static final int MAX_DRAW_BUFFERS = 8;
|
||||
// cmd (4) + count (4) + buffers (4 * MAX_DRAW_BUFFERS)
|
||||
private static final int DRAW_BUFFERS_SIZE = 4 + 4 + 4 * MAX_DRAW_BUFFERS;
|
||||
|
||||
public void writeDrawBuffers(int count, int buffer) {
|
||||
ensureCapacity(DRAW_BUFFERS_SIZE);
|
||||
writeInt(GLCommand.DRAW_BUFFERS);
|
||||
writeInt(count);
|
||||
writeInt(buffer);
|
||||
for (int i = 1; i < MAX_DRAW_BUFFERS; i++) {
|
||||
writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeDrawBuffers(int count, IntBuffer bufs) {
|
||||
ensureCapacity(DRAW_BUFFERS_SIZE);
|
||||
writeInt(GLCommand.DRAW_BUFFERS);
|
||||
writeInt(count);
|
||||
final int pos = bufs.position();
|
||||
for (int i = 0; i < count && i < MAX_DRAW_BUFFERS; i++) {
|
||||
writeInt(bufs.get(pos + i));
|
||||
}
|
||||
for (int i = count; i < MAX_DRAW_BUFFERS; i++) {
|
||||
writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// === Complex object reference ===
|
||||
|
||||
public void writeComplexRef(DisplayListCommand cmd) {
|
||||
ensureCapacity(8);
|
||||
final int index = complexObjects.size();
|
||||
complexObjects.add(cmd);
|
||||
writeInt(GLCommand.COMPLEX_REF);
|
||||
writeInt(index);
|
||||
}
|
||||
|
||||
// === Reading methods for optimization pass ===
|
||||
|
||||
private long readPointer = 0;
|
||||
|
||||
/**
|
||||
* Reset read position to the beginning of the buffer.
|
||||
*/
|
||||
public void resetRead() {
|
||||
readPointer = basePointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's more data to read.
|
||||
*/
|
||||
public boolean hasRemaining() {
|
||||
return readPointer < writePointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an int from the current position.
|
||||
*/
|
||||
public int readInt() {
|
||||
final int value = memGetInt(readPointer);
|
||||
readPointer += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
public long readLong() {
|
||||
final long value = memGetLong(readPointer);
|
||||
readPointer += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a float from the current position.
|
||||
*/
|
||||
public float readFloat() {
|
||||
final float value = memGetFloat(readPointer);
|
||||
readPointer += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a double from the current position.
|
||||
*/
|
||||
public double readDouble() {
|
||||
final double value = memGetDouble(readPointer);
|
||||
readPointer += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Matrix4f from the current position (16 floats in column-major order).
|
||||
*/
|
||||
public Matrix4f readMatrix4f(Matrix4f dest) {
|
||||
dest.m00(readFloat()); dest.m01(readFloat()); dest.m02(readFloat()); dest.m03(readFloat());
|
||||
dest.m10(readFloat()); dest.m11(readFloat()); dest.m12(readFloat()); dest.m13(readFloat());
|
||||
dest.m20(readFloat()); dest.m21(readFloat()); dest.m22(readFloat()); dest.m23(readFloat());
|
||||
dest.m30(readFloat()); dest.m31(readFloat()); dest.m32(readFloat()); dest.m33(readFloat());
|
||||
return dest;
|
||||
}
|
||||
|
||||
// === Lifecycle ===
|
||||
|
||||
public int size() {
|
||||
return (int) (writePointer - basePointer);
|
||||
}
|
||||
|
||||
public long address() {
|
||||
return basePointer;
|
||||
}
|
||||
|
||||
public ByteBuffer toBuffer() {
|
||||
buffer.limit(size());
|
||||
buffer.position(0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public Object[] getComplexObjects() {
|
||||
return complexObjects.toArray();
|
||||
}
|
||||
|
||||
public Object getComplexObject(int index) {
|
||||
return complexObjects.get(index);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return size() == 0 && complexObjects.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the buffer for reuse. Does NOT free memory.
|
||||
*/
|
||||
public void clear() {
|
||||
writePointer = basePointer;
|
||||
complexObjects.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the underlying buffer. Must be called when done.
|
||||
*/
|
||||
public void free() {
|
||||
if (buffer != null) {
|
||||
memFree(buffer);
|
||||
buffer = null;
|
||||
basePointer = 0;
|
||||
writePointer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+561
@@ -0,0 +1,561 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
|
||||
/**
|
||||
* Executes commands from a CommandBuffer
|
||||
*/
|
||||
public final class CommandBufferExecutor {
|
||||
private CommandBufferExecutor() {}
|
||||
|
||||
// Static reusable buffer for matrix ops (render thread only, LWJGL2/3 compatible)
|
||||
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
|
||||
// Static reusable buffer for fog/light/material params
|
||||
private static final FloatBuffer PARAMS_BUFFER = BufferUtils.createFloatBuffer(4);
|
||||
// Static reusable buffer for clip plane equation
|
||||
private static final DoubleBuffer PARAMS_DOUBLE_BUFFER = BufferUtils.createDoubleBuffer(4);
|
||||
// Static reusable buffer for draw buffers
|
||||
private static final int MAX_DRAW_BUFFERS = 8;
|
||||
private static final IntBuffer DRAW_BUFFERS_BUFFER = BufferUtils.createIntBuffer(MAX_DRAW_BUFFERS);
|
||||
|
||||
/**
|
||||
* Execute all commands in the buffer.
|
||||
*
|
||||
* @param buffer The command buffer (position should be at 0, limit at size)
|
||||
* @param complexObjects Array of complex objects (TexImage2DCmd, etc.)
|
||||
* @param ownedVbos Array of VBOs owned by the display list (indexed by DrawRange commands)
|
||||
*/
|
||||
public static void execute(ByteBuffer buffer, Object[] complexObjects, DisplayListVBO ownedVbos) {
|
||||
long ptr = memAddress(buffer);
|
||||
final long end = ptr + buffer.limit();
|
||||
|
||||
while (ptr < end) {
|
||||
final int cmd = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
|
||||
switch (cmd) {
|
||||
// === Single int commands ===
|
||||
case GLCommand.ENABLE -> {
|
||||
GLStateManager.glEnable(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.DISABLE -> {
|
||||
GLStateManager.glDisable(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.CLEAR -> {
|
||||
GLStateManager.glClear(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.CLEAR_STENCIL -> {
|
||||
GLStateManager.glClearStencil(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.CULL_FACE -> {
|
||||
GLStateManager.glCullFace(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.DEPTH_FUNC -> {
|
||||
GLStateManager.glDepthFunc(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.SHADE_MODEL -> {
|
||||
GLStateManager.glShadeModel(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.LOGIC_OP -> {
|
||||
GLStateManager.glLogicOp(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.MATRIX_MODE -> {
|
||||
GLStateManager.glMatrixMode(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.ACTIVE_TEXTURE -> {
|
||||
GLStateManager.glActiveTexture(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.USE_PROGRAM -> {
|
||||
GLStateManager.glUseProgram(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.PUSH_ATTRIB -> {
|
||||
GLStateManager.glPushAttrib(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.POP_ATTRIB -> {
|
||||
GLStateManager.glPopAttrib();
|
||||
ptr += 4; // Skip unused padding
|
||||
}
|
||||
case GLCommand.LOAD_IDENTITY -> {
|
||||
GLStateManager.glLoadIdentity();
|
||||
}
|
||||
case GLCommand.PUSH_MATRIX -> {
|
||||
GLStateManager.glPushMatrix();
|
||||
}
|
||||
case GLCommand.POP_MATRIX -> {
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
case GLCommand.STENCIL_MASK -> {
|
||||
GLStateManager.glStencilMask(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.DEPTH_MASK -> {
|
||||
GLStateManager.glDepthMask(memGetInt(ptr) != 0);
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.FRONT_FACE -> {
|
||||
GLStateManager.glFrontFace(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
// === Two int commands ===
|
||||
case GLCommand.BIND_TEXTURE -> {
|
||||
final int target = memGetInt(ptr);
|
||||
final int texture = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
}
|
||||
case GLCommand.POLYGON_MODE -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int mode = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glPolygonMode(face, mode);
|
||||
}
|
||||
case GLCommand.COLOR_MATERIAL -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int mode = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glColorMaterial(face, mode);
|
||||
}
|
||||
case GLCommand.LINE_STIPPLE -> {
|
||||
final int factor = memGetInt(ptr);
|
||||
final int pattern = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glLineStipple(factor, (short) pattern);
|
||||
}
|
||||
case GLCommand.STENCIL_MASK_SEPARATE -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int mask = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glStencilMaskSeparate(face, mask);
|
||||
}
|
||||
case GLCommand.FOGI -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final int param = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glFogi(pname, param);
|
||||
}
|
||||
case GLCommand.HINT -> {
|
||||
final int target = memGetInt(ptr);
|
||||
final int mode = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glHint(target, mode);
|
||||
}
|
||||
|
||||
// === Three int commands ===
|
||||
case GLCommand.STENCIL_FUNC -> {
|
||||
final int func = memGetInt(ptr);
|
||||
final int ref = memGetInt(ptr + 4);
|
||||
final int mask = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glStencilFunc(func, ref, mask);
|
||||
}
|
||||
case GLCommand.STENCIL_OP -> {
|
||||
final int fail = memGetInt(ptr);
|
||||
final int zfail = memGetInt(ptr + 4);
|
||||
final int zpass = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glStencilOp(fail, zfail, zpass);
|
||||
}
|
||||
case GLCommand.TEX_PARAMETERI -> {
|
||||
final int target = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final int param = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glTexParameteri(target, pname, param);
|
||||
}
|
||||
|
||||
// === Four int commands ===
|
||||
case GLCommand.VIEWPORT -> {
|
||||
final int x = memGetInt(ptr);
|
||||
final int y = memGetInt(ptr + 4);
|
||||
final int width = memGetInt(ptr + 8);
|
||||
final int height = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
case GLCommand.SCISSOR -> {
|
||||
final int x = memGetInt(ptr);
|
||||
final int y = memGetInt(ptr + 4);
|
||||
final int width = memGetInt(ptr + 8);
|
||||
final int height = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glScissor(x, y, width, height);
|
||||
}
|
||||
case GLCommand.BLEND_FUNC -> {
|
||||
final int srcRgb = memGetInt(ptr);
|
||||
final int dstRgb = memGetInt(ptr + 4);
|
||||
final int srcAlpha = memGetInt(ptr + 8);
|
||||
final int dstAlpha = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
if (srcRgb == srcAlpha && dstRgb == dstAlpha) {
|
||||
GLStateManager.glBlendFunc(srcRgb, dstRgb);
|
||||
} else {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
}
|
||||
}
|
||||
case GLCommand.COLOR_MASK -> {
|
||||
final boolean r = memGetInt(ptr) != 0;
|
||||
final boolean g = memGetInt(ptr + 4) != 0;
|
||||
final boolean b = memGetInt(ptr + 8) != 0;
|
||||
final boolean a = memGetInt(ptr + 12) != 0;
|
||||
ptr += 16;
|
||||
GLStateManager.glColorMask(r, g, b, a);
|
||||
}
|
||||
case GLCommand.STENCIL_FUNC_SEPARATE -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int func = memGetInt(ptr + 4);
|
||||
final int ref = memGetInt(ptr + 8);
|
||||
final int mask = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glStencilFuncSeparate(face, func, ref, mask);
|
||||
}
|
||||
case GLCommand.STENCIL_OP_SEPARATE -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int sfail = memGetInt(ptr + 4);
|
||||
final int dpfail = memGetInt(ptr + 8);
|
||||
final int dppass = memGetInt(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glStencilOpSeparate(face, sfail, dpfail, dppass);
|
||||
}
|
||||
|
||||
// === Float commands ===
|
||||
case GLCommand.POINT_SIZE -> {
|
||||
GLStateManager.glPointSize(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.LINE_WIDTH -> {
|
||||
GLStateManager.glLineWidth(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.POLYGON_OFFSET -> {
|
||||
final float factor = memGetFloat(ptr);
|
||||
final float units = memGetFloat(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glPolygonOffset(factor, units);
|
||||
}
|
||||
case GLCommand.COLOR -> {
|
||||
final float r = memGetFloat(ptr);
|
||||
final float g = memGetFloat(ptr + 4);
|
||||
final float b = memGetFloat(ptr + 8);
|
||||
final float a = memGetFloat(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glColor4f(r, g, b, a);
|
||||
}
|
||||
case GLCommand.CLEAR_COLOR -> {
|
||||
final float r = memGetFloat(ptr);
|
||||
final float g = memGetFloat(ptr + 4);
|
||||
final float b = memGetFloat(ptr + 8);
|
||||
final float a = memGetFloat(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glClearColor(r, g, b, a);
|
||||
}
|
||||
case GLCommand.CLEAR_DEPTH -> {
|
||||
final double depth = memGetDouble(ptr);
|
||||
ptr += 8;
|
||||
GLStateManager.glClearDepth(depth);
|
||||
}
|
||||
case GLCommand.BLEND_COLOR -> {
|
||||
final float r = memGetFloat(ptr);
|
||||
final float g = memGetFloat(ptr + 4);
|
||||
final float b = memGetFloat(ptr + 8);
|
||||
final float a = memGetFloat(ptr + 12);
|
||||
ptr += 16;
|
||||
GLStateManager.glBlendColor(r, g, b, a);
|
||||
}
|
||||
|
||||
// === Mixed int+float commands ===
|
||||
case GLCommand.ALPHA_FUNC -> {
|
||||
final int func = memGetInt(ptr);
|
||||
final float ref = memGetFloat(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glAlphaFunc(func, ref);
|
||||
}
|
||||
case GLCommand.FOGF -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final float param = memGetFloat(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glFogf(pname, param);
|
||||
}
|
||||
case GLCommand.LIGHTF -> {
|
||||
final int light = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final float param = memGetFloat(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glLightf(light, pname, param);
|
||||
}
|
||||
case GLCommand.LIGHT_MODELF -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final float param = memGetFloat(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glLightModelf(pname, param);
|
||||
}
|
||||
case GLCommand.LIGHTI -> {
|
||||
final int light = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final int param = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glLighti(light, pname, param);
|
||||
}
|
||||
case GLCommand.LIGHT_MODELI -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final int param = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
GLStateManager.glLightModeli(pname, param);
|
||||
}
|
||||
case GLCommand.MATERIALF -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final float param = memGetFloat(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glMaterialf(face, pname, param);
|
||||
}
|
||||
case GLCommand.TEX_PARAMETERF -> {
|
||||
final int target = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final float param = memGetFloat(ptr + 8);
|
||||
ptr += 12;
|
||||
GLStateManager.glTexParameterf(target, pname, param);
|
||||
}
|
||||
|
||||
// === Double commands ===
|
||||
case GLCommand.TRANSLATE -> {
|
||||
final double x = memGetDouble(ptr);
|
||||
final double y = memGetDouble(ptr + 8);
|
||||
final double z = memGetDouble(ptr + 16);
|
||||
ptr += 24;
|
||||
GLStateManager.glTranslated(x, y, z);
|
||||
}
|
||||
case GLCommand.ROTATE -> {
|
||||
final double angle = memGetDouble(ptr);
|
||||
final double x = memGetDouble(ptr + 8);
|
||||
final double y = memGetDouble(ptr + 16);
|
||||
final double z = memGetDouble(ptr + 24);
|
||||
ptr += 32;
|
||||
GLStateManager.glRotated(angle, x, y, z);
|
||||
}
|
||||
case GLCommand.SCALE -> {
|
||||
final double x = memGetDouble(ptr);
|
||||
final double y = memGetDouble(ptr + 8);
|
||||
final double z = memGetDouble(ptr + 16);
|
||||
ptr += 24;
|
||||
GLStateManager.glScaled(x, y, z);
|
||||
}
|
||||
case GLCommand.ORTHO -> {
|
||||
final double left = memGetDouble(ptr);
|
||||
final double right = memGetDouble(ptr + 8);
|
||||
final double bottom = memGetDouble(ptr + 16);
|
||||
final double top = memGetDouble(ptr + 24);
|
||||
final double zNear = memGetDouble(ptr + 32);
|
||||
final double zFar = memGetDouble(ptr + 40);
|
||||
ptr += 48;
|
||||
GLStateManager.glOrtho(left, right, bottom, top, zNear, zFar);
|
||||
}
|
||||
case GLCommand.FRUSTUM -> {
|
||||
final double left = memGetDouble(ptr);
|
||||
final double right = memGetDouble(ptr + 8);
|
||||
final double bottom = memGetDouble(ptr + 16);
|
||||
final double top = memGetDouble(ptr + 24);
|
||||
final double zNear = memGetDouble(ptr + 32);
|
||||
final double zFar = memGetDouble(ptr + 40);
|
||||
ptr += 48;
|
||||
GLStateManager.glFrustum(left, right, bottom, top, zNear, zFar);
|
||||
}
|
||||
|
||||
// === Matrix commands ===
|
||||
case GLCommand.MULT_MATRIX -> {
|
||||
// Mode-agnostic: just multiply current matrix
|
||||
MATRIX_BUFFER.clear();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
MATRIX_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
MATRIX_BUFFER.flip();
|
||||
GLStateManager.glMultMatrix(MATRIX_BUFFER);
|
||||
}
|
||||
case GLCommand.LOAD_MATRIX -> {
|
||||
MATRIX_BUFFER.clear();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
MATRIX_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
MATRIX_BUFFER.flip();
|
||||
GLStateManager.glLoadMatrix(MATRIX_BUFFER);
|
||||
}
|
||||
|
||||
// === Buffer commands ===
|
||||
case GLCommand.FOG -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final int count = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
PARAMS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PARAMS_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
PARAMS_BUFFER.flip();
|
||||
GLStateManager.glFog(pname, PARAMS_BUFFER);
|
||||
}
|
||||
case GLCommand.LIGHT -> {
|
||||
final int light = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final int count = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
PARAMS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PARAMS_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
PARAMS_BUFFER.flip();
|
||||
GLStateManager.glLight(light, pname, PARAMS_BUFFER);
|
||||
}
|
||||
case GLCommand.LIGHT_MODEL -> {
|
||||
final int pname = memGetInt(ptr);
|
||||
final int count = memGetInt(ptr + 4);
|
||||
ptr += 8;
|
||||
PARAMS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PARAMS_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
PARAMS_BUFFER.flip();
|
||||
GLStateManager.glLightModel(pname, PARAMS_BUFFER);
|
||||
}
|
||||
case GLCommand.MATERIAL -> {
|
||||
final int face = memGetInt(ptr);
|
||||
final int pname = memGetInt(ptr + 4);
|
||||
final int count = memGetInt(ptr + 8);
|
||||
ptr += 12;
|
||||
PARAMS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PARAMS_BUFFER.put(memGetFloat(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
PARAMS_BUFFER.flip();
|
||||
GLStateManager.glMaterial(face, pname, PARAMS_BUFFER);
|
||||
}
|
||||
|
||||
case GLCommand.CLIP_PLANE -> {
|
||||
final int plane = memGetInt(ptr);
|
||||
final double a = memGetDouble(ptr + 4);
|
||||
final double b = memGetDouble(ptr + 12);
|
||||
final double c = memGetDouble(ptr + 20);
|
||||
final double d = memGetDouble(ptr + 28);
|
||||
ptr += 36;
|
||||
PARAMS_DOUBLE_BUFFER.clear();
|
||||
PARAMS_DOUBLE_BUFFER.put(a).put(b).put(c).put(d);
|
||||
PARAMS_DOUBLE_BUFFER.flip();
|
||||
GLStateManager.glClipPlane(plane, PARAMS_DOUBLE_BUFFER);
|
||||
}
|
||||
|
||||
// === Draw commands ===
|
||||
case GLCommand.DRAW_RANGE -> {
|
||||
final int vboIndex = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
ownedVbos.render(vboIndex);
|
||||
}
|
||||
case GLCommand.DRAW_RANGE_RESTORE -> {
|
||||
// Draw VBO range then restore GL current state from last vertex attributes
|
||||
final int vboIndex = memGetInt(ptr);
|
||||
final int flags = memGetInt(ptr + 4);
|
||||
|
||||
// Draw the VBO
|
||||
ownedVbos.render(vboIndex);
|
||||
|
||||
// Restore attributes based on flags
|
||||
if ((flags & VertexFlags.COLOR_BIT) != 0) {
|
||||
final int color = memGetInt(ptr + 8);
|
||||
final byte a = (byte) ((color >> 24) & 0xFF);
|
||||
final byte b = (byte) ((color >> 16) & 0xFF);
|
||||
final byte g = (byte) ((color >> 8) & 0xFF);
|
||||
final byte r = (byte) ((color & 0xFF));
|
||||
GLStateManager.glColor4ub(r, g, b, a);
|
||||
}
|
||||
if ((flags & VertexFlags.NORMAL_BIT) != 0) {
|
||||
final int normal = memGetInt(ptr + 12);
|
||||
float nx = (byte)(normal) / 127.0f;
|
||||
float ny = (byte)(normal >> 8) / 127.0f;
|
||||
float nz = (byte)(normal >> 16) / 127.0f;
|
||||
GLStateManager.glNormal3f(nx, ny, nz);
|
||||
}
|
||||
if ((flags & VertexFlags.TEXTURE_BIT) != 0) {
|
||||
final float s = memGetFloat(ptr + 16);
|
||||
final float t = memGetFloat(ptr + 20);
|
||||
GLStateManager.glTexCoord2f(s, t);
|
||||
}
|
||||
ptr += 24; // Skip full command size (28 - 4 for cmd already read)
|
||||
}
|
||||
case GLCommand.CALL_LIST -> {
|
||||
final int listId = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
DisplayListManager.glCallList(listId);
|
||||
}
|
||||
case GLCommand.DRAW_BUFFER -> {
|
||||
GLStateManager.glDrawBuffer(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.DRAW_BUFFERS -> {
|
||||
final int count = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
DRAW_BUFFERS_BUFFER.clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
DRAW_BUFFERS_BUFFER.put(memGetInt(ptr + i * 4));
|
||||
}
|
||||
DRAW_BUFFERS_BUFFER.flip();
|
||||
ptr += 4 * MAX_DRAW_BUFFERS;
|
||||
GLStateManager.glDrawBuffers(DRAW_BUFFERS_BUFFER);
|
||||
}
|
||||
case GLCommand.DRAW_ARRAYS -> {
|
||||
GLStateManager.glDrawArrays(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8));
|
||||
ptr += 12;
|
||||
}
|
||||
case GLCommand.DRAW_ELEMENTS -> {
|
||||
GLStateManager.glDrawElements(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8), memGetLong(ptr + 12));
|
||||
ptr += 20;
|
||||
}
|
||||
case GLCommand.BIND_VBO -> {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.BIND_VAO -> {
|
||||
GLStateManager.glBindVertexArray(memGetInt(ptr));
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
// === Complex object reference ===
|
||||
case GLCommand.COMPLEX_REF -> {
|
||||
final int index = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
((DisplayListCommand) complexObjects[index]).execute();
|
||||
}
|
||||
|
||||
default -> throw new IllegalStateException("Unknown command opcode: " + cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public final class CommandBufferProcessor {
|
||||
private static final Matrix4f tempMatrix = new Matrix4f();
|
||||
|
||||
private CommandBufferProcessor() {}
|
||||
|
||||
/**
|
||||
* Copy a complete command (opcode already read) from raw to final buffer.
|
||||
*/
|
||||
public static void copyCommand(CommandBuffer raw, CommandBuffer out) {
|
||||
final int opcode = raw.readInt();
|
||||
copyCommandData(opcode, raw, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy command data based on opcode (opcode already consumed from raw).
|
||||
*/
|
||||
public static void copyCommandData(int opcode, CommandBuffer raw, CommandBuffer out) {
|
||||
switch (opcode) {
|
||||
// Single int commands
|
||||
case GLCommand.ENABLE -> out.writeEnable(raw.readInt());
|
||||
case GLCommand.DISABLE -> out.writeDisable(raw.readInt());
|
||||
case GLCommand.CLEAR -> out.writeClear(raw.readInt());
|
||||
case GLCommand.CLEAR_STENCIL -> out.writeClearStencil(raw.readInt());
|
||||
case GLCommand.CULL_FACE -> out.writeCullFace(raw.readInt());
|
||||
case GLCommand.DEPTH_FUNC -> out.writeDepthFunc(raw.readInt());
|
||||
case GLCommand.SHADE_MODEL -> out.writeShadeModel(raw.readInt());
|
||||
case GLCommand.LOGIC_OP -> out.writeLogicOp(raw.readInt());
|
||||
case GLCommand.MATRIX_MODE -> out.writeMatrixMode(raw.readInt());
|
||||
case GLCommand.ACTIVE_TEXTURE -> out.writeActiveTexture(raw.readInt());
|
||||
case GLCommand.USE_PROGRAM -> out.writeUseProgram(raw.readInt());
|
||||
case GLCommand.PUSH_ATTRIB -> out.writePushAttrib(raw.readInt());
|
||||
case GLCommand.POP_ATTRIB -> { raw.readInt(); out.writePopAttrib(); }
|
||||
case GLCommand.LOAD_IDENTITY -> out.writeLoadIdentity();
|
||||
case GLCommand.PUSH_MATRIX -> out.writePushMatrix();
|
||||
case GLCommand.POP_MATRIX -> out.writePopMatrix();
|
||||
case GLCommand.STENCIL_MASK -> out.writeStencilMask(raw.readInt());
|
||||
case GLCommand.DEPTH_MASK -> out.writeDepthMask(raw.readInt() != 0);
|
||||
case GLCommand.FRONT_FACE -> out.writeFrontFace(raw.readInt());
|
||||
|
||||
// Two int commands
|
||||
case GLCommand.BIND_TEXTURE -> out.writeBindTexture(raw.readInt(), raw.readInt());
|
||||
case GLCommand.POLYGON_MODE -> out.writePolygonMode(raw.readInt(), raw.readInt());
|
||||
case GLCommand.COLOR_MATERIAL -> out.writeColorMaterial(raw.readInt(), raw.readInt());
|
||||
case GLCommand.LINE_STIPPLE -> out.writeLineStipple(raw.readInt(), raw.readInt());
|
||||
case GLCommand.STENCIL_MASK_SEPARATE -> out.writeStencilMaskSeparate(raw.readInt(), raw.readInt());
|
||||
case GLCommand.FOGI -> out.writeFogi(raw.readInt(), raw.readInt());
|
||||
case GLCommand.HINT -> out.writeHint(raw.readInt(), raw.readInt());
|
||||
|
||||
// Three int commands
|
||||
case GLCommand.STENCIL_FUNC -> out.writeStencilFunc(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.STENCIL_OP -> out.writeStencilOp(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.TEX_PARAMETERI -> out.writeTexParameteri(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
|
||||
// Four int commands
|
||||
case GLCommand.VIEWPORT -> out.writeViewport(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.SCISSOR -> out.writeScissor(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.BLEND_FUNC -> out.writeBlendFunc(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.COLOR_MASK -> out.writeColorMask(raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0);
|
||||
case GLCommand.STENCIL_FUNC_SEPARATE -> out.writeStencilFuncSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.STENCIL_OP_SEPARATE -> out.writeStencilOpSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
|
||||
|
||||
// Float commands
|
||||
case GLCommand.POINT_SIZE -> out.writePointSize(raw.readFloat());
|
||||
case GLCommand.LINE_WIDTH -> out.writeLineWidth(raw.readFloat());
|
||||
case GLCommand.POLYGON_OFFSET -> out.writePolygonOffset(raw.readFloat(), raw.readFloat());
|
||||
case GLCommand.COLOR -> out.writeColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
|
||||
case GLCommand.CLEAR_COLOR -> out.writeClearColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
|
||||
|
||||
// Single double commands
|
||||
case GLCommand.CLEAR_DEPTH -> out.writeClearDepth(raw.readDouble());
|
||||
|
||||
// Four float commands
|
||||
case GLCommand.BLEND_COLOR -> out.writeBlendColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
|
||||
|
||||
// Mixed int+float commands
|
||||
case GLCommand.ALPHA_FUNC -> out.writeAlphaFunc(raw.readInt(), raw.readFloat());
|
||||
case GLCommand.FOGF -> out.writeFogf(raw.readInt(), raw.readFloat());
|
||||
case GLCommand.LIGHTF -> out.writeLightf(raw.readInt(), raw.readInt(), raw.readFloat());
|
||||
case GLCommand.LIGHT_MODELF -> out.writeLightModelf(raw.readInt(), raw.readFloat());
|
||||
case GLCommand.LIGHTI -> out.writeLighti(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.LIGHT_MODELI -> out.writeLightModeli(raw.readInt(), raw.readInt());
|
||||
case GLCommand.MATERIALF -> out.writeMaterialf(raw.readInt(), raw.readInt(), raw.readFloat());
|
||||
case GLCommand.TEX_PARAMETERF -> out.writeTexParameterf(raw.readInt(), raw.readInt(), raw.readFloat());
|
||||
|
||||
// Double commands
|
||||
case GLCommand.TRANSLATE -> out.writeTranslate(raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
case GLCommand.ROTATE -> out.writeRotate(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
case GLCommand.SCALE -> out.writeScale(raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
case GLCommand.ORTHO -> out.writeOrtho(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
case GLCommand.FRUSTUM -> out.writeFrustum(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
|
||||
|
||||
// Matrix commands
|
||||
case GLCommand.MULT_MATRIX -> { out.writeMultMatrix(raw.readMatrix4f(tempMatrix)); }
|
||||
case GLCommand.LOAD_MATRIX -> { out.writeLoadMatrix(raw.readMatrix4f(tempMatrix)); }
|
||||
|
||||
// FloatBuffer commands (inline 4 floats max)
|
||||
case GLCommand.FOG -> {
|
||||
final int pname = raw.readInt();
|
||||
final int count = raw.readInt();
|
||||
final FloatBuffer fb = FloatBuffer.allocate(count);
|
||||
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
|
||||
fb.flip();
|
||||
out.writeFog(pname, fb);
|
||||
}
|
||||
case GLCommand.LIGHT -> {
|
||||
final int light = raw.readInt();
|
||||
final int pname = raw.readInt();
|
||||
final int count = raw.readInt();
|
||||
final FloatBuffer fb = FloatBuffer.allocate(count);
|
||||
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
|
||||
fb.flip();
|
||||
out.writeLight(light, pname, fb);
|
||||
}
|
||||
case GLCommand.LIGHT_MODEL -> {
|
||||
final int pname = raw.readInt();
|
||||
final int count = raw.readInt();
|
||||
final FloatBuffer fb = FloatBuffer.allocate(count);
|
||||
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
|
||||
fb.flip();
|
||||
out.writeLightModel(pname, fb);
|
||||
}
|
||||
case GLCommand.MATERIAL -> {
|
||||
final int face = raw.readInt();
|
||||
final int pname = raw.readInt();
|
||||
final int count = raw.readInt();
|
||||
final FloatBuffer fb = FloatBuffer.allocate(count);
|
||||
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
|
||||
fb.flip();
|
||||
out.writeMaterial(face, pname, fb);
|
||||
}
|
||||
|
||||
// Clip plane command (4 doubles)
|
||||
case GLCommand.CLIP_PLANE -> {
|
||||
final int plane = raw.readInt();
|
||||
final double a = raw.readDouble();
|
||||
final double b = raw.readDouble();
|
||||
final double c = raw.readDouble();
|
||||
final double d = raw.readDouble();
|
||||
out.writeClipPlane(plane, a, b, c, d);
|
||||
}
|
||||
|
||||
// Draw/call commands
|
||||
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt());
|
||||
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
|
||||
case GLCommand.DRAW_ARRAYS -> out.writeDrawArrays(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.DRAW_ELEMENTS -> out.writeDrawElements(raw.readInt(), raw.readInt(), raw.readInt(), raw.readLong());
|
||||
case GLCommand.BIND_VBO -> out.writeBindVBO(raw.readInt());
|
||||
case GLCommand.BIND_VAO -> out.writeBindVAO(raw.readInt());
|
||||
|
||||
// Complex object reference - transfer the object to output buffer
|
||||
case GLCommand.COMPLEX_REF -> {
|
||||
final int index = raw.readInt();
|
||||
final Object obj = raw.getComplexObject(index);
|
||||
out.writeComplexRef((DisplayListCommand) obj);
|
||||
}
|
||||
|
||||
default -> GLStateManager.LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* Records GL commands to a CommandBuffer during display list compilation.
|
||||
*/
|
||||
public final class CommandRecorder {
|
||||
private final CommandBuffer buffer;
|
||||
private int commandCount;
|
||||
|
||||
public CommandRecorder() {
|
||||
this.buffer = new CommandBuffer();
|
||||
this.commandCount = 0;
|
||||
}
|
||||
|
||||
public CommandBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public int getCommandCount() {
|
||||
return commandCount;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
buffer.free();
|
||||
}
|
||||
|
||||
// ==================== Recording Methods ====================
|
||||
|
||||
public void recordEnable(int cap) {
|
||||
buffer.writeEnable(cap);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDisable(int cap) {
|
||||
buffer.writeDisable(cap);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClear(int mask) {
|
||||
buffer.writeClear(mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClearColor(float r, float g, float b, float a) {
|
||||
buffer.writeClearColor(r, g, b, a);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClearDepth(double depth) {
|
||||
buffer.writeClearDepth(depth);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBlendColor(float r, float g, float b, float a) {
|
||||
buffer.writeBlendColor(r, g, b, a);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClearStencil(int s) {
|
||||
buffer.writeClearStencil(s);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordColor(float r, float g, float b, float a) {
|
||||
buffer.writeColor(r, g, b, a);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
|
||||
buffer.writeColorMask(r, g, b, a);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDepthMask(boolean flag) {
|
||||
buffer.writeDepthMask(flag);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFrontFace(int mode) {
|
||||
buffer.writeFrontFace(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDepthFunc(int func) {
|
||||
buffer.writeDepthFunc(func);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
buffer.writeBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordAlphaFunc(int func, float ref) {
|
||||
buffer.writeAlphaFunc(func, ref);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordCullFace(int mode) {
|
||||
buffer.writeCullFace(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordShadeModel(int mode) {
|
||||
buffer.writeShadeModel(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBindTexture(int target, int texture) {
|
||||
buffer.writeBindTexture(target, texture);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordTexParameteri(int target, int pname, int param) {
|
||||
buffer.writeTexParameteri(target, pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordTexParameterf(int target, int pname, float param) {
|
||||
buffer.writeTexParameterf(target, pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordMatrixMode(int mode) {
|
||||
buffer.writeMatrixMode(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLoadIdentity() {
|
||||
buffer.writeLoadIdentity();
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPushMatrix() {
|
||||
buffer.writePushMatrix();
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPopMatrix() {
|
||||
buffer.writePopMatrix();
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordMultMatrix(Matrix4f matrix) {
|
||||
buffer.writeMultMatrix(matrix);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLoadMatrix(Matrix4f matrix) {
|
||||
buffer.writeLoadMatrix(matrix);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
buffer.writeOrtho(left, right, bottom, top, zNear, zFar);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
|
||||
buffer.writeFrustum(left, right, bottom, top, zNear, zFar);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordViewport(int x, int y, int width, int height) {
|
||||
buffer.writeViewport(x, y, width, height);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordScissor(int x, int y, int width, int height) {
|
||||
buffer.writeScissor(x, y, width, height);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPointSize(float size) {
|
||||
buffer.writePointSize(size);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLineWidth(float width) {
|
||||
buffer.writeLineWidth(width);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLineStipple(int factor, int pattern) {
|
||||
buffer.writeLineStipple(factor, pattern);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPolygonOffset(float factor, float units) {
|
||||
buffer.writePolygonOffset(factor, units);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPolygonMode(int face, int mode) {
|
||||
buffer.writePolygonMode(face, mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordColorMaterial(int face, int mode) {
|
||||
buffer.writeColorMaterial(face, mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLogicOp(int opcode) {
|
||||
buffer.writeLogicOp(opcode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordActiveTexture(int texture) {
|
||||
buffer.writeActiveTexture(texture);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordUseProgram(int program) {
|
||||
buffer.writeUseProgram(program);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPushAttrib(int mask) {
|
||||
buffer.writePushAttrib(mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordPopAttrib() {
|
||||
buffer.writePopAttrib();
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFogf(int pname, float param) {
|
||||
buffer.writeFogf(pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFogi(int pname, int param) {
|
||||
buffer.writeFogi(pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordHint(int target, int mode) {
|
||||
buffer.writeHint(target, mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordFog(int pname, FloatBuffer params) {
|
||||
buffer.writeFog(pname, params);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLightf(int light, int pname, float param) {
|
||||
buffer.writeLightf(light, pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLighti(int light, int pname, int param) {
|
||||
buffer.writeLighti(light, pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLight(int light, int pname, FloatBuffer params) {
|
||||
buffer.writeLight(light, pname, params);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLightModelf(int pname, float param) {
|
||||
buffer.writeLightModelf(pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLightModeli(int pname, int param) {
|
||||
buffer.writeLightModeli(pname, param);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordLightModel(int pname, FloatBuffer params) {
|
||||
buffer.writeLightModel(pname, params);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordMaterialf(int face, int pname, float val) {
|
||||
buffer.writeMaterialf(face, pname, val);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordMaterial(int face, int pname, FloatBuffer params) {
|
||||
buffer.writeMaterial(face, pname, params);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordClipPlane(int plane, double a, double b, double c, double d) {
|
||||
buffer.writeClipPlane(plane, a, b, c, d);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilFunc(int func, int ref, int mask) {
|
||||
buffer.writeStencilFunc(func, ref, mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilFuncSeparate(int face, int func, int ref, int mask) {
|
||||
buffer.writeStencilFuncSeparate(face, func, ref, mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilOp(int fail, int zfail, int zpass) {
|
||||
buffer.writeStencilOp(fail, zfail, zpass);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
|
||||
buffer.writeStencilOpSeparate(face, sfail, dpfail, dppass);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilMask(int mask) {
|
||||
buffer.writeStencilMask(mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordStencilMaskSeparate(int face, int mask) {
|
||||
buffer.writeStencilMaskSeparate(face, mask);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordCallList(int listId) {
|
||||
buffer.writeCallList(listId);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawBuffer(int mode) {
|
||||
buffer.writeDrawBuffer(mode);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawBuffers(int count, int buf) {
|
||||
buffer.writeDrawBuffers(count, buf);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawBuffers(int count, IntBuffer bufs) {
|
||||
buffer.writeDrawBuffers(count, bufs);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawArrays(int mode, int start, int count) {
|
||||
buffer.writeDrawArrays(mode, start, count);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
|
||||
buffer.writeDrawElements(mode, indices_count, type, indices_buffer_offset);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBindVBO(int vbo) {
|
||||
buffer.writeBindVBO(vbo);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBindVAO(int vao) {
|
||||
buffer.writeBindVAO(vao);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordComplexCommand(DisplayListCommand cmd) {
|
||||
buffer.writeComplexRef(cmd);
|
||||
commandCount++;
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
|
||||
/**
|
||||
* Represents a compiled display list.
|
||||
*
|
||||
* <p>Contains commands serialized to a direct ByteBuffer for off-heap storage,
|
||||
* with complex objects (TexImage2DCmd, TexSubImage2DCmd) stored separately.
|
||||
*
|
||||
* <p>The ownedVbos array contains VBOs that are referenced by DrawRange commands.
|
||||
*/
|
||||
public final class CompiledDisplayList {
|
||||
/**
|
||||
* Empty display list singleton. Per OpenGL spec, an empty list is still valid.
|
||||
* This instance does nothing when rendered and is never deleted (shared singleton).
|
||||
*/
|
||||
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null);
|
||||
|
||||
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
|
||||
private final Object[] complexObjects; // Complex commands (TexImage2D, etc.)
|
||||
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
|
||||
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO ownedVbos) {
|
||||
this.commandBuffer = commandBuffer;
|
||||
this.complexObjects = complexObjects;
|
||||
this.ownedVbos = ownedVbos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this display list by executing all commands.
|
||||
*/
|
||||
public void render() {
|
||||
if (commandBuffer != null && commandBuffer.limit() > 0) {
|
||||
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all resources held by this display list.
|
||||
*/
|
||||
public void delete() {
|
||||
// Delete complex objects that hold resources
|
||||
if (complexObjects != null) {
|
||||
for (Object obj : complexObjects) {
|
||||
if (obj instanceof DisplayListCommand cmd) {
|
||||
cmd.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close VBOs
|
||||
if (ownedVbos != null) {
|
||||
ownedVbos.delete();
|
||||
}
|
||||
|
||||
// Free the command buffer (off-heap memory)
|
||||
if (commandBuffer != null) {
|
||||
memFree(commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command buffer for debugging/inspection.
|
||||
*/
|
||||
public ByteBuffer getCommandBuffer() {
|
||||
return commandBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complex objects for inlining into another display list (nested lists).
|
||||
*/
|
||||
public Object[] getComplexObjects() {
|
||||
return complexObjects;
|
||||
}
|
||||
|
||||
public DisplayListVBO getOwnedVbos() {
|
||||
return ownedVbos;
|
||||
}
|
||||
|
||||
// === Test inspection methods ===
|
||||
|
||||
/**
|
||||
* Get counts of each command type in the buffer.
|
||||
* Used for testing to verify optimization results.
|
||||
*/
|
||||
public Int2IntMap getCommandCounts() {
|
||||
final Int2IntMap counts = new Int2IntOpenHashMap();
|
||||
if (commandBuffer == null || commandBuffer.limit() == 0) {
|
||||
return counts;
|
||||
}
|
||||
long ptr = memAddress(commandBuffer);
|
||||
final long end = ptr + commandBuffer.limit();
|
||||
while (ptr < end) {
|
||||
final int cmd = memGetInt(ptr);
|
||||
counts.mergeInt(cmd, 1, Integer::sum);
|
||||
ptr += GLCommand.getCommandSize(cmd, ptr);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sequence of command opcodes in order.
|
||||
* Used for testing to verify command ordering.
|
||||
*/
|
||||
public IntList getCommandOpcodes() {
|
||||
final IntList opcodes = new IntArrayList();
|
||||
if (commandBuffer == null || commandBuffer.limit() == 0) {
|
||||
return opcodes;
|
||||
}
|
||||
long ptr = memAddress(commandBuffer);
|
||||
final long end = ptr + commandBuffer.limit();
|
||||
while (ptr < end) {
|
||||
final int cmd = memGetInt(ptr);
|
||||
opcodes.add(cmd);
|
||||
ptr += GLCommand.getCommandSize(cmd, ptr);
|
||||
}
|
||||
return opcodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return DisplayListManager.getCompiledDisplayListString(0, this, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
|
||||
/**
|
||||
* A class that stores multiple vertex formats & their corresponding buffers.
|
||||
*/
|
||||
public final class DisplayListVBO {
|
||||
|
||||
private final SubVBO[] vbos;
|
||||
|
||||
DisplayListVBO(SubVBO[] vbos) {
|
||||
this.vbos = vbos;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
for (SubVBO vbo : vbos) {
|
||||
vbo.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public SubVBO[] getVBOs() {
|
||||
return vbos;
|
||||
}
|
||||
|
||||
public void render(int index) {
|
||||
vbos[index].render();
|
||||
}
|
||||
|
||||
public static final class SubVBO {
|
||||
|
||||
private final IVertexArrayObject vao;
|
||||
private final int drawMode;
|
||||
private final int start;
|
||||
private final int count;
|
||||
|
||||
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count) {
|
||||
this.vao = vao;
|
||||
this.drawMode = drawMode;
|
||||
this.start = start;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
vao.delete();
|
||||
}
|
||||
|
||||
public void render() {
|
||||
vao.bind();
|
||||
vao.draw(drawMode, start, count);
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
public IVertexArrayObject getVAO() {
|
||||
return vao;
|
||||
}
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexedVAO;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.nmemFree;
|
||||
|
||||
/**
|
||||
* Compiles multiple ByteBuffers of the same Vertex format into a VBO. Any buffer added to the builder will be freed afterward.
|
||||
*/
|
||||
public final class DisplayListVBOBuilder {
|
||||
|
||||
// Removes the need for a HashMap. Since it's only an array of 16 values, this should be faster
|
||||
private final List<FormatData>[] formats = new List[VertexFlags.BITSET_SIZE];
|
||||
private int count;
|
||||
|
||||
public DisplayListVBOBuilder addDraw(VertexFormat format, int drawMode, List<ByteBuffer> buffers) {
|
||||
List<FormatData> data = formats[format.getVertexFlags()];
|
||||
if (data == null) {
|
||||
data = new ArrayList<>();
|
||||
formats[format.getVertexFlags()] = data;
|
||||
}
|
||||
data.add(new FormatData(buffers, drawMode, count));
|
||||
count++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayListVBOBuilder addDraws(List<AccumulatedDraw> draws) {
|
||||
for (AccumulatedDraw draw : draws) {
|
||||
addDraw(draw.format, draw.drawMode, draw.drawBuffers);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayListVBO build() {
|
||||
final DisplayListVBO.SubVBO[] vbos = new DisplayListVBO.SubVBO[count];
|
||||
final ArrayList<ByteBuffer> allBuffers = new ArrayList<>();
|
||||
for (int i = 0; i < VertexFlags.BITSET_SIZE; i++) {
|
||||
List<FormatData> formatData = formats[i];
|
||||
if (formatData == null) continue;
|
||||
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[i];
|
||||
int start = 0;
|
||||
final IVertexArrayObject vao = VAOManager.createStorageVAO(format, -1, 0); // drawMode will be ignored
|
||||
final IVertexBuffer vbo = vao.getVBO();
|
||||
for (FormatData data : formatData) {
|
||||
int vertexCount;
|
||||
final List<ByteBuffer> drawBuffers = data.buffers;
|
||||
if (drawBuffers.size() == 1) {
|
||||
final ByteBuffer buffer = drawBuffers.get(0);
|
||||
allBuffers.add(buffer);
|
||||
vertexCount = format.getVertexCount(buffer);
|
||||
} else {
|
||||
allBuffers.addAll(drawBuffers);
|
||||
int size = 0;
|
||||
for (ByteBuffer buffer : drawBuffers) {
|
||||
size += buffer.remaining();
|
||||
}
|
||||
vertexCount = format.getVertexCount(size);
|
||||
}
|
||||
if (data.drawMode == GL11.GL_QUADS) {
|
||||
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
|
||||
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6);
|
||||
} else {
|
||||
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount);
|
||||
}
|
||||
|
||||
|
||||
start += vertexCount;
|
||||
}
|
||||
ByteBuffer bigBuffer = mergeAndDelete(allBuffers);
|
||||
vbo.allocate(bigBuffer, start);
|
||||
memFree(bigBuffer);
|
||||
|
||||
allBuffers.clear();
|
||||
}
|
||||
return new DisplayListVBO(vbos);
|
||||
}
|
||||
|
||||
private static ByteBuffer mergeAndDelete(List<ByteBuffer> buffers) {
|
||||
if (buffers.size() == 1) {
|
||||
return buffers.get(0);
|
||||
}
|
||||
int needed = 0;
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
needed += buffer.remaining();
|
||||
}
|
||||
|
||||
ByteBuffer out = memAlloc(needed);
|
||||
long dst = memAddress0(out);
|
||||
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
final int len = buffer.limit();
|
||||
final long address = memAddress0(buffer);
|
||||
memCopy(address, dst, len);
|
||||
dst += len;
|
||||
|
||||
nmemFree(address);
|
||||
}
|
||||
|
||||
out.limit(needed);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static final class FormatData {
|
||||
|
||||
private final List<ByteBuffer> buffers;
|
||||
private final int drawMode;
|
||||
private final int drawIndex;
|
||||
|
||||
public FormatData(List<ByteBuffer> buffers, int drawMode, int drawIndex) {
|
||||
this.buffers = buffers;
|
||||
this.drawMode = drawMode;
|
||||
this.drawIndex = drawIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
|
||||
/**
|
||||
* Command opcodes for ByteBuffer-based display list serialization.
|
||||
* Each command has a fixed layout described in comments.
|
||||
* All values are 4-byte aligned.
|
||||
*/
|
||||
public final class GLCommand {
|
||||
private GLCommand() {}
|
||||
|
||||
// === Single int commands (8 bytes: opcode + param) ===
|
||||
public static final int ENABLE = 1; // [cmd:4][cap:4]
|
||||
public static final int DISABLE = 2; // [cmd:4][cap:4]
|
||||
public static final int CLEAR = 3; // [cmd:4][mask:4]
|
||||
public static final int CLEAR_STENCIL = 4; // [cmd:4][s:4]
|
||||
public static final int CULL_FACE = 5; // [cmd:4][mode:4]
|
||||
public static final int DEPTH_FUNC = 6; // [cmd:4][func:4]
|
||||
public static final int SHADE_MODEL = 7; // [cmd:4][mode:4]
|
||||
public static final int LOGIC_OP = 8; // [cmd:4][opcode:4]
|
||||
public static final int MATRIX_MODE = 9; // [cmd:4][mode:4]
|
||||
public static final int ACTIVE_TEXTURE = 10; // [cmd:4][texture:4]
|
||||
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
|
||||
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
|
||||
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
|
||||
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
|
||||
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
|
||||
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
|
||||
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
|
||||
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
|
||||
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
|
||||
|
||||
// === Two int commands (12 bytes) ===
|
||||
public static final int BIND_TEXTURE = 20; // [cmd:4][target:4][texture:4]
|
||||
public static final int POLYGON_MODE = 21; // [cmd:4][face:4][mode:4]
|
||||
public static final int COLOR_MATERIAL = 22; // [cmd:4][face:4][mode:4]
|
||||
public static final int LINE_STIPPLE = 23; // [cmd:4][factor:4][pattern:4]
|
||||
public static final int STENCIL_MASK_SEPARATE = 24; // [cmd:4][face:4][mask:4]
|
||||
public static final int FOGI = 25; // [cmd:4][pname:4][param:4]
|
||||
public static final int HINT = 26; // [cmd:4][target:4][mode:4]
|
||||
|
||||
// === Three int commands (16 bytes) ===
|
||||
public static final int STENCIL_FUNC = 30; // [cmd:4][func:4][ref:4][mask:4]
|
||||
public static final int STENCIL_OP = 31; // [cmd:4][fail:4][zfail:4][zpass:4]
|
||||
public static final int TEX_PARAMETERI = 32; // [cmd:4][target:4][pname:4][param:4]
|
||||
|
||||
// === Four int commands (20 bytes) ===
|
||||
public static final int VIEWPORT = 40; // [cmd:4][x:4][y:4][width:4][height:4]
|
||||
public static final int BLEND_FUNC = 41; // [cmd:4][srcRgb:4][dstRgb:4][srcAlpha:4][dstAlpha:4]
|
||||
public static final int COLOR_MASK = 42; // [cmd:4][r:4][g:4][b:4][a:4] (0 or 1 each)
|
||||
public static final int STENCIL_FUNC_SEPARATE = 43; // [cmd:4][face:4][func:4][ref:4][mask:4]
|
||||
public static final int STENCIL_OP_SEPARATE = 44; // [cmd:4][face:4][sfail:4][dpfail:4][dppass:4]
|
||||
public static final int SCISSOR = 45; // [cmd:4][x:4][y:4][width:4][height:4]
|
||||
|
||||
// === Float commands ===
|
||||
public static final int POINT_SIZE = 50; // [cmd:4][size:4f]
|
||||
public static final int LINE_WIDTH = 51; // [cmd:4][width:4f]
|
||||
public static final int POLYGON_OFFSET = 52; // [cmd:4][factor:4f][units:4f]
|
||||
public static final int NORMAL = 53; // [cmd:4][x:4f][y:4f][z:4f]
|
||||
public static final int COLOR = 54; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
|
||||
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
|
||||
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
|
||||
|
||||
// === Mixed int+float commands ===
|
||||
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
|
||||
public static final int FOGF = 61; // [cmd:4][pname:4][param:4f]
|
||||
public static final int LIGHTF = 62; // [cmd:4][light:4][pname:4][param:4f]
|
||||
public static final int LIGHT_MODELF = 63; // [cmd:4][pname:4][param:4f]
|
||||
public static final int LIGHTI = 64; // [cmd:4][light:4][pname:4][param:4]
|
||||
public static final int LIGHT_MODELI = 65; // [cmd:4][pname:4][param:4]
|
||||
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
|
||||
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
|
||||
|
||||
// === Double commands ===
|
||||
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
|
||||
public static final int 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
|
||||
public static final int CLIP_PLANE = 94; // [cmd:4][plane:4][a:8d][b:8d][c:8d][d:8d] = 40 bytes
|
||||
|
||||
// === Draw commands ===
|
||||
public static final int DRAW_RANGE = 100; // [cmd:4][vboIndex:4] = 8 bytes
|
||||
public static final int DRAW_RANGE_RESTORE = 101; // [cmd:4][vboIndex:4][flags:4][color:4][normal:4][lastTexCoord:8f] = 28 bytes
|
||||
public static final int CALL_LIST = 102; // [cmd:4][listId:4] = 8 bytes
|
||||
|
||||
|
||||
public static final int DRAW_ARRAYS = 110; // [cmd:4][drawMode:4][start:4][count:4] = 20 bytes
|
||||
public static final int DRAW_ELEMENTS = 111; // [cmd:4][mode:4][indices_count:4][type:4][indices_buffer_offset:8] = 28 bytes
|
||||
public static final int DRAW_BUFFER = 112; // [cmd:4][mode:4] = 8 bytes
|
||||
public static final int DRAW_BUFFERS = 113; // [cmd:4][count:4][bufs:4*8] = 40 bytes (up to 8 buffers)
|
||||
/**
|
||||
* Draw VBO range with attribute restoration after draw.
|
||||
* Used for immediate mode VBOs to restore GL current state (color, normal, texcoord).
|
||||
*/
|
||||
|
||||
|
||||
// === Bind commands ===
|
||||
public static final int BIND_VBO = 120;
|
||||
public static final int BIND_VAO = 121;
|
||||
|
||||
// === Complex object reference ===
|
||||
public static final int COMPLEX_REF = 255; // [cmd:4][index:4] = 8 bytes
|
||||
|
||||
/**
|
||||
* Get a human-readable name for an opcode (for debugging).
|
||||
*/
|
||||
public static String getName(int opcode) {
|
||||
return switch (opcode) {
|
||||
case ENABLE -> "ENABLE";
|
||||
case DISABLE -> "DISABLE";
|
||||
case CLEAR -> "CLEAR";
|
||||
case CLEAR_STENCIL -> "CLEAR_STENCIL";
|
||||
case CULL_FACE -> "CULL_FACE";
|
||||
case DEPTH_FUNC -> "DEPTH_FUNC";
|
||||
case SHADE_MODEL -> "SHADE_MODEL";
|
||||
case LOGIC_OP -> "LOGIC_OP";
|
||||
case MATRIX_MODE -> "MATRIX_MODE";
|
||||
case ACTIVE_TEXTURE -> "ACTIVE_TEXTURE";
|
||||
case USE_PROGRAM -> "USE_PROGRAM";
|
||||
case PUSH_ATTRIB -> "PUSH_ATTRIB";
|
||||
case POP_ATTRIB -> "POP_ATTRIB";
|
||||
case LOAD_IDENTITY -> "LOAD_IDENTITY";
|
||||
case PUSH_MATRIX -> "PUSH_MATRIX";
|
||||
case POP_MATRIX -> "POP_MATRIX";
|
||||
case STENCIL_MASK -> "STENCIL_MASK";
|
||||
case DEPTH_MASK -> "DEPTH_MASK";
|
||||
case FRONT_FACE -> "FRONT_FACE";
|
||||
case BIND_TEXTURE -> "BIND_TEXTURE";
|
||||
case POLYGON_MODE -> "POLYGON_MODE";
|
||||
case COLOR_MATERIAL -> "COLOR_MATERIAL";
|
||||
case LINE_STIPPLE -> "LINE_STIPPLE";
|
||||
case STENCIL_MASK_SEPARATE -> "STENCIL_MASK_SEPARATE";
|
||||
case FOGI -> "FOGI";
|
||||
case HINT -> "HINT";
|
||||
case STENCIL_FUNC -> "STENCIL_FUNC";
|
||||
case STENCIL_OP -> "STENCIL_OP";
|
||||
case TEX_PARAMETERI -> "TEX_PARAMETERI";
|
||||
case VIEWPORT -> "VIEWPORT";
|
||||
case SCISSOR -> "SCISSOR";
|
||||
case BLEND_FUNC -> "BLEND_FUNC";
|
||||
case COLOR_MASK -> "COLOR_MASK";
|
||||
case STENCIL_FUNC_SEPARATE -> "STENCIL_FUNC_SEPARATE";
|
||||
case STENCIL_OP_SEPARATE -> "STENCIL_OP_SEPARATE";
|
||||
case POINT_SIZE -> "POINT_SIZE";
|
||||
case LINE_WIDTH -> "LINE_WIDTH";
|
||||
case POLYGON_OFFSET -> "POLYGON_OFFSET";
|
||||
case COLOR -> "COLOR";
|
||||
case CLEAR_COLOR -> "CLEAR_COLOR";
|
||||
case BLEND_COLOR -> "BLEND_COLOR";
|
||||
case ALPHA_FUNC -> "ALPHA_FUNC";
|
||||
case FOGF -> "FOGF";
|
||||
case LIGHTF -> "LIGHTF";
|
||||
case LIGHT_MODELF -> "LIGHT_MODELF";
|
||||
case LIGHTI -> "LIGHTI";
|
||||
case LIGHT_MODELI -> "LIGHT_MODELI";
|
||||
case MATERIALF -> "MATERIALF";
|
||||
case TEX_PARAMETERF -> "TEX_PARAMETERF";
|
||||
case TRANSLATE -> "TRANSLATE";
|
||||
case 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 CLIP_PLANE -> "CLIP_PLANE";
|
||||
case DRAW_RANGE -> "DRAW_RANGE";
|
||||
case CALL_LIST -> "CALL_LIST";
|
||||
case DRAW_BUFFER -> "DRAW_BUFFER";
|
||||
case DRAW_BUFFERS -> "DRAW_BUFFERS";
|
||||
case COMPLEX_REF -> "COMPLEX_REF";
|
||||
case DRAW_RANGE_RESTORE -> "DRAW_RANGE_RESTORE";
|
||||
case BIND_VAO -> "BIND_VAO";
|
||||
case BIND_VBO -> "BIND_VBO";
|
||||
default -> "UNKNOWN(" + opcode + ")";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get size of a command in bytes (including the opcode).
|
||||
*/
|
||||
public static int getCommandSize(int cmd, long ptr) {
|
||||
return switch (cmd) {
|
||||
// Opcode-only commands (4 bytes)
|
||||
case GLCommand.LOAD_IDENTITY, GLCommand.PUSH_MATRIX, GLCommand.POP_MATRIX -> 4;
|
||||
|
||||
// Single int commands (8 bytes)
|
||||
case GLCommand.ENABLE, GLCommand.DISABLE, GLCommand.CLEAR, GLCommand.CLEAR_STENCIL,
|
||||
GLCommand.CULL_FACE, GLCommand.DEPTH_FUNC, GLCommand.SHADE_MODEL, GLCommand.LOGIC_OP,
|
||||
GLCommand.MATRIX_MODE, GLCommand.ACTIVE_TEXTURE, GLCommand.USE_PROGRAM,
|
||||
GLCommand.PUSH_ATTRIB, GLCommand.POP_ATTRIB, GLCommand.STENCIL_MASK,
|
||||
GLCommand.DEPTH_MASK, GLCommand.FRONT_FACE, GLCommand.POINT_SIZE, GLCommand.LINE_WIDTH,
|
||||
GLCommand.CALL_LIST, GLCommand.COMPLEX_REF, GLCommand.DRAW_RANGE, GLCommand.BIND_VBO,
|
||||
GLCommand.BIND_VAO -> 8;
|
||||
|
||||
// Two int commands (12 bytes)
|
||||
case GLCommand.BIND_TEXTURE, GLCommand.POLYGON_MODE, GLCommand.COLOR_MATERIAL,
|
||||
GLCommand.LINE_STIPPLE, GLCommand.STENCIL_MASK_SEPARATE, GLCommand.FOGI,
|
||||
GLCommand.HINT, GLCommand.POLYGON_OFFSET, GLCommand.ALPHA_FUNC, GLCommand.FOGF,
|
||||
GLCommand.LIGHT_MODELF, GLCommand.LIGHT_MODELI -> 12;
|
||||
|
||||
// Three int commands (16 bytes)
|
||||
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
|
||||
GLCommand.LIGHTF, GLCommand.LIGHTI,
|
||||
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF, GLCommand.DRAW_ARRAYS -> 16;
|
||||
|
||||
// Four int commands (20 bytes)
|
||||
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
|
||||
GLCommand.STENCIL_FUNC_SEPARATE, GLCommand.STENCIL_OP_SEPARATE,
|
||||
GLCommand.SCISSOR,
|
||||
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
|
||||
|
||||
case GLCommand.DRAW_ELEMENTS -> 24;
|
||||
|
||||
// DRAW_RANGE_RESTORE: 28 bytes
|
||||
case GLCommand.DRAW_RANGE_RESTORE -> 28;
|
||||
|
||||
// DRAW_BUFFER: 8 bytes [cmd:4][mode:4]
|
||||
case GLCommand.DRAW_BUFFER -> 8;
|
||||
|
||||
// DRAW_BUFFERS: 40 bytes [cmd:4][count:4][bufs:4*8]
|
||||
case GLCommand.DRAW_BUFFERS -> 40;
|
||||
|
||||
// Double commands
|
||||
case GLCommand.TRANSLATE, GLCommand.SCALE -> 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;
|
||||
}
|
||||
case GLCommand.CLIP_PLANE -> 40; // cmd + plane + 4 doubles
|
||||
|
||||
default -> throw new IllegalStateException("Unknown command: " + cmd);
|
||||
};
|
||||
}
|
||||
}
|
||||
+449
@@ -0,0 +1,449 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
|
||||
* Used both during display list compilation and for live immediate mode emulation in core profile.
|
||||
*/
|
||||
public final class ImmediateModeRecorder {
|
||||
private static final DirectTessellator tessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/** Separate tessellator for the splash thread. Nulled after splash completes. */
|
||||
private static DirectTessellator splashTessellator;
|
||||
|
||||
private ImmediateModeRecorder() {
|
||||
|
||||
}
|
||||
|
||||
public static void initSplashTessellator() {
|
||||
splashTessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
public static void destroySplashTessellator() {
|
||||
splashTessellator = null;
|
||||
}
|
||||
|
||||
private static DirectTessellator tessellator() {
|
||||
final DirectTessellator splash = splashTessellator;
|
||||
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
return splash;
|
||||
}
|
||||
return tessellator;
|
||||
}
|
||||
|
||||
public static DirectTessellator getInternalTessellator() {
|
||||
return tessellator();
|
||||
}
|
||||
|
||||
public static boolean isDrawing() {
|
||||
return tessellator().isDrawing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current texture coordinate.
|
||||
* Called by GLStateManager.glTexCoord*.
|
||||
*/
|
||||
public static void setTexCoord(float s, float t) {
|
||||
tessellator().setTextureUV(s, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current lightmap coordinate as packed brightness.
|
||||
* Called by GLStateManager.glMultiTexCoord2f for unit 1 during recording/drawing.
|
||||
*/
|
||||
public static void setLightmapCoord(float s, float t) {
|
||||
tessellator().setBrightness(((int) t << 16) | ((int) s & 0xFFFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current normal vector.
|
||||
* Called by GLStateManager.glNormal*.
|
||||
*/
|
||||
public static void setNormal(float x, float y, float z) {
|
||||
tessellator().setNormal(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current color on the internal tessellator.
|
||||
* Called by GLStateManager.glColor* when inside a glBegin/glEnd block.
|
||||
*/
|
||||
public static void setColor(float r, float g, float b, float a) {
|
||||
tessellator().setColorRGBA((int) (r * 255.0f), (int) (g * 255.0f), (int) (b * 255.0f), (int) (a * 255.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording a new primitive.
|
||||
* Resets the tessellator to clear any data from the previous draw.
|
||||
*
|
||||
* @param primitiveType GL primitive type (GL_QUADS, GL_TRIANGLES, etc.)
|
||||
*/
|
||||
public static void begin(int primitiveType) {
|
||||
final DirectTessellator t = tessellator();
|
||||
t.reset();
|
||||
t.startDrawing(primitiveType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording a new primitive with initial color sync from GLSM cache.
|
||||
* Used for live immediate mode emulation only (not display list recording).
|
||||
*/
|
||||
public static void beginLive(int primitiveType) {
|
||||
final DirectTessellator t = tessellator();
|
||||
t.reset();
|
||||
t.startDrawing(primitiveType);
|
||||
final var c = GLStateManager.getColor();
|
||||
t.setColorRGBA((int) (c.getRed() * 255.0f), (int) (c.getGreen() * 255.0f), (int) (c.getBlue() * 255.0f), (int) (c.getAlpha() * 255.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static DirectTessellator end() {
|
||||
final DirectTessellator t = tessellator();
|
||||
if (!t.isDrawing) {
|
||||
throw new IllegalStateException("glEnd called without glBegin");
|
||||
}
|
||||
|
||||
t.isDrawing = false;
|
||||
|
||||
if (t.isEmpty()) return null;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a vertex with current attributes.
|
||||
* Color is read from GLStateManager at call time.
|
||||
*/
|
||||
public static void vertex(float x, float y, float z) {
|
||||
final DirectTessellator t = tessellator();
|
||||
if (!t.isDrawing) {
|
||||
throw new IllegalStateException("glVertex called outside glBegin/glEnd");
|
||||
}
|
||||
t.addVertex(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static boolean hasGeometry() {
|
||||
return tessellator().vertexCount != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all state including current texcoord/normal.
|
||||
* Called when starting a new display list.
|
||||
*/
|
||||
public static void reset() {
|
||||
tessellator().reset();
|
||||
}
|
||||
|
||||
private static final int LOC_POSITION = Usage.POSITION.getAttributeLocation();
|
||||
private static final int LOC_COLOR = Usage.COLOR.getAttributeLocation();
|
||||
private static final int LOC_UV0 = Usage.PRIMARY_UV.getAttributeLocation();
|
||||
private static final int LOC_UV1 = Usage.SECONDARY_UV.getAttributeLocation();
|
||||
private static final int LOC_NORMAL = Usage.NORMAL.getAttributeLocation();
|
||||
private static final int NUM_ATTRIBS = 1 + Arrays.stream(Usage.values())
|
||||
.mapToInt(Usage::getAttributeLocation)
|
||||
.max().orElse(0);
|
||||
|
||||
private static final ByteBuffer[] scratchAttribBuffers = new ByteBuffer[NUM_ATTRIBS];
|
||||
private static final long[] scratchAttribBaseOffsets = new long[NUM_ATTRIBS];
|
||||
private static final int[] scratchVBOIds = new int[NUM_ATTRIBS];
|
||||
private static final ByteBuffer[] scratchVBOBuffers = new ByteBuffer[NUM_ATTRIBS];
|
||||
private static final long[] scratchVBOReadOffsets = new long[NUM_ATTRIBS];
|
||||
|
||||
private static final class AttribReader {
|
||||
ByteBuffer data;
|
||||
int stride;
|
||||
int baseOffset;
|
||||
int size;
|
||||
int loc;
|
||||
VertexAttribState.Attrib attrib;
|
||||
}
|
||||
|
||||
private static final AttribReader[] readers = new AttribReader[NUM_ATTRIBS];
|
||||
private static int nonPositionCount;
|
||||
private static final int[] nonPositionIndices = new int[NUM_ATTRIBS - 1];
|
||||
private static int positionReaderIndex = -1;
|
||||
|
||||
static {
|
||||
for (int i = 0; i < NUM_ATTRIBS; i++) {
|
||||
readers[i] = new AttribReader();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a glDrawArrays call during display list recording.
|
||||
* Reads vertex data from VBOs or client memory referenced by VertexAttribState and converts to immediate mode geometry.
|
||||
*/
|
||||
public static DirectTessellator processDrawArraysFromAttribs(int mode, int first, int count) {
|
||||
final int readVBOCount = readbackAttribVBOs(first, count);
|
||||
|
||||
try {
|
||||
prepareReaders();
|
||||
if (positionReaderIndex < 0) return null;
|
||||
return readVerticesFromBuffer(mode, first, count);
|
||||
} finally {
|
||||
freeReadbackBuffers(readVBOCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a glDrawElements call during display list recording when a VBO is bound.
|
||||
*/
|
||||
public static DirectTessellator processDrawElementsFromVBO(int mode, int indicesCount, int indexType, long indicesOffset, int eboId) {
|
||||
if (eboId == 0) return null;
|
||||
|
||||
final int readVBOCount = readbackAttribVBOs(-1, 0);
|
||||
|
||||
try {
|
||||
prepareReaders();
|
||||
if (positionReaderIndex < 0) return null;
|
||||
|
||||
final int prevEBO = GLStateManager.getBoundEBO();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
final int eboSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
|
||||
if (eboSize <= 0) {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
return null;
|
||||
}
|
||||
|
||||
final int indexSize = VertexAttribState.Attrib.glTypeSizeBytes(indexType);
|
||||
final int eboReadOffset = (int) indicesOffset;
|
||||
final int eboReadSize = indicesCount * indexSize;
|
||||
if (eboReadOffset + eboReadSize > eboSize) {
|
||||
GLStateManager.LOGGER.warn("[EBO] Read range {}+{} exceeds buffer size {}", eboReadOffset, eboReadSize, eboSize);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
return null;
|
||||
}
|
||||
final ByteBuffer eboData = MemoryUtilities.memAlloc(eboReadSize);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboReadOffset, eboData);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
|
||||
try {
|
||||
return readVerticesFromBufferIndexed(mode, eboData, indexType, 0, indicesCount);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(eboData);
|
||||
}
|
||||
} finally {
|
||||
freeReadbackBuffers(readVBOCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data for all enabled attribs into scratchAttribBuffers.
|
||||
* For VBO-backed attribs, reads VBO data. For client-memory attribs, uses the client pointer directly.
|
||||
*/
|
||||
private static int readbackAttribVBOs(int firstVertex, int vertexCount) {
|
||||
int readVBOCount = 0;
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
final boolean partialRead = firstVertex >= 0;
|
||||
|
||||
for (int i = 0; i < NUM_ATTRIBS; i++) {
|
||||
scratchAttribBuffers[i] = null;
|
||||
scratchAttribBaseOffsets[i] = 0;
|
||||
final VertexAttribState.Attrib a = VertexAttribState.get(i);
|
||||
if (!a.enabled) continue;
|
||||
|
||||
if (a.vboId != 0) {
|
||||
// VBO-backed attrib — read from GPU
|
||||
ByteBuffer existing = null;
|
||||
int existingIdx = -1;
|
||||
for (int j = 0; j < readVBOCount; j++) {
|
||||
if (scratchVBOIds[j] == a.vboId) {
|
||||
existing = scratchVBOBuffers[j];
|
||||
existingIdx = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existing != null) {
|
||||
scratchAttribBuffers[i] = existing;
|
||||
scratchAttribBaseOffsets[i] = scratchVBOReadOffsets[existingIdx];
|
||||
} else {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
|
||||
final int bufferSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
|
||||
if (bufferSize <= 0) {
|
||||
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
|
||||
continue;
|
||||
}
|
||||
|
||||
final long readOffset;
|
||||
final int readSize;
|
||||
if (partialRead) {
|
||||
final int stride = a.effectiveStride();
|
||||
long rangeStart = a.offset + (long) firstVertex * stride;
|
||||
long rangeEnd = a.offset + (long) (firstVertex + vertexCount - 1) * stride + (long) a.size * a.typeSizeBytes();
|
||||
|
||||
for (int k = 0; k < NUM_ATTRIBS; k++) {
|
||||
if (k == i) continue;
|
||||
final VertexAttribState.Attrib ak = VertexAttribState.get(k);
|
||||
if (!ak.enabled || ak.vboId != a.vboId) continue;
|
||||
final int ks = ak.effectiveStride();
|
||||
rangeStart = Math.min(rangeStart, ak.offset + (long) firstVertex * ks);
|
||||
rangeEnd = Math.max(rangeEnd, ak.offset + (long) (firstVertex + vertexCount - 1) * ks
|
||||
+ (long) ak.size * ak.typeSizeBytes());
|
||||
}
|
||||
rangeStart = Math.max(0, rangeStart);
|
||||
rangeEnd = Math.min(bufferSize, rangeEnd);
|
||||
readOffset = rangeStart;
|
||||
readSize = (int) (rangeEnd - rangeStart);
|
||||
} else {
|
||||
readOffset = 0;
|
||||
readSize = bufferSize;
|
||||
}
|
||||
|
||||
if (readSize <= 0) continue;
|
||||
|
||||
final ByteBuffer buf = MemoryUtilities.memAlloc(readSize);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, readOffset, buf);
|
||||
scratchAttribBuffers[i] = buf;
|
||||
scratchAttribBaseOffsets[i] = readOffset;
|
||||
scratchVBOIds[readVBOCount] = a.vboId;
|
||||
scratchVBOReadOffsets[readVBOCount] = readOffset;
|
||||
scratchVBOBuffers[readVBOCount] = buf;
|
||||
readVBOCount++;
|
||||
}
|
||||
} else if (a.clientPointer != null) {
|
||||
// Client-memory attrib — use pointer directly (no allocation needed)
|
||||
scratchAttribBuffers[i] = a.clientPointer;
|
||||
}
|
||||
}
|
||||
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
|
||||
return readVBOCount;
|
||||
}
|
||||
|
||||
private static void freeReadbackBuffers(int readVBOCount) {
|
||||
for (int i = 0; i < readVBOCount; i++) {
|
||||
MemoryUtilities.memFree(scratchVBOBuffers[i]);
|
||||
scratchVBOBuffers[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void prepareReaders() {
|
||||
nonPositionCount = 0;
|
||||
positionReaderIndex = -1;
|
||||
|
||||
for (int i = 0; i < NUM_ATTRIBS; i++) {
|
||||
final ByteBuffer buf = scratchAttribBuffers[i];
|
||||
if (buf == null) continue;
|
||||
final VertexAttribState.Attrib a = VertexAttribState.get(i);
|
||||
|
||||
final AttribReader r = readers[i];
|
||||
r.data = buf;
|
||||
r.stride = a.effectiveStride();
|
||||
r.baseOffset = (int) (a.offset - scratchAttribBaseOffsets[i]);
|
||||
r.size = a.size;
|
||||
r.loc = i;
|
||||
r.attrib = a;
|
||||
|
||||
if (i == LOC_POSITION) {
|
||||
positionReaderIndex = i;
|
||||
} else {
|
||||
nonPositionIndices[nonPositionCount++] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static DirectTessellator readVerticesFromBuffer(int mode, int first, int count) {
|
||||
begin(mode);
|
||||
final DirectTessellator t = tessellator();
|
||||
|
||||
for (int i = first; i < first + count; i++) {
|
||||
readVertexFromBuffer(t, i);
|
||||
}
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
private static DirectTessellator readVerticesFromBufferIndexed(int mode, ByteBuffer eboData, int indexType, int eboOffset, int indicesCount) {
|
||||
begin(mode);
|
||||
final DirectTessellator t = tessellator();
|
||||
|
||||
for (int i = 0; i < indicesCount; i++) {
|
||||
final int index = readIndex(eboData, indexType, eboOffset, i);
|
||||
readVertexFromBuffer(t, index);
|
||||
}
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
public static int readIndex(ByteBuffer ebo, int indexType, int eboOffset, int i) {
|
||||
return switch (indexType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> ebo.get(eboOffset + i) & 0xFF;
|
||||
case GL11.GL_UNSIGNED_SHORT -> ebo.getShort(eboOffset + i * 2) & 0xFFFF;
|
||||
case GL11.GL_UNSIGNED_INT -> ebo.getInt(eboOffset + i * 4);
|
||||
default -> i;
|
||||
};
|
||||
}
|
||||
|
||||
private static void readVertexFromBuffer(DirectTessellator t, int vertexIndex) {
|
||||
for (int i = 0; i < nonPositionCount; i++) {
|
||||
final AttribReader r = readers[nonPositionIndices[i]];
|
||||
final int base = r.baseOffset + vertexIndex * r.stride;
|
||||
emitAttrib(t, r, base);
|
||||
}
|
||||
|
||||
if (positionReaderIndex >= 0) {
|
||||
final AttribReader r = readers[positionReaderIndex];
|
||||
final int base = r.baseOffset + vertexIndex * r.stride;
|
||||
t.addVertex(
|
||||
r.attrib.readComponent(r.data, base, 0),
|
||||
r.attrib.readComponent(r.data, base, 1),
|
||||
(r.size >= 3) ? r.attrib.readComponent(r.data, base, 2) : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitAttrib(DirectTessellator t, AttribReader r, int base) {
|
||||
if (r.loc == LOC_NORMAL) {
|
||||
t.setNormal(
|
||||
r.attrib.readComponent(r.data, base, 0),
|
||||
r.attrib.readComponent(r.data, base, 1),
|
||||
r.attrib.readComponent(r.data, base, 2));
|
||||
} else if (r.loc == LOC_UV0) {
|
||||
t.setTextureUV(
|
||||
r.attrib.readComponent(r.data, base, 0),
|
||||
(r.size >= 2) ? r.attrib.readComponent(r.data, base, 1) : 0.0f);
|
||||
} else if (r.loc == LOC_UV1) {
|
||||
if (r.attrib.type == GL11.GL_SHORT) {
|
||||
final int s = r.data.getShort(base) & 0xFFFF;
|
||||
final int u = (r.size >= 2) ? (r.data.getShort(base + 2) & 0xFFFF) : 0;
|
||||
t.setBrightness((u << 16) | s);
|
||||
} else if (r.attrib.type == GL11.GL_FLOAT) {
|
||||
final int s = (int) r.data.getFloat(base);
|
||||
final int u = (r.size >= 2) ? (int) r.data.getFloat(base + 4) : 0;
|
||||
t.setBrightness((u << 16) | (s & 0xFFFF));
|
||||
}
|
||||
} else if (r.loc == LOC_COLOR) {
|
||||
final float cr = r.attrib.readComponent(r.data, base, 0);
|
||||
final float cg = r.attrib.readComponent(r.data, base, 1);
|
||||
final float cb = r.attrib.readComponent(r.data, base, 2);
|
||||
final float ca = (r.size >= 4) ? r.attrib.readComponent(r.data, base, 3) : 1.0f;
|
||||
t.setColorRGBA((int) (cr * 255.0f), (int) (cg * 255.0f), (int) (cb * 255.0f), (int) (ca * 255.0f));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
/**
|
||||
* Command: glMultMatrix(matrix)
|
||||
* Multiplies the current matrix by the specified matrix.
|
||||
*/
|
||||
public record MultMatrixCmd(Matrix4f matrix, FloatBuffer buffer) implements DisplayListCommand {
|
||||
|
||||
/**
|
||||
* Create a MultMatrixCmd with pre-allocated buffer.
|
||||
*/
|
||||
public static MultMatrixCmd create(Matrix4f matrix) {
|
||||
final FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
|
||||
matrix.get(buffer);
|
||||
buffer.rewind();
|
||||
return new MultMatrixCmd(new Matrix4f(matrix), buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
buffer.rewind();
|
||||
GLStateManager.glMultMatrix(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return String.format("MultMatrix(%s)", matrix);
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
|
||||
* Specifies a two-dimensional texture image during display list playback.
|
||||
*/
|
||||
public record TexImage2DCmd(
|
||||
int target,
|
||||
int level,
|
||||
int internalformat,
|
||||
int width,
|
||||
int height,
|
||||
int border,
|
||||
int format,
|
||||
int type,
|
||||
@Nullable byte[] pixelData
|
||||
) implements DisplayListCommand {
|
||||
|
||||
/**
|
||||
* Create command from IntBuffer
|
||||
*/
|
||||
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 4; // 4 bytes per int
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asIntBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command from FloatBuffer
|
||||
*/
|
||||
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 4; // 4 bytes per float
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asFloatBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command from DoubleBuffer
|
||||
*/
|
||||
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 8; // 8 bytes per double
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asDoubleBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command from ByteBuffer
|
||||
*/
|
||||
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = limit - pos;
|
||||
data = new byte[size];
|
||||
pixels.get(data);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ByteBuffer buffer = null;
|
||||
if (pixelData != null) {
|
||||
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
|
||||
buffer.put(pixelData);
|
||||
buffer.flip();
|
||||
}
|
||||
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
|
||||
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s)",
|
||||
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
|
||||
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
|
||||
* Updates a portion of an existing two-dimensional texture image during display list playback.
|
||||
*/
|
||||
public record TexSubImage2DCmd(
|
||||
int target,
|
||||
int level,
|
||||
int xoffset,
|
||||
int yoffset,
|
||||
int width,
|
||||
int height,
|
||||
int format,
|
||||
int type,
|
||||
@Nullable byte[] pixelData
|
||||
) implements DisplayListCommand {
|
||||
|
||||
/**
|
||||
* Create command from IntBuffer
|
||||
*/
|
||||
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 4; // 4 bytes per int
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asIntBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command from FloatBuffer
|
||||
*/
|
||||
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 4; // 4 bytes per float
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asFloatBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command from DoubleBuffer
|
||||
*/
|
||||
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 8; // 8 bytes per double
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asDoubleBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command from ByteBuffer
|
||||
*/
|
||||
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = limit - pos;
|
||||
data = new byte[size];
|
||||
pixels.get(data);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ByteBuffer buffer = null;
|
||||
if (pixelData != null) {
|
||||
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
|
||||
buffer.put(pixelData);
|
||||
buffer.flip();
|
||||
}
|
||||
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
|
||||
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s)",
|
||||
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
|
||||
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
package com.gtnewhorizons.angelica.glsm.redirect;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.IntInsnNode;
|
||||
import org.objectweb.asm.tree.LdcInsnNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.objectweb.asm.tree.TypeInsnNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Core GL call redirector for GLSM. Rewrites bytecode so that GL calls go through {@code GLStateManager} for state tracking and backend abstraction.
|
||||
* <p>
|
||||
* THIS CLASS MIGHT BE LOADED ON A DIFFERENT CLASS LOADER.
|
||||
* IT SHOULD NOT CALL ANY CODE FROM THE MAIN MOD.
|
||||
*/
|
||||
public class GLSMRedirector {
|
||||
|
||||
private static final boolean ASSERT_MAIN_THREAD = Boolean.getBoolean("angelica.assertMainThread");
|
||||
private static final boolean LOG_SPAM = Boolean.getBoolean("angelica.redirectorLogspam");
|
||||
private static final Logger LOGGER = LogManager.getLogger("GLSMRedirector");
|
||||
|
||||
private static final String Drawable = "org/lwjgl/opengl/Drawable";
|
||||
private static final String GLStateManager = "com/gtnewhorizons/angelica/glsm/GLStateManager";
|
||||
private static final String GL_PREFIX = "org/lwjgl/opengl/GL";
|
||||
private static final String ARBVertexArrayObject = "org/lwjgl/opengl/ARBVertexArrayObject";
|
||||
private static final String APPLEVertexArrayObject = "org/lwjgl/opengl/APPLEVertexArrayObject";
|
||||
private static final String Project = "org/lwjgl/util/glu/Project";
|
||||
private static final String GLU = "org/lwjgl/util/glu/GLU";
|
||||
private static final String OpenGlHelper = "net/minecraft/client/renderer/OpenGlHelper";
|
||||
private static final String EXTBlendFunc = "org/lwjgl/opengl/EXTBlendFuncSeparate";
|
||||
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
|
||||
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
|
||||
private static final String ARBInstancedArrays = "org/lwjgl/opengl/ARBInstancedArrays";
|
||||
|
||||
// Redirect VAO related calls from NHLib
|
||||
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
|
||||
|
||||
private static final String MinecraftClient = "net.minecraft.client";
|
||||
private static final String SplashProgress = "cpw.mods.fml.client.SplashProgress";
|
||||
private static final Set<String> ExcludedMinecraftMainThreadChecks = ImmutableSet.of(
|
||||
"startGame", "func_71384_a",
|
||||
"initializeTextures", "func_77474_a"
|
||||
);
|
||||
|
||||
private static final Map<String, Map<String, String>> methodRedirects = new HashMap<>(32);
|
||||
private static final Map<String, String> glMethodRedirects = new HashMap<>(256);
|
||||
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
|
||||
private static final Map<String, String> typeRedirects = new HashMap<>();
|
||||
private static final ClassConstantPoolParser cstPoolParser;
|
||||
|
||||
private static final String[] CORE_EXCLUSIONS = {
|
||||
"org.lwjgl",
|
||||
"com.gtnewhorizon.gtnhlib.asm",
|
||||
"com.gtnewhorizons.angelica.glsm.",
|
||||
"me.eigenraven.lwjgl3ify"
|
||||
};
|
||||
|
||||
static {
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_ALPHA_TEST, "AlphaTest");
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_BLEND, "Blend");
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_DEPTH_TEST, "DepthTest");
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_CULL_FACE, "Cull");
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_LIGHTING, "Lighting");
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_TEXTURE_2D, "Texture");
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_FOG, "Fog");
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL12.GL_RESCALE_NORMAL, "RescaleNormal");
|
||||
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_SCISSOR_TEST, "ScissorTest");
|
||||
|
||||
final var gl11 = RedirectMap.newMap()
|
||||
.add("glAlphaFunc")
|
||||
.add("glBegin")
|
||||
.add("glBindTexture")
|
||||
.add("glBlendFunc")
|
||||
.add("glCallList")
|
||||
.add("glCallLists")
|
||||
.add("glClear")
|
||||
.add("glClearColor")
|
||||
.add("glClearDepth")
|
||||
.add("glClearStencil")
|
||||
.add("glClipPlane")
|
||||
.add("glColor3b")
|
||||
.add("glColor3d")
|
||||
.add("glColor3f")
|
||||
.add("glColor3ub")
|
||||
.add("glColor4b")
|
||||
.add("glColor4d")
|
||||
.add("glColor4f")
|
||||
.add("glColor4ub")
|
||||
.add("glColorMask")
|
||||
.add("glColorMaterial")
|
||||
.add("glDeleteLists")
|
||||
.add("glGenLists")
|
||||
.add("glGenTextures")
|
||||
.add("glIsList")
|
||||
.add("glDeleteTextures")
|
||||
.add("glDepthFunc")
|
||||
.add("glDepthMask")
|
||||
.add("glDepthRange")
|
||||
.add("glDrawArrays")
|
||||
.add("glDrawBuffer")
|
||||
.add("glDrawElements")
|
||||
.add("glEdgeFlag")
|
||||
.add("glEnd")
|
||||
.add("glEndList")
|
||||
.add("glFog")
|
||||
.add("glFogf")
|
||||
.add("glFogi")
|
||||
.add("glFrustum")
|
||||
.add("glGetBoolean")
|
||||
.add("glGetFloat")
|
||||
.add("glGetInteger")
|
||||
.add("glGetLight")
|
||||
.add("glGetMaterial")
|
||||
.add("glGetTexLevelParameteri")
|
||||
.add("glGetTexParameterf")
|
||||
.add("glGetTexParameteri")
|
||||
.add("glIsEnabled")
|
||||
.add("glMaterial")
|
||||
.add("glMaterialf")
|
||||
.add("glMateriali")
|
||||
.add("glLight")
|
||||
.add("glLightf")
|
||||
.add("glLighti")
|
||||
.add("glLightModel")
|
||||
.add("glLightModelf")
|
||||
.add("glLightModeli")
|
||||
.add("glLineStipple")
|
||||
.add("glLineWidth")
|
||||
.add("glListBase")
|
||||
.add("glLoadIdentity")
|
||||
.add("glLoadMatrix")
|
||||
.add("glLogicOp")
|
||||
.add("glMatrixMode")
|
||||
.add("glMultMatrix")
|
||||
.add("glNewList")
|
||||
.add("glNormal3b")
|
||||
.add("glNormal3d")
|
||||
.add("glFlush")
|
||||
.add("glFinish")
|
||||
.add("glGetError")
|
||||
.add("glGetString")
|
||||
.add("glGetTexImage")
|
||||
.add("glNormal3f")
|
||||
.add("glNormal3i")
|
||||
.add("glOrtho")
|
||||
.add("glPopAttrib")
|
||||
.add("glPopMatrix")
|
||||
.add("glPushAttrib")
|
||||
.add("glPushMatrix")
|
||||
.add("glRasterPos2d")
|
||||
.add("glRasterPos2f")
|
||||
.add("glRasterPos2i")
|
||||
.add("glRasterPos3d")
|
||||
.add("glRasterPos3f")
|
||||
.add("glRasterPos3i")
|
||||
.add("glRasterPos4d")
|
||||
.add("glRasterPos4f")
|
||||
.add("glRasterPos4i")
|
||||
.add("glRotated")
|
||||
.add("glRotatef")
|
||||
.add("glScaled")
|
||||
.add("glScalef")
|
||||
.add("glShadeModel")
|
||||
.add("glTexCoord1d")
|
||||
.add("glTexCoord1f")
|
||||
.add("glTexCoord2d")
|
||||
.add("glTexCoord2f")
|
||||
.add("glTexCoord3d")
|
||||
.add("glTexCoord3f")
|
||||
.add("glTexCoord4d")
|
||||
.add("glTexCoord4f")
|
||||
.add("glTexEnvi")
|
||||
.add("glTexEnvf")
|
||||
.add("glTexEnv")
|
||||
.add("glGetTexEnv")
|
||||
.add("glGetTexEnvi")
|
||||
.add("glGetTexEnvf")
|
||||
.add("glTexGeni")
|
||||
.add("glTexGenf")
|
||||
.add("glTexGend")
|
||||
.add("glTexGen")
|
||||
.add("glGetTexGen")
|
||||
.add("glGetTexGeni")
|
||||
.add("glGetTexGenf")
|
||||
.add("glGetTexGend")
|
||||
.add("glTexImage1D")
|
||||
.add("glTexImage2D")
|
||||
.add("glTexImage3D")
|
||||
.add("glTexSubImage1D")
|
||||
.add("glTexSubImage2D")
|
||||
.add("glCopyTexImage1D")
|
||||
.add("glCopyTexImage2D")
|
||||
.add("glCopyTexSubImage1D")
|
||||
.add("glCopyTexSubImage2D")
|
||||
.add("glPixelStoref")
|
||||
.add("glPixelStorei")
|
||||
.add("glPixelTransferf")
|
||||
.add("glPixelTransferi")
|
||||
.add("glTexParameter")
|
||||
.add("glTexParameterf")
|
||||
.add("glTexParameteri")
|
||||
.add("glCullFace")
|
||||
.add("glFrontFace")
|
||||
.add("glHint")
|
||||
.add("glLineStipple")
|
||||
.add("glLineWidth")
|
||||
.add("glPointSize")
|
||||
.add("glPolygonMode")
|
||||
.add("glPolygonOffset")
|
||||
.add("glPolygonStipple")
|
||||
.add("glAccum")
|
||||
.add("glReadBuffer")
|
||||
.add("glSampleCoverage")
|
||||
.add("glScissor")
|
||||
.add("glStencilFunc")
|
||||
.add("glStencilFuncSeparate")
|
||||
.add("glStencilMask")
|
||||
.add("glStencilMaskSeparate")
|
||||
.add("glStencilOp")
|
||||
.add("glStencilOpSeparate")
|
||||
.add("glTranslated")
|
||||
.add("glTranslatef")
|
||||
.add("glVertex2d")
|
||||
.add("glVertex2f")
|
||||
.add("glVertex2i")
|
||||
.add("glVertex3d")
|
||||
.add("glVertex3f")
|
||||
.add("glVertex3i")
|
||||
.add("glVertexPointer")
|
||||
.add("glColorPointer")
|
||||
.add("glNormalPointer")
|
||||
.add("glTexCoordPointer")
|
||||
.add("glEnableClientState")
|
||||
.add("glDisableClientState")
|
||||
.add("glInterleavedArrays")
|
||||
.add("glPushClientAttrib")
|
||||
.add("glPopClientAttrib")
|
||||
.add("glViewport")
|
||||
.add("glFeedbackBuffer")
|
||||
.add("glRenderMode")
|
||||
.add("glPassThrough")
|
||||
.add("glSelectBuffer")
|
||||
.add("glInitNames")
|
||||
.add("glPushName")
|
||||
.add("glPopName")
|
||||
.add("glLoadName");
|
||||
final var gl12 = RedirectMap.newMap()
|
||||
.add("glTexImage3D")
|
||||
.add("glTexSubImage3D")
|
||||
.add("glCopyTexSubImage3D");
|
||||
final var gl13 = RedirectMap.newMap()
|
||||
.add("glActiveTexture")
|
||||
.add("glSampleCoverage")
|
||||
.add("glClientActiveTexture")
|
||||
.add("glMultiTexCoord2f")
|
||||
.add("glMultiTexCoord2d")
|
||||
.add("glMultiTexCoord2s");
|
||||
final var gl14 = RedirectMap.newMap()
|
||||
.add("glBlendFuncSeparate", "tryBlendFuncSeparate")
|
||||
.add("glBlendColor")
|
||||
.add("glBlendEquation");
|
||||
final var gl15 = RedirectMap.newMap()
|
||||
.add("glGenBuffers")
|
||||
.add("glBindBuffer")
|
||||
.add("glDeleteBuffers")
|
||||
.add("glBufferData")
|
||||
.add("glBufferSubData")
|
||||
.add("glMapBuffer")
|
||||
.add("glUnmapBuffer")
|
||||
.add("glGetBufferSubData")
|
||||
.add("glGetBufferParameteri")
|
||||
.add("glIsBuffer");
|
||||
final var gl20 = RedirectMap.newMap()
|
||||
.add("glBlendEquationSeparate")
|
||||
.add("glDrawBuffers")
|
||||
.add("glStencilFuncSeparate")
|
||||
.add("glStencilMaskSeparate")
|
||||
.add("glStencilOpSeparate")
|
||||
.add("glUseProgram")
|
||||
.add("glShaderSource")
|
||||
.add("glLinkProgram")
|
||||
.add("glDeleteProgram")
|
||||
.add("glCreateShader")
|
||||
.add("glCompileShader")
|
||||
.add("glCreateProgram")
|
||||
.add("glAttachShader")
|
||||
.add("glDetachShader")
|
||||
.add("glValidateProgram")
|
||||
.add("glGetUniformLocation")
|
||||
.add("glGetAttribLocation")
|
||||
.add("glUniform1f")
|
||||
.add("glUniform2f")
|
||||
.add("glUniform3f")
|
||||
.add("glUniform4f")
|
||||
.add("glUniform1i")
|
||||
.add("glUniform2i")
|
||||
.add("glUniform3i")
|
||||
.add("glUniform4i")
|
||||
.add("glUniform1")
|
||||
.add("glUniform2")
|
||||
.add("glUniform3")
|
||||
.add("glUniform4")
|
||||
.add("glUniformMatrix2")
|
||||
.add("glUniformMatrix3")
|
||||
.add("glUniformMatrix4")
|
||||
.add("glDeleteShader")
|
||||
.add("glGetShaderi")
|
||||
.add("glGetShaderInfoLog")
|
||||
.add("glGetProgrami")
|
||||
.add("glGetProgramInfoLog")
|
||||
.add("glBindAttribLocation")
|
||||
.add("glVertexAttrib2f")
|
||||
.add("glVertexAttrib2s")
|
||||
.add("glVertexAttrib3f")
|
||||
.add("glVertexAttrib4f")
|
||||
.add("glGetActiveUniform")
|
||||
.add("glGetAttachedShaders")
|
||||
.add("glGetShaderSource")
|
||||
.add("glGetUniform")
|
||||
.add("glVertexAttribPointer")
|
||||
.add("glEnableVertexAttribArray")
|
||||
.add("glDisableVertexAttribArray");
|
||||
final var gl30 = RedirectMap.newMap()
|
||||
.add("glGenVertexArrays")
|
||||
.add("glBindVertexArray")
|
||||
.add("glDeleteVertexArrays")
|
||||
.add("glBindFramebuffer")
|
||||
.add("glDeleteFramebuffers")
|
||||
.add("glGenFramebuffers")
|
||||
.add("glCheckFramebufferStatus")
|
||||
.add("glFramebufferTexture2D")
|
||||
.add("glVertexAttribIPointer")
|
||||
.add("glGenerateMipmap")
|
||||
.add("glGetFramebufferAttachmentParameteri")
|
||||
.add("glBlitFramebuffer");
|
||||
final var gl31 = RedirectMap.newMap()
|
||||
.add("glDrawElementsInstanced");
|
||||
final var gl32 = RedirectMap.newMap()
|
||||
.add("glFramebufferTexture");
|
||||
final var gl33 = RedirectMap.newMap()
|
||||
.add("glGenSamplers")
|
||||
.add("glDeleteSamplers")
|
||||
.add("glBindSampler")
|
||||
.add("glSamplerParameteri")
|
||||
.add("glSamplerParameterf")
|
||||
.add("glVertexAttribDivisor");
|
||||
final var gl42 = RedirectMap.newMap()
|
||||
.add("glBindImageTexture")
|
||||
.add("glMemoryBarrier")
|
||||
.add("glTexStorage2D");
|
||||
final var gl43 = RedirectMap.newMap()
|
||||
.add("glDispatchCompute")
|
||||
.add("glClearBufferSubData")
|
||||
.add("glBindVertexBuffer")
|
||||
.add("glVertexAttribFormat")
|
||||
.add("glVertexAttribIFormat")
|
||||
.add("glVertexAttribBinding");
|
||||
final var gl44 = RedirectMap.newMap()
|
||||
.add("glBufferStorage")
|
||||
.add("glClearTexImage");
|
||||
|
||||
// Merge all GL version maps into the flat lookup
|
||||
glMethodRedirects.putAll(gl11);
|
||||
glMethodRedirects.putAll(gl12);
|
||||
glMethodRedirects.putAll(gl13);
|
||||
glMethodRedirects.putAll(gl14);
|
||||
glMethodRedirects.putAll(gl15);
|
||||
glMethodRedirects.putAll(gl20);
|
||||
glMethodRedirects.putAll(gl30);
|
||||
glMethodRedirects.putAll(gl31);
|
||||
glMethodRedirects.putAll(gl32);
|
||||
glMethodRedirects.putAll(gl33);
|
||||
glMethodRedirects.putAll(gl42);
|
||||
glMethodRedirects.putAll(gl43);
|
||||
glMethodRedirects.putAll(gl44);
|
||||
|
||||
// MINECRAFT
|
||||
methodRedirects.put(OpenGlHelper, RedirectMap.newMap()
|
||||
.add("glBlendFunc", "tryBlendFuncSeparate")
|
||||
.add("func_148821_a", "tryBlendFuncSeparate")
|
||||
.add("func_153171_g", "glBindFramebuffer")
|
||||
.add("func_153174_h", "glDeleteFramebuffers")
|
||||
.add("func_153165_e", "glGenFramebuffers")
|
||||
.add("func_153167_i", "glCheckFramebufferStatus")
|
||||
.add("func_153188_a", "glFramebufferTexture2D")
|
||||
.add("setActiveTexture")
|
||||
.add("setLightmapTextureCoords")
|
||||
.add("isFramebufferEnabled")
|
||||
);
|
||||
methodRedirects.put(EXTBlendFunc, RedirectMap.newMap().add("glBlendFuncSeparateEXT", "tryBlendFuncSeparate"));
|
||||
|
||||
// ARB
|
||||
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
|
||||
.add("glActiveTextureARB")
|
||||
);
|
||||
methodRedirects.put(ARBShaderObjects, RedirectMap.newMap()
|
||||
.add("glUseProgramObjectARB", "glUseProgram")
|
||||
.add("glShaderSourceARB", "glShaderSource")
|
||||
.add("glLinkProgramARB", "glLinkProgram")
|
||||
.add("glCreateShaderObjectARB", "glCreateShader")
|
||||
.add("glCompileShaderARB", "glCompileShader")
|
||||
.add("glCreateProgramObjectARB", "glCreateProgram")
|
||||
.add("glAttachObjectARB", "glAttachShader")
|
||||
.add("glDetachObjectARB", "glDetachShader")
|
||||
.add("glValidateProgramARB", "glValidateProgram")
|
||||
.add("glGetUniformLocationARB", "glGetUniformLocation")
|
||||
.add("glGetAttribLocationARB", "glGetAttribLocation")
|
||||
.add("glDeleteObjectARB")
|
||||
.add("glGetObjectParameterARB")
|
||||
.add("glGetObjectParameteriARB")
|
||||
.add("glGetInfoLogARB")
|
||||
.add("glGetHandleARB")
|
||||
.add("glUniform1fARB", "glUniform1f")
|
||||
.add("glUniform2fARB", "glUniform2f")
|
||||
.add("glUniform3fARB", "glUniform3f")
|
||||
.add("glUniform4fARB", "glUniform4f")
|
||||
.add("glUniform1iARB", "glUniform1i")
|
||||
.add("glUniform2iARB", "glUniform2i")
|
||||
.add("glUniform3iARB", "glUniform3i")
|
||||
.add("glUniform4iARB", "glUniform4i")
|
||||
.add("glUniform1ARB", "glUniform1")
|
||||
.add("glUniform2ARB", "glUniform2")
|
||||
.add("glUniform3ARB", "glUniform3")
|
||||
.add("glUniform4ARB", "glUniform4")
|
||||
.add("glUniformMatrix2ARB", "glUniformMatrix2")
|
||||
.add("glUniformMatrix3ARB", "glUniformMatrix3")
|
||||
.add("glUniformMatrix4ARB", "glUniformMatrix4")
|
||||
.add("glGetActiveUniformARB", "glGetActiveUniform")
|
||||
.add("glGetAttachedObjectsARB", "glGetAttachedShaders")
|
||||
.add("glGetShaderSourceARB", "glGetShaderSource")
|
||||
.add("glGetUniformARB", "glGetUniform")
|
||||
);
|
||||
methodRedirects.put(ARBVertexArrayObject, RedirectMap.newMap()
|
||||
.add("glBindVertexArray")
|
||||
.add("glDeleteVertexArrays")
|
||||
);
|
||||
methodRedirects.put(ARBInstancedArrays, RedirectMap.newMap()
|
||||
.add("glVertexAttribDivisorARB")
|
||||
);
|
||||
|
||||
// APPLE
|
||||
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
|
||||
.add("glBindVertexArrayAPPLE", "glBindVertexArray")
|
||||
);
|
||||
|
||||
// GTNHLib VAO
|
||||
methodRedirects.put(UniversalVAO, RedirectMap.newMap()
|
||||
.add("bindVertexArray", "glBindVertexArray")
|
||||
.add("deleteVertexArrays", "glDeleteVertexArrays")
|
||||
);
|
||||
|
||||
// GLU
|
||||
methodRedirects.put(Project, RedirectMap.newMap()
|
||||
.add("gluPerspective")
|
||||
.add("gluLookAt")
|
||||
.add("gluPickMatrix")
|
||||
);
|
||||
methodRedirects.put(GLU, RedirectMap.newMap()
|
||||
.add("gluPerspective")
|
||||
.add("gluLookAt")
|
||||
.add("gluOrtho2D")
|
||||
.add("gluPickMatrix")
|
||||
);
|
||||
|
||||
// Quadric type replacements
|
||||
typeRedirects.put("org/lwjgl/util/glu/Sphere", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaSphere");
|
||||
typeRedirects.put("org/lwjgl/util/glu/Cylinder", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaCylinder");
|
||||
typeRedirects.put("org/lwjgl/util/glu/Disk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaDisk");
|
||||
typeRedirects.put("org/lwjgl/util/glu/PartialDisk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaPartialDisk");
|
||||
|
||||
// Build constant pool scanner
|
||||
final List<String> stringsToSearch = new ArrayList<>(32);
|
||||
stringsToSearch.add(GL_PREFIX);
|
||||
stringsToSearch.addAll(typeRedirects.keySet());
|
||||
for (String key : methodRedirects.keySet()) {
|
||||
stringsToSearch.add(key);
|
||||
}
|
||||
cstPoolParser = new ClassConstantPoolParser(stringsToSearch.toArray(new String[0]));
|
||||
}
|
||||
|
||||
/** Core exclusions that GLSM always requires. */
|
||||
public String[] getCoreExclusions() {
|
||||
return CORE_EXCLUSIONS.clone();
|
||||
}
|
||||
|
||||
public boolean shouldTransform(byte[] basicClass) {
|
||||
return cstPoolParser.find(basicClass, true);
|
||||
}
|
||||
|
||||
/** @return Was the class changed? */
|
||||
public boolean transformClassNode(String transformedName, ClassNode cn) {
|
||||
boolean changed = false;
|
||||
final boolean isOpenGlHelper = transformedName.equals("net.minecraft.client.renderer.OpenGlHelper");
|
||||
|
||||
for (MethodNode mn : cn.methods) {
|
||||
if (isOpenGlHelper && (mn.name.equals("glBlendFunc") || mn.name.equals("func_148821_a"))) {
|
||||
continue;
|
||||
}
|
||||
boolean redirectInMethod = false;
|
||||
for (AbstractInsnNode node : mn.instructions.toArray()) {
|
||||
if (node instanceof TypeInsnNode tNode) {
|
||||
// Redirect NEW and CHECKCAST for quadric classes
|
||||
if (tNode.getOpcode() == Opcodes.NEW || tNode.getOpcode() == Opcodes.CHECKCAST) {
|
||||
final String redirect = typeRedirects.get(tNode.desc);
|
||||
if (redirect != null) {
|
||||
if (LOG_SPAM) {
|
||||
LOGGER.info("Redirecting {} in {} from {} to {}", tNode.getOpcode() == Opcodes.NEW ? "NEW" : "CHECKCAST", transformedName, tNode.desc, redirect);
|
||||
}
|
||||
tNode.desc = redirect;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} else if (node instanceof MethodInsnNode mNode) {
|
||||
if (mNode.desc.equals("(I)V") && mNode.owner.startsWith(GL_PREFIX) && (mNode.name.equals("glEnable") || mNode.name.equals("glDisable"))) {
|
||||
final AbstractInsnNode prevNode = node.getPrevious();
|
||||
String name = null;
|
||||
if (prevNode instanceof LdcInsnNode ldcNode) {
|
||||
name = glCapRedirects.get(((Integer) ldcNode.cst));
|
||||
} else if (prevNode instanceof IntInsnNode intNode) {
|
||||
name = glCapRedirects.get(intNode.operand);
|
||||
}
|
||||
if (name != null) {
|
||||
if (mNode.name.equals("glEnable")) {
|
||||
name = "enable" + name;
|
||||
} else {
|
||||
name = "disable" + name;
|
||||
}
|
||||
}
|
||||
if (LOG_SPAM) {
|
||||
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
|
||||
if (name == null) {
|
||||
LOGGER.info("Redirecting call in {} from {}.{}(I)V to GLStateManager.{}(I)V", transformedName, shortOwner, mNode.name, mNode.name);
|
||||
} else {
|
||||
LOGGER.info("Redirecting call in {} from {}.{}(I)V to GLStateManager.{}()V", transformedName, shortOwner, mNode.name, name);
|
||||
}
|
||||
}
|
||||
mNode.owner = GLStateManager;
|
||||
if (name != null) {
|
||||
mNode.name = name;
|
||||
mNode.desc = "()V";
|
||||
mn.instructions.remove(prevNode);
|
||||
}
|
||||
changed = true;
|
||||
redirectInMethod = true;
|
||||
} else if (mNode.name.equals("makeCurrent") && mNode.owner.startsWith(Drawable)) {
|
||||
mNode.setOpcode(Opcodes.INVOKESTATIC);
|
||||
mNode.owner = GLStateManager;
|
||||
mNode.desc = "(L" + Drawable + ";)V";
|
||||
mNode.itf = false;
|
||||
changed = true;
|
||||
if (LOG_SPAM) {
|
||||
LOGGER.info("Redirecting call in {} to GLStateManager.makeCurrent()", transformedName);
|
||||
}
|
||||
} else {
|
||||
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
|
||||
if (redirects != null) {
|
||||
final String glsmName = redirects.get(mNode.name);
|
||||
if (glsmName != null) {
|
||||
if (LOG_SPAM) {
|
||||
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
|
||||
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
|
||||
}
|
||||
mNode.owner = GLStateManager;
|
||||
mNode.name = glsmName;
|
||||
changed = true;
|
||||
redirectInMethod = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Redirect <init> calls for quadric classes
|
||||
if (mNode.getOpcode() == Opcodes.INVOKESPECIAL && mNode.name.equals("<init>")) {
|
||||
final String redirect = typeRedirects.get(mNode.owner);
|
||||
if (redirect != null) {
|
||||
if (LOG_SPAM) {
|
||||
LOGGER.info("Redirecting <init> in {} from {} to {}", transformedName, mNode.owner, redirect);
|
||||
}
|
||||
mNode.owner = redirect;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ASSERT_MAIN_THREAD && redirectInMethod && !transformedName.startsWith(SplashProgress) && !(transformedName.startsWith(MinecraftClient) && ExcludedMinecraftMainThreadChecks.contains(mn.name))) {
|
||||
mn.instructions.insert(new MethodInsnNode(Opcodes.INVOKESTATIC, GLStateManager, "assertMainThread", "()V", false));
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static final class RedirectMap<K> extends HashMap<K, K> {
|
||||
|
||||
public static RedirectMap<String> newMap() {
|
||||
return new RedirectMap<>();
|
||||
}
|
||||
|
||||
public RedirectMap<K> add(K name) {
|
||||
this.put(name, name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RedirectMap<K> add(K name, K newName) {
|
||||
this.put(name, newName);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,128 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
|
||||
|
||||
/**
|
||||
* A stack for boolean GL state with lazy copy-on-write optimization.
|
||||
* <p>
|
||||
* When using the global attrib depth tracking (from glPushAttrib/glPopAttrib), state is only
|
||||
* saved when actually modified, dramatically reducing overhead when most states
|
||||
* don't change (e.g., GL_ENABLE_BIT saves ~270 states but typically only 3-4 change).
|
||||
*/
|
||||
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
|
||||
|
||||
protected final boolean[] stack;
|
||||
|
||||
/**
|
||||
* The depth at which state has been saved. Compared against GLStateManager.getAttribDepth()
|
||||
* to determine if we need to save before modification or restore on pop.
|
||||
*/
|
||||
protected int savedDepth;
|
||||
|
||||
public BooleanStateStack(int glCap) {
|
||||
this(glCap, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a BooleanStateStack with a custom initial state.
|
||||
* Useful for GL states that default to true (e.g., GL_DITHER, GL_MULTISAMPLE).
|
||||
*
|
||||
* @param glCap GL capability constant
|
||||
* @param initialState initial enabled state
|
||||
*/
|
||||
public BooleanStateStack(int glCap, boolean initialState) {
|
||||
this(glCap, initialState, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a BooleanStateStack with custom initial state and FFP flag.
|
||||
*
|
||||
* @param glCap GL capability constant
|
||||
* @param initialState initial enabled state
|
||||
* @param ffpStateOnly when true, GL calls are skipped when FFP emulation is active
|
||||
*/
|
||||
public BooleanStateStack(int glCap, boolean initialState, boolean ffpStateOnly) {
|
||||
super(glCap, ffpStateOnly);
|
||||
this.enabled = initialState;
|
||||
stack = new boolean[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
}
|
||||
|
||||
// ==================== Traditional Stack Operations ====================
|
||||
|
||||
@Override
|
||||
public BooleanStateStack push() {
|
||||
if (savedDepth >= stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (savedDepth + 1) + " reached");
|
||||
}
|
||||
stack[savedDepth++] = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanStateStack pop() {
|
||||
if (savedDepth == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
final boolean oldValue = stack[--savedDepth];
|
||||
// Call setEnabledDirect to avoid triggering beforeModify during restore
|
||||
setEnabledDirect(oldValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return savedDepth == 0;
|
||||
}
|
||||
|
||||
// ==================== Lazy Copy-on-Write Operations ====================
|
||||
|
||||
@Override
|
||||
public int pushDepth() {
|
||||
// No-op: global depth is managed by GLStateManager
|
||||
return GLStateManager.getAttribDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore state if it was modified at the current depth.
|
||||
* Only called by GLStateManager.popState() for states that registered as modified.
|
||||
*/
|
||||
@Override
|
||||
public BooleanStateStack popDepth() {
|
||||
// We're only called if we were modified, so savedDepth should match
|
||||
if (savedDepth > 0) {
|
||||
final boolean oldValue = stack[--savedDepth];
|
||||
setEnabledDirect(oldValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before modifying state. If we haven't saved at the current depth yet,
|
||||
* save the current value and register with GLStateManager for restoration.
|
||||
*/
|
||||
@Override
|
||||
public void beforeModify() {
|
||||
final int globalDepth = GLStateManager.getAttribDepth();
|
||||
if (savedDepth < globalDepth) {
|
||||
stack[savedDepth++] = enabled;
|
||||
GLStateManager.registerModifiedState(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDepth() {
|
||||
return GLStateManager.getAttribDepth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
beforeModify();
|
||||
super.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private void setEnabledDirect(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LineState;
|
||||
|
||||
/**
|
||||
* Stack for GL line state (width, stipple factor, stipple pattern).
|
||||
* Used by GL_LINE_BIT push/pop attrib.
|
||||
*/
|
||||
public class LineStateStack extends LineState implements IStateStack<LineStateStack> {
|
||||
|
||||
protected final LineState[] stack;
|
||||
protected int pointer;
|
||||
|
||||
public LineStateStack() {
|
||||
stack = new LineState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new LineState();
|
||||
}
|
||||
}
|
||||
|
||||
public LineStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LineStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PointState;
|
||||
|
||||
/**
|
||||
* Stack for GL point state (size).
|
||||
* Used by GL_POINT_BIT push/pop attrib.
|
||||
*/
|
||||
public class PointStateStack extends PointState implements IStateStack<PointStateStack> {
|
||||
|
||||
protected final PointState[] stack;
|
||||
protected int pointer;
|
||||
|
||||
public PointStateStack() {
|
||||
stack = new PointState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new PointState();
|
||||
}
|
||||
}
|
||||
|
||||
public PointStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PointStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
}
|
||||
+14
-18
@@ -2,41 +2,37 @@ package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
|
||||
|
||||
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
|
||||
|
||||
protected final BooleanState[] stack;
|
||||
/**
|
||||
* Stack for GL polygon state (mode, offset, cull face mode, front face).
|
||||
* Used by GL_POLYGON_BIT push/pop attrib.
|
||||
*/
|
||||
public class PolygonStateStack extends PolygonState implements IStateStack<PolygonStateStack> {
|
||||
|
||||
protected final PolygonState[] stack;
|
||||
protected int pointer;
|
||||
|
||||
public BooleanStateStack(int glCap) {
|
||||
super(glCap);
|
||||
stack = new BooleanState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public PolygonStateStack() {
|
||||
stack = new PolygonState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new BooleanState(glCap);
|
||||
stack[i] = new PolygonState();
|
||||
}
|
||||
}
|
||||
|
||||
public BooleanStateStack push() {
|
||||
if(pointer == stack.length) {
|
||||
public PolygonStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BooleanStateStack pop() {
|
||||
if(pointer == 0) {
|
||||
public PolygonStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
if (GLStateManager.isAMD() && glCap == GL11.GL_BLEND) {
|
||||
setEnabled(enabled);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.StencilState;
|
||||
|
||||
/**
|
||||
* Stack for GL stencil buffer state.
|
||||
* Used by GL_STENCIL_BUFFER_BIT push/pop attrib.
|
||||
*/
|
||||
public class StencilStateStack extends StencilState implements IStateStack<StencilStateStack> {
|
||||
|
||||
protected final StencilState[] stack;
|
||||
protected int pointer;
|
||||
|
||||
public StencilStateStack() {
|
||||
stack = new StencilState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new StencilState();
|
||||
}
|
||||
}
|
||||
|
||||
public StencilStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StencilStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+58
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
/**
|
||||
* BooleanStateStack for per-texture-unit state.
|
||||
* Switches to correct unit (raw GL) before enable/disable calls.
|
||||
*/
|
||||
public class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
private final int unitIndex;
|
||||
|
||||
public TextureUnitBooleanStateStack(int glCap, int unitIndex) {
|
||||
super(glCap, false, true); // All texture enable/disable states are FFP-only
|
||||
this.unitIndex = unitIndex;
|
||||
}
|
||||
|
||||
public TextureUnitBooleanStateStack(int glCap, int unitIndex, boolean initialState) {
|
||||
super(glCap, initialState, true);
|
||||
this.unitIndex = unitIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
beforeModify();
|
||||
setEnabledWithUnitSwitch(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureUnitBooleanStateStack popDepth() {
|
||||
if (savedDepth > 0) {
|
||||
final boolean oldValue = stack[--savedDepth];
|
||||
setEnabledWithUnitSwitch(oldValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setEnabledWithUnitSwitch(boolean enabled) {
|
||||
final boolean bypass = GLStateManager.shouldBypassCache();
|
||||
if (bypass || enabled != this.enabled) {
|
||||
if (!bypass) this.enabled = enabled;
|
||||
|
||||
if (!ffpStateOnly) {
|
||||
final int currentUnit = GLStateManager.getActiveTextureUnit();
|
||||
final boolean needsSwitch = currentUnit != unitIndex;
|
||||
|
||||
if (needsSwitch) BackendManager.RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + unitIndex);
|
||||
if (enabled) BackendManager.RENDER_BACKEND.enable(glCap); else BackendManager.RENDER_BACKEND.disable(glCap);
|
||||
if (needsSwitch) BackendManager.RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + currentUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getUnitIndex() {
|
||||
return unitIndex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* Stack for a Vector3f value (e.g. current normal).
|
||||
*/
|
||||
public class Vec3fStack implements IStateStack<Vec3fStack> {
|
||||
|
||||
private final Vector3f value;
|
||||
private final float[][] stack;
|
||||
private int pointer;
|
||||
|
||||
public Vec3fStack(Vector3f value) {
|
||||
this.value = value;
|
||||
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3fStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer][0] = value.x;
|
||||
stack[pointer][1] = value.y;
|
||||
stack[pointer][2] = value.z;
|
||||
pointer++;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3fStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
pointer--;
|
||||
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2]);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
/**
|
||||
* Stack for a Vector4f value (e.g. current texture coordinates).
|
||||
*/
|
||||
public class Vec4fStack implements IStateStack<Vec4fStack> {
|
||||
|
||||
private final Vector4f value;
|
||||
private final float[][] stack;
|
||||
private int pointer;
|
||||
|
||||
public Vec4fStack(Vector4f value) {
|
||||
this.value = value;
|
||||
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec4fStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer][0] = value.x;
|
||||
stack[pointer][1] = value.y;
|
||||
stack[pointer][2] = value.z;
|
||||
stack[pointer][3] = value.w;
|
||||
pointer++;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec4fStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
pointer--;
|
||||
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2], stack[pointer][3]);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+1
-1
@@ -7,7 +7,7 @@ import org.lwjgl.opengl.GL11;
|
||||
@Getter
|
||||
public class AlphaState implements ISettableState<AlphaState> {
|
||||
@Setter protected int function = GL11.GL_ALWAYS;
|
||||
@Setter protected float reference = -1.0F;
|
||||
@Setter protected float reference = 0.0F;
|
||||
|
||||
@Override
|
||||
public AlphaState set(AlphaState state) {
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
|
||||
@Getter @Setter
|
||||
public class BlendState implements ISettableState<BlendState> {
|
||||
protected int srcRgb = GL11.GL_ONE;
|
||||
protected int dstRgb = GL11.GL_ZERO;
|
||||
protected int srcAlpha = GL11.GL_ONE;
|
||||
protected int dstAlpha = GL11.GL_ZERO;
|
||||
protected int equationRgb = GL14.GL_FUNC_ADD;
|
||||
protected int equationAlpha = GL14.GL_FUNC_ADD;
|
||||
protected float blendColorR = 0.0f;
|
||||
protected float blendColorG = 0.0f;
|
||||
protected float blendColorB = 0.0f;
|
||||
protected float blendColorA = 0.0f;
|
||||
|
||||
public BlendState() {}
|
||||
|
||||
public BlendState(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
this.srcRgb = srcRgb;
|
||||
this.dstRgb = dstRgb;
|
||||
this.srcAlpha = srcAlpha;
|
||||
this.dstAlpha = dstAlpha;
|
||||
}
|
||||
|
||||
public void setAll(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
this.srcRgb = srcRgb;
|
||||
this.dstRgb = dstRgb;
|
||||
this.srcAlpha = srcAlpha;
|
||||
this.dstAlpha = dstAlpha;
|
||||
}
|
||||
|
||||
public void setSrcDstRgb(int srcRgb, int dstRgb) {
|
||||
this.srcRgb = srcRgb;
|
||||
this.dstRgb = dstRgb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlendState set(BlendState state) {
|
||||
this.srcRgb = state.srcRgb;
|
||||
this.dstRgb = state.dstRgb;
|
||||
this.srcAlpha = state.srcAlpha;
|
||||
this.dstAlpha = state.dstAlpha;
|
||||
this.equationRgb = state.equationRgb;
|
||||
this.equationAlpha = state.equationAlpha;
|
||||
this.blendColorR = state.blendColorR;
|
||||
this.blendColorG = state.blendColorG;
|
||||
this.blendColorB = state.blendColorB;
|
||||
this.blendColorA = state.blendColorA;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(Object state) {
|
||||
if (this == state) return true;
|
||||
if (!(state instanceof BlendState blendState)) return false;
|
||||
return srcRgb == blendState.srcRgb && dstRgb == blendState.dstRgb
|
||||
&& srcAlpha == blendState.srcAlpha && dstAlpha == blendState.dstAlpha
|
||||
&& equationRgb == blendState.equationRgb && equationAlpha == blendState.equationAlpha
|
||||
&& Float.compare(blendColorR, blendState.blendColorR) == 0
|
||||
&& Float.compare(blendColorG, blendState.blendColorG) == 0
|
||||
&& Float.compare(blendColorB, blendState.blendColorB) == 0
|
||||
&& Float.compare(blendColorA, blendState.blendColorA) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlendState copy() {
|
||||
return new BlendState().set(this);
|
||||
}
|
||||
}
|
||||
+25
-6
@@ -4,13 +4,26 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
public class BooleanState implements ISettableState<BooleanState> {
|
||||
protected final int glCap;
|
||||
protected final boolean ffpStateOnly;
|
||||
|
||||
@Getter protected boolean enabled;
|
||||
protected boolean stateUnknown;
|
||||
|
||||
public BooleanState(int glCap) {
|
||||
this(glCap, false);
|
||||
}
|
||||
|
||||
public BooleanState(int glCap, boolean ffpStateOnly) {
|
||||
this.glCap = glCap;
|
||||
this.ffpStateOnly = ffpStateOnly;
|
||||
}
|
||||
|
||||
public void setUnknownState() {
|
||||
stateUnknown = true;
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
@@ -22,12 +35,18 @@ public class BooleanState implements ISettableState<BooleanState> {
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (GLStateManager.shouldBypassCache() || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.isAMD() && GLStateManager.isPoppingAttributes())) {
|
||||
this.enabled = enabled;
|
||||
if (enabled) {
|
||||
GL11.glEnable(this.glCap);
|
||||
} else {
|
||||
GL11.glDisable(this.glCap);
|
||||
final boolean bypass = GLStateManager.shouldBypassCache();
|
||||
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
|
||||
stateUnknown = false;
|
||||
if (!bypass) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
if (!ffpStateOnly) {
|
||||
if (enabled) {
|
||||
RENDER_BACKEND.enable(this.glCap);
|
||||
} else {
|
||||
RENDER_BACKEND.disable(this.glCap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
/**
|
||||
* Stores eye-space clip plane equations for core profile emulation.
|
||||
*/
|
||||
public class ClipPlaneState {
|
||||
private final float[][] eyePlanes = new float[GLStateManager.MAX_CLIP_PLANES][4];
|
||||
|
||||
private static final Vector4f tempPlane = new Vector4f();
|
||||
private static final Matrix4f tempInverse = new Matrix4f();
|
||||
|
||||
public void setPlane(int index, double a, double b, double c, double d, Matrix4f modelView) {
|
||||
final float[] dest = eyePlanes[index];
|
||||
modelView.invert(tempInverse);
|
||||
tempPlane.set((float) a, (float) b, (float) c, (float) d);
|
||||
tempInverse.transformTranspose(tempPlane);
|
||||
dest[0] = tempPlane.x;
|
||||
dest[1] = tempPlane.y;
|
||||
dest[2] = tempPlane.z;
|
||||
dest[3] = tempPlane.w;
|
||||
}
|
||||
|
||||
/** Writes the 4-component eye-space plane equation into {@code buf} at its current position. */
|
||||
public void putEyePlane(int index, FloatBuffer buf) {
|
||||
final float[] p = eyePlanes[index];
|
||||
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+7
@@ -6,6 +6,13 @@ public class ColorMask implements ISettableState<ColorMask> {
|
||||
public boolean blue = true;
|
||||
public boolean alpha = true;
|
||||
|
||||
public void setAll(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
this.red = red;
|
||||
this.green = green;
|
||||
this.blue = blue;
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorMask set(ColorMask state) {
|
||||
this.red = state.red;
|
||||
Loaded 100 of 1138 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user