Compare commits

..
122 Commits
Author SHA1 Message Date
sisyphussy 21c01a1aa4 Fix terrible performance on Intel (#1718) 2026-05-03 14:32:58 -07:00
Caedis 83a26b2291 Add Cosmetic Armor support for Dynamic Lights (#1708) 2026-05-03 14:31:49 -07:00
Kotmatross 7e44c1efcb Fix stale uniforms when switching shader program (#1715) 2026-05-03 14:30:46 -07:00
VortexSo4 155f209db5 remove ctm guide as it is worse than wiki (#1706) 2026-04-30 11:03:18 -07:00
SpacEagle17 c4a6c165a0 Increase MAX_COLOR_BUFFERS limit to 32 (#1704) 2026-04-28 12:33:51 -07:00
Eclipse-Sol 624fe6b453 Add targeting of snow-covered blocks. (#1696) 2026-04-26 15:26:10 -07:00
YoshyandJason Mitchell fd45fda0ab Fix AO Lighting for compact CTM (#1697)
Co-authored-by: Jason Mitchell <[email protected]>
2026-04-26 12:55:24 -07:00
Alexdoru 00fc97a611 Fix dynamic lights world client leaks (#1700) 2026-04-26 10:01:46 -07:00
Alexdoru 445dc487b9 fix world client leak caused by lambda in IrisExclusiveUniforms$WorldInfoUniforms (#1701) 2026-04-26 09:56:08 -07:00
Yoshy f43fbd4d9f Fix CTM to not spread to nearby TEs (#1695) 2026-04-25 13:09:38 -07:00
Jason Mitchell dbabc4b3dc Maybe fix DragonAPI on OSX - take two (#1689)
* Maybe fix DragonAPI on OSX - take two
* Bind a default VAO on the SharedDrawable
* Mixin that tries to swap out glGetProgramInfoLog when appropriate

* no direct gl calls
2026-04-21 15:12:02 -07:00
Jason Mitchell e01097264b Fix infinite extent AABB crash w/ kotlin, and ensure proper sizing/padding on VAO/VBO snapshots. (#1687) 2026-04-20 10:11:46 -07:00
Jason Mitchell 0a922f3129 AE2 Crash Fix (#1684)
* Snapshot all relevant data into display lists (Avoids referencing stale/recycled data and a crash on Nvidia)
* Route deletes through GLStateManager
2026-04-19 08:19:02 -07:00
Eclipse-Sol 4a2dcd0b22 Automate the collection of legacy IDs for very old shaderpacks. (#1631) 2026-04-18 09:53:06 -07:00
Eclipse-Sol 0db691db0b Add FlatteningMap and EntityFlatteningMap (#1635) 2026-04-18 09:20:24 -07:00
danyadev e5cd1372e7 always request 0.9.0+ GTNHLib version when a player has an outdated version (#1672) 2026-04-18 09:18:28 -07:00
Kotmatross fbd07cc575 NTM:Space compat rework and update (#1675) 2026-04-18 09:14:49 -07:00
George Paton 91f2cf9dfb Add "Mods Only" Dynamic FOV option (#1680) 2026-04-18 09:13:39 -07:00
Kaiz 34a6967c71 fix(iris): implement full GL alpha test (#1673) 2026-04-18 09:12:46 -07:00
Alexdoru 9c295a1e22 Fix cached item in RenderItemFrame leaking both world client and server (#1659) 2026-04-13 15:04:36 -07:00
DarkShadow44 9fd02900ef Load modern block properties (#1626) 2026-04-13 15:03:23 -07:00
Jason Mitchell 27e9a4d4fd Fix class circularity error with early redirector (#1670) 2026-04-13 15:02:26 -07:00
Darren Eberly dfc08053d5 Don't advance buffer positions for Material shininess in GLSM (#1668) 2026-04-12 22:25:25 -07:00
VortexSo4 1af03fa369 add CTM guide (#1667) 2026-04-11 17:49:38 -07:00
NealDeal34 3ae3b2527d Update zh_CN.lang (#1623)
* Update zh_CN.lang

* update zh_CN.lang for dynamic lights
2026-04-07 08:21:34 +02:00
Jason Mitchell fd2a14a000 Various fixes (#1652) 2026-04-06 17:35:15 -07:00
DarkShadow44 9f0f6a6232 Always render clouds late for shaders (#1649) 2026-04-06 09:52:34 -07:00
DarkShadow44 2942c7eb84 Disable clouds when shaders are active (#1648) 2026-04-06 09:52:21 -07:00
Alexdoru 47762f921e fix two world client memory leaks (#1650) 2026-04-06 16:38:03 +00:00
Jason Mitchell bdc4431760 Fix some issues someone was having with nixos (#1647) 2026-04-05 16:21:58 -07:00
DarkShadow44 7e1beadfb5 DH: Fix mismatch from upstream for solas shaders (#1645) 2026-04-04 17:27:19 -07:00
Jason Mitchell 59a4b84023 Standalone GLSM mod - Umbra (#1642) 2026-04-03 21:51:35 -07:00
Jason Mitchell 8d53e25e0a Compact CTM Fix (#1640) 2026-04-03 16:48:08 -07:00
VortexSo4 bbf83db379 Implement compact ctm (#1636) 2026-04-02 22:32:28 +00:00
Jason Mitchell ec82280b9b Match OpenGL Buffer Behavior for glGet (#1637) 2026-04-02 14:20:06 -07:00
DarkShadow44andJason Mitchell d7588e4c14 Adapt shader block id override for Angelica 2.x (#1601)
Co-authored-by: Jason Mitchell <[email protected]>
2026-04-02 14:18:55 -07:00
Jason Mitchell 54c99386d4 Do a similar fix as MixinDroppedItemGlintEdges to held items (#1632) 2026-03-31 18:31:21 -07:00
DarkShadow44 465abf889a Pass mc_Entity like upstream does (#1627) 2026-03-31 16:18:35 -07:00
DarkShadow44 4acb83be6a Port shadow translucent (aka shadow_water) from upstream (#1628) 2026-03-31 16:09:25 -07:00
DarkShadow44 80f9724ee6 Handle GL_QUAD_STRIP in legacy conversion (#1629) 2026-03-31 15:57:57 -07:00
Jason Mitchell 8cf55bb216 Safely handle off client thread crash reports/glGetString (#1630) 2026-03-31 15:49:47 -07:00
Eclipse-Sol 3ebc8868c7 Fix item reset hopefully for the last time (#1621) 2026-03-30 19:32:47 -07:00
JohnButlerThird 0938e6d975 Only run the logic on bop grass (fix my mod bugging out) (#1620) 2026-03-30 18:17:42 -07:00
Eclipse-Sol d3022a40d2 Create new deferred overlay system to fix z-fighting issues (#1605) 2026-03-30 15:50:18 -07:00
Eclipse-Sol 2645c803f9 Better horse support (#1599) 2026-03-30 15:41:53 -07:00
Jason Mitchell d91c356a99 Shader fixes (#1619) 2026-03-30 15:34:54 -07:00
Jason Mitchell 189ff4a80f Defer client attributes. They're uploaded/handled elsewhere. Should fix a crash on j8/lwjgl2 (#1618) 2026-03-30 09:58:40 -07:00
Jason Mitchell ffb4ca9472 Get rid of some allocations, and use immutable variants for the suppliers (#1612) 2026-03-30 09:58:27 -07:00
Jason Mitchell 7d8c8d306d More Double overloads (#1613) 2026-03-29 15:09:13 -07:00
Kotmatross 117ff0cacf Fix other dynamic uniforms not updating properly (#1610) 2026-03-29 12:53:09 -07:00
Jason Mitchell 83fdd57534 Narrow another overly broad transformer exclusion 2026-03-29 09:24:50 -07:00
Kaiz 951be04e9a fix: correctly handle cross-chunk TileEntity render bounds 2026-03-29 09:20:13 -07:00
Jason Mitchell 0ad69f3433 FINE, we'll work with java8/forge. 2026-03-28 20:09:42 -07:00
Fabian Maurer 0dabecc784 Handle GL_POLYGON in legacy conversion 2026-03-28 14:41:23 -07:00
Jason Mitchell 9b3fad4c6d Ensure sufficient coverage + optimize the check 2026-03-28 13:16:54 -07:00
Kaiz e1e8256861 fix: crash when client-side vertex attrib pointers are used in core profile 2026-03-28 13:16:54 -07:00
Jason Mitchell e65c01c2a3 Handle method handles in invokedynamic 2026-03-28 12:46:19 -07:00
Jason Mitchell b9de4df18e Update alpha test to hopefully work with RTM and AE2 (#1597) 2026-03-28 09:40:10 -07:00
Kotmatross a5d31cd1b6 Return phaseChangeNotifier (#1596) 2026-03-28 09:15:46 -07:00
DarkShadow44 29f8d718cc Pass texturemap to composite shader and honor texture replacements (#1589) 2026-03-27 19:07:45 -07:00
12411234 52b0db2566 Fix missing glGetTexImage method crash when using XaerosWorldMap (#1592) 2026-03-27 19:02:51 -07:00
Eclipse-Sol e9eb45f4f2 Centers the Wither Armor (#1590) 2026-03-27 18:37:17 -07:00
Jason Mitchell 92e28a4cd5 Blur Compat (#1593) 2026-03-27 18:32:24 -07:00
Jason Mitchell f46a86889c Fix crash with fancy block particles. (#1591) 2026-03-27 17:52:14 -07:00
Jason MitchellandAlexdoru c827d446d5 External Redirector API (#1580)
Co-authored-by: Alexdoru <[email protected]>
2026-03-27 11:20:29 -07:00
Eclipse-Sol 00ef355ce8 Fix sky lighting on drawCrossSquare rendered blocks. (#1576) 2026-03-25 18:58:40 -07:00
Kotmatross 1282056fff Fix blend leak in BatchingFontRenderer (#1575) 2026-03-25 15:34:43 -07:00
Alexdoru 6c422d47a2 Make the mod no-op on server (#1574) 2026-03-25 12:59:23 -07:00
DarkShadow44 057ea8035c Distant Horizons shader support (#1553) 2026-03-25 10:48:14 -07:00
Jason Mitchell 3ea47c8385 Various fixes (#1571) 2026-03-24 22:17:40 -07:00
Eclipse-Sol 306d732d81 Fixes 3rd person enchant glints z-fighting with the item model (#1569) 2026-03-24 17:24:23 -07:00
Jason Mitchell a483669579 Maybe fix botania mana pool rendering 2026-03-23 21:09:33 -07:00
Eclipse-SolandJason Mitchell 53996500d7 Fix modded enchant glints not being animated (#1566)
Co-authored-by: Jason Mitchell <[email protected]>
2026-03-23 22:41:29 -04:00
Jason Mitchell 04dd8ce6c7 narrow scope of mixin per Eclipse 2026-03-23 19:24:16 -07:00
Jason Mitchell 8c9edfa4c3 Fix alpha test 2026-03-23 19:24:16 -07:00
Jason Mitchell 3f44195756 Change compat config 2026-03-23 09:27:56 -07:00
Jason Mitchell c98f056d74 ASM Hook Compat
* Adds a config to allow disabling
* Narrows Advanced LightSabers
* Narrows Alfheim (unreleased versions have compat, might make this version restricted)
2026-03-23 09:00:24 -07:00
Jason Mitchell 7e25f5d03e tweak wording 2026-03-22 18:41:58 -07:00
Jason Mitchell b5553654d0 More pass throughs 2026-03-22 18:41:58 -07:00
Jason Mitchell a3fffb47be Ban GL usage.
* Everything must go through GLSM/Render Backend
2026-03-22 11:56:08 -07:00
Jason Mitchell 09cc557be0 Clear cache on reload shaders 2026-03-22 11:07:37 -07:00
SpacEagle17 aff5ff7e7b Add a second profiles button to Angelica 2026-03-22 10:09:23 -07:00
Jason Mitchell e7d08a53b6 Add debug context 2026-03-22 10:08:49 -07:00
Jason Mitchell 3b2a0af5c4 Add missing overrides 2026-03-22 10:08:49 -07:00
Jason Mitchell 8c44e79990 Add always translucent config for brandyn 2026-03-22 10:08:49 -07:00
Jason Mitchell d2e491ed47 Misc fixes
* Add missing glMapBuffer overload
* Keep track of bound pixel buffer and suspend when needed
2026-03-21 22:07:15 -07:00
Jason Mitchell bccc5b00ca Supernova Support 2026-03-21 22:06:38 -07:00
Jason Mitchellandbrandyn 5ed26f3af7 Fix font alpha test transparency and BlockGrass subclass rendering with Celeritas AO
Co-Authored-By: brandyn <[email protected]>
2026-03-21 11:21:39 -07:00
DarkShadow44 9e7d19b883 Add missing DH lwjgl3 functions (#1546) 2026-03-21 10:56:19 -07:00
Jason Mitchell 5f61e387fb Use GLSM instead of relying on the redirector 2026-03-21 10:56:19 -07:00
Jason Mitchell 70cfa2ae90 Swappable RenderBackend
* Adds Lwjgl2GLRenderBackend & Lwjgl3GLRenderBackend
* Lwjgl3GLRenderBackend uses lwjgl3 natively w/o lwjgl3ify
* GLSMRedirector now redirects LWJGL3 methods as well for mods with a hard dep on lwjgl3 (distant horizons standalone)
* GLSM uses RENDER_BACKEND instead of GL directly
2026-03-21 10:56:19 -07:00
Jason Mitchell 37717bd4db GLSM Extraction (#1527) 2026-03-19 22:53:47 -07:00
Eclipse-Sol b179340a0f Fix mob's eyes from z-fighting (#1519) 2026-03-19 09:33:52 -07:00
Alexdoru 465e36148c use mixin to change the f3 text background instead of canceling the event (#1537) 2026-03-19 09:32:56 -07:00
Caedis a3f0efd629 Make fog controls work with BoP fog (#1539) 2026-03-19 09:24:40 -07:00
Death Fuel 2386d630c5 Update FragmentShaderGenerator.java (#1526) 2026-03-16 17:35:01 -07:00
Alexdoru 6a784d8d3b use classnode name in CeleritasBlockTransform (#1521) 2026-03-15 15:49:41 +01:00
Jason Mitchell fe9028d300 Log once instead of every time. (#1520) 2026-03-14 19:15:06 -07:00
Death Fuel 4c5e5df290 Replace compatibility mode texture formats (#1518) 2026-03-14 18:42:26 -07:00
Jason Mitchell 39ee364418 Improvements (#1516) 2026-03-14 15:06:30 -07:00
sisyphussy 4c9cae5e0b Optimize Streaming (#1515) 2026-03-14 13:13:40 -07:00
DarkShadow44 11b53c9f8f Add glTexImage3D with IntBuffer (#1506) 2026-03-11 11:55:47 -07:00
Jason Mitchell c769a08a4e Fix matching across newlines (#1505) 2026-03-11 09:52:21 -07:00
Jason Mitchell 90dac9f39d Add some missing glGetTexEnv* (#1503) 2026-03-10 19:18:58 -07:00
Jason Mitchell c5c4b290b1 Add missing overload (#1495) 2026-03-10 11:50:58 -07:00
Jason Mitchell 4b46de4566 Botania fix take two (#1501) 2026-03-10 10:22:06 -07:00
Jason Mitchell 9f2a71be4e Add missing overloads (#1500) 2026-03-10 10:06:12 -07:00
Jason Mitchell 79fe08417c Fix overwrite visibility crash (#1499) 2026-03-10 09:45:50 -07:00
Jason Mitchell 5de64abdef Maybe make Xaero work on j8 (#1494) 2026-03-09 19:47:41 -07:00
Death Fuel 2e07465166 UI Fix (#1492) 2026-03-09 16:22:44 -07:00
Death Fuel 2b610b9aea Better font rendering MC parity + book mode (#1489) 2026-03-09 11:49:33 -07:00
MalTeeez ab192ddd2a Split MixinRenderManager into two for DAPI compat (#1480) 2026-03-08 06:07:01 +01:00
Jason Mitchell cb5e2ed7d4 Lazy init entity lighting maps (#1476) 2026-03-07 14:01:55 -08:00
ah-OOG-ah e914ef7563 Actually sleep in FPS limiter (#1471) 2026-03-07 12:20:28 -08:00
Jason Mitchell 748dbd55a1 So secret you can't see them. (#1475) 2026-03-07 11:48:24 -08:00
Jason Mitchell 140c0c2285 Snapshot VAO/VBO data during display list compilation, like mesa does. (#1473) 2026-03-07 11:17:08 -08:00
sisyphussy af26442b98 Fix intel tests (#1442) 2026-03-07 10:12:56 -08:00
danyadevandAlexdoru ef07ee9f77 Refactor CeleritasBlockTransform (#1455)
Co-authored-by: Alexdoru <[email protected]>
2026-03-07 17:26:39 +01:00
Jason Mitchell 1b8087e8a3 Emulate glLineWidth on core using geom shaders (#1468) 2026-03-06 19:05:06 -08:00
Jason Mitchell 9107ea9ae5 Fix load -> enter -> leave: panorama rendering, cleanup state & fix test (#1467) 2026-03-06 19:01:43 -08:00
Jason Mitchell 938a6bda90 Replace panorama with modern opengl equivalent (#1466) 2026-03-06 16:01:27 -08:00
Jason Mitchell e9f6f72741 More core profile followons (#1464) 2026-03-06 15:50:30 -08:00
551 changed files with 24990 additions and 6880 deletions

No files matched your search

+2 -2
View File
@@ -3,8 +3,8 @@
/.idea/
/.vs/
/.vscode/
/run/
/build/
run/
build/
/eclipse/
.classpath
.project
+32 -4
View File
@@ -7,6 +7,8 @@ plugins {
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")
@@ -19,8 +21,7 @@ tasks.test {
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"
val isMacOsArm64 = System.getProperty("os.name").lowercase().contains("mac") && System.getProperty("os.arch") == "aarch64"
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (isMacOsArm64) {
@@ -47,22 +48,49 @@ tasks.register<Copy>("copyDependencies") {
}
val embedOnly: Configuration by configurations
val shadowImplementation: Configuration by configurations
// Downgrade embedOnly jars for the tests
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)
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")
}
+23 -5
View File
@@ -47,6 +47,8 @@ configurations {
// 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
@@ -54,6 +56,7 @@ configurations {
compileClasspath.extendsFrom(embedOnly)
testCompileClasspath.extendsFrom(embedOnly)
testRuntimeClasspath.extendsFrom(embedOnly)
}
@@ -66,29 +69,36 @@ dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.42")
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.33:dev")
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-26.g5ee7d96-GTNH") {
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-32.g7dd88a4-GTNH") {
exclude module: "antlr4" // we only want to shadow the runtime module
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.2-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.2-GTNH") { transitive = false }
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.8.35-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
//devOnlyNonPublishable('com.github.GTNewHorizons:worldedit-gtnh:v0.0.9')
// Notfine Deps
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev")
@@ -111,12 +121,15 @@ dependencies {
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5593_H261"))
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5663_H261"))
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.3-GTNH:dev")
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
@@ -127,6 +140,9 @@ dependencies {
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.1-GTNH:dev")
// Distant Horizons
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
afterEvaluate {
dependencies {
testImplementation(platform('org.junit:junit-bom:5.14.1'))
@@ -137,6 +153,8 @@ dependencies {
testImplementation('org.junit.platform:junit-platform-engine')
testImplementation('org.junit.platform:junit-platform-reporting')
testImplementation('org.junit.platform:junit-platform-launcher')
testImplementation('com.tngtech.archunit:archunit-junit5:1.4.1')
}
}
+197
View File
@@ -0,0 +1,197 @@
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:5.0.3")
// 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.ow2.asm:asm-tree:9.7")
testImplementation("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.apache.logging.log4j:log4j-core:${property("log4jVersion")}")
testRuntimeOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:${property("celeritasVersion")}") { isTransitive = false }
testRuntimeOnly("xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:${property("jvmDowngraderVersion")}:downgraded-8")
testRuntimeOnly("org.apache.commons:commons-lang3:${property("commonsLang3Version")}")
}
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)
if (System.getProperty("os.name").lowercase().contains("mac")
&& System.getProperty("os.arch") == "aarch64") {
vendor = JvmVendorSpec.AZUL
}
}
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")
}
}
}
}
}
+14
View File
@@ -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
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import net.minecraft.launchwrapper.Launch;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
@@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Compat transformation for mod shaders running under core profile.
@@ -39,13 +39,11 @@ import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
*/
public class CompatShaderTransformer {
private static final Pattern VERSION_PATTERN =
Pattern.compile("#version\\s+(\\d+)(?:\\s+(\\w+))?");
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);
private static final Pattern DEFINE_PATTERN = Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
/** Compat builtins that trigger AST transformation. */
/** 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",
@@ -69,14 +67,9 @@ public class CompatShaderTransformer {
private static final Path DUMP_DIR;
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
static {
boolean isDev = false;
try {
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
isDev = Boolean.TRUE.equals(deobfEnv);
} catch (Exception ignored) {}
DUMP_DIR = (isDev || Boolean.parseBoolean(System.getProperty("angelica.compat.dumpShaders", "false")))
? Paths.get("compat_shaders") : null;
DUMP_DIR = Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false")) ? Paths.get("compat_shaders") : null;
}
private static final int CACHE_SIZE = 32;
@@ -84,9 +77,9 @@ public class CompatShaderTransformer {
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;
}
});
return size() > CACHE_SIZE;
}
});
public static void clearCache() {
cache.clear();
@@ -112,7 +105,7 @@ public class CompatShaderTransformer {
result = transformInternal(source, isFragment);
cache.put(key, result);
} catch (Exception e) {
LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
result = fixupVersion(source);
}
}
@@ -139,7 +132,7 @@ public class CompatShaderTransformer {
declaredVersion = Integer.parseInt(versionMatcher.group(1));
}
final int targetVersion = Math.max(declaredVersion, 330);
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);
@@ -160,22 +153,20 @@ public class CompatShaderTransformer {
// 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("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("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");
}
@@ -263,7 +254,9 @@ public class CompatShaderTransformer {
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))");
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))");
}
/**
@@ -306,10 +299,9 @@ public class CompatShaderTransformer {
transformer.injectVariable("uniform float angelica_FogEnd;");
transformer.injectVariable("uniform vec4 angelica_FogColor;");
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
transformer.injectFunction(
"angelica_FogParameters angelica_Fog = angelica_FogParameters("
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
transformer.injectFunction("angelica_FogParameters angelica_Fog = angelica_FogParameters("
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
}
/**
@@ -356,7 +348,7 @@ public class CompatShaderTransformer {
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
} catch (IOException e) {
LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
}
}
@@ -7,20 +7,20 @@ 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.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
/**
* Per-program compat uniform location cache and upload manager.
*
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader programs.
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader
* programs.
*/
public class CompatUniformManager {
@@ -39,10 +39,11 @@ public class CompatUniformManager {
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;
static final int LOC_IRIS_TEXTURE_MATRIX = 14;
static final int LOC_ALPHA_TEST_REF = 15;
static final int LOC_SCENE_COLOR = 16;
static final int LOC_CLIP_PLANES = 17;
static final int LOC_CLIP_PLANES_ENABLED = 18;
// Light source fields: 12 per light × 2 lights = 24 locations
static final int LIGHT_FIELDS = 12;
@@ -50,14 +51,14 @@ public class CompatUniformManager {
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;
static final int LOC_LIGHT_BASE = 19;
// 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_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 43
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 47
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 48
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
@@ -69,6 +70,7 @@ public class CompatUniformManager {
};
private static final String[] UNIFORM_NAMES;
static {
UNIFORM_NAMES = new String[LOC_COUNT];
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
@@ -85,14 +87,14 @@ public class CompatUniformManager {
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_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
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];
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] = "angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
}
}
for (int fi = 0; fi < MAT_FIELDS; fi++) {
@@ -113,6 +115,7 @@ public class CompatUniformManager {
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);
@@ -134,13 +137,13 @@ public class CompatUniformManager {
boolean hasAny = false;
for (int i = 0; i < LOC_COUNT; i++) {
locs[i] = GL20.glGetUniformLocation(program, UNIFORM_NAMES[i]);
locs[i] = RENDER_BACKEND.getUniformLocation(program, UNIFORM_NAMES[i]);
if (locs[i] != -1) hasAny = true;
}
if (hasAny) {
programLocations.put(program, locs);
LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
@@ -202,10 +205,10 @@ public class CompatUniformManager {
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
mv.get(mat4Buf);
if (locs[LOC_MODELVIEW] != -1) {
GL20.glUniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW] != -1) {
GL20.glUniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
}
}
@@ -213,7 +216,7 @@ public class CompatUniformManager {
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
GL20.glUniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
@@ -221,10 +224,10 @@ public class CompatUniformManager {
mv.normal(normalMatrix);
normalMatrix.get(mat3Buf);
if (locs[LOC_NORMAL] != -1) {
GL20.glUniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
}
if (locs[LOC_IRIS_NORMAL] != -1) {
GL20.glUniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
}
@@ -234,10 +237,10 @@ public class CompatUniformManager {
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
proj.get(mat4Buf);
if (locs[LOC_PROJECTION] != -1) {
GL20.glUniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION] != -1) {
GL20.glUniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
}
}
@@ -245,20 +248,26 @@ public class CompatUniformManager {
if (locs[LOC_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
GL20.glUniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, 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) {
GL20.glUniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
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);
GL20.glUniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
}
// iris_TextureMatrix — texture unit 0 matrix (animated by glints, etc.)
if (locs[LOC_IRIS_TEXTURE_MATRIX] != -1) {
GLStateManager.getTextures().getTextureUnitMatrix(0).get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_TEXTURE_MATRIX], false, mat4Buf);
}
}
}
@@ -267,39 +276,36 @@ public class CompatUniformManager {
// Fog uniforms
FogStateStack fog = GLStateManager.getFogState();
if (locs[LOC_FOG_DENSITY] != -1) {
GL20.glUniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
RENDER_BACKEND.uniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
}
if (locs[LOC_FOG_START] != -1) {
GL20.glUniform1f(locs[LOC_FOG_START], fog.getStart());
RENDER_BACKEND.uniform1f(locs[LOC_FOG_START], fog.getStart());
}
if (locs[LOC_FOG_END] != -1) {
GL20.glUniform1f(locs[LOC_FOG_END], fog.getEnd());
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.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y).put((float) fog.getFogColor().z).put(fog.getFogAlpha());
vec4Buf.flip();
GL20.glUniform4(locs[LOC_FOG_COLOR], vec4Buf);
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) {
if (!GLStateManager.getAlphaTest().isEnabled() || GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
ref = -1.0f;
} else {
ref = GLStateManager.getAlphaState().getReference();
}
GL20.glUniform1f(locs[LOC_ALPHA_TEST_REF], ref);
RENDER_BACKEND.uniform1f(locs[LOC_ALPHA_TEST_REF], ref);
}
}
/**
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a
* Same computation as {@code Uniforms.uploadLightingPreComputed()}.
* 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();
@@ -310,7 +316,7 @@ public class CompatUniformManager {
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
.put(mat.diffuse.w);
vec4Buf.flip();
GL20.glUniform4(locs[LOC_SCENE_COLOR], vec4Buf);
RENDER_BACKEND.uniform4(locs[LOC_SCENE_COLOR], vec4Buf);
}
private static void uploadLightSources(int[] locs) {
@@ -322,19 +328,20 @@ public class CompatUniformManager {
final LightState light = lights[li];
if (locs[base + LF_AMBIENT] != -1)
GL20.glUniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
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)
GL20.glUniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
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)
GL20.glUniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
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)
GL20.glUniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
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) {
float len = (float) Math.sqrt(light.position.x * light.position.x
+ light.position.y * light.position.y + light.position.z * light.position.z);
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;
@@ -345,24 +352,17 @@ public class CompatUniformManager {
} else {
hx = 0; hy = 0; hz = 1;
}
float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
final float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
GL20.glUniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
RENDER_BACKEND.uniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
}
if (locs[base + LF_SPOT_DIRECTION] != -1)
GL20.glUniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
if (locs[base + LF_SPOT_EXPONENT] != -1)
GL20.glUniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
if (locs[base + LF_SPOT_CUTOFF] != -1)
GL20.glUniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
if (locs[base + LF_SPOT_COS_CUTOFF] != -1)
GL20.glUniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
if (locs[base + LF_CONSTANT_ATTEN] != -1)
GL20.glUniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
if (locs[base + LF_LINEAR_ATTEN] != -1)
GL20.glUniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
if (locs[base + LF_QUADRATIC_ATTEN] != -1)
GL20.glUniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
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);
}
}
@@ -371,20 +371,20 @@ public class CompatUniformManager {
if (locs[LOC_MAT_BASE] == -1) return;
final MaterialState mat = GLStateManager.getFrontMaterial();
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
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)
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
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)
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
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)
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
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)
GL20.glUniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
}
private static void uploadClipPlanes(int[] locs) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
GL20.glUniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
@@ -393,7 +393,7 @@ public class CompatUniformManager {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
GL20.glUniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
@@ -8,6 +7,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VBOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
@@ -15,12 +15,13 @@ import com.gtnewhorizons.angelica.glsm.recording.DisplayListVBO;
import com.gtnewhorizons.angelica.glsm.recording.DisplayListVBOBuilder;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatchBuilder;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector3f;
@@ -49,8 +50,6 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
*/
@UtilityClass
public class DisplayListManager {
private static final Logger LOGGER = LogManager.getLogger("DisplayListManager");
// -Dangelica.debugDisplayLists: disable transform collapsing and draw merging
private static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debugDisplayLists");
@@ -60,7 +59,7 @@ public class DisplayListManager {
static {
LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.logDisplayListCompilation");
if (LOG_DISPLAY_LIST_COMPILATION) {
LogManager.getLogger("DisplayListManager").warn("Display list compilation logging ENABLED (-Dangelica.logDisplayListCompilation=true)");
GLStateManager.LOGGER.warn("Display list compilation logging ENABLED (-Dangelica.logDisplayListCompilation=true)");
}
}
@@ -113,7 +112,6 @@ public class DisplayListManager {
* Compilation context for a single display list being compiled.
* Used to support nested glNewList() calls (spec-violating but needed for mod compatibility).
*/
@Desugar
private record CompilationContext(
int listId,
int listMode,
@@ -144,6 +142,16 @@ public class DisplayListManager {
return glListMode == GL11.GL_COMPILE_AND_EXECUTE;
}
public static CommandRecorder pauseRecording() {
final CommandRecorder r = currentRecorder;
currentRecorder = null;
return r;
}
public static void resumeRecording(CommandRecorder r) {
currentRecorder = r;
}
/**
* Emit accumulated transform as MultMatrix if non-identity, then reset.
*/
@@ -160,7 +168,7 @@ public class DisplayListManager {
// Save pending transform ops for logging (before we clear them)
if (multMatrixSources != null && pendingTransformOps != null) {
if (pendingTransformOps.isEmpty()) {
LOGGER.warn("flushMatrix: non-identity transform with no tracked ops");
GLStateManager.LOGGER.warn("flushMatrix: non-identity transform with no tracked ops");
multMatrixSources.add(Collections.singletonList("(unknown source)"));
} else {
multMatrixSources.add(new ArrayList<>(pendingTransformOps));
@@ -174,11 +182,6 @@ public class DisplayListManager {
stateGeneration++;
}
// COMPILE_AND_EXECUTE: apply to GLSM software stack
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
GLStateManager.getMatrixStack().mul(relativeTransform);
}
// Reset to identity - we're now synchronized with GL
relativeTransform.identity();
@@ -258,6 +261,9 @@ public class DisplayListManager {
if (currentRecorder == null) return;
drawBarrier();
currentRecorder.recordColor(r, g, b, a);
if (ImmediateModeRecorder.isDrawing()) {
ImmediateModeRecorder.setColor(r, g, b, a);
}
}
public static void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
@@ -563,12 +569,16 @@ public class DisplayListManager {
currentRecorder.recordDrawBuffers(count, bufs);
}
// Complex commands are texture uploads - not draw barriers
public static void recordComplexCommand(DisplayListCommand cmd) {
if (currentRecorder == null) return;
currentRecorder.recordComplexCommand(cmd);
}
public static void recordIndexedDrawCapture(IndexedDrawCapture capture) {
if (currentRecorder == null) return;
currentRecorder.recordIndexedDrawCapture(capture);
}
public static void recordLoadMatrix(Matrix4f matrix) {
if (currentRecorder != null) currentRecorder.recordLoadMatrix(matrix);
}
@@ -577,14 +587,6 @@ public class DisplayListManager {
if (currentRecorder != null) currentRecorder.recordLoadIdentity();
}
public static void recordDrawArrays(int mode, int start, int count) {
if (currentRecorder != null) currentRecorder.recordDrawArrays(mode, start, count);
}
public static void recordDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
if (currentRecorder != null) currentRecorder.recordDrawElements(mode, indices_count, type, indices_buffer_offset);
}
public static void recordBindVBO(int vbo) { if (currentRecorder != null) currentRecorder.recordBindVBO(vbo); }
public static void recordBindVAO(int vao) { if (currentRecorder != null) currentRecorder.recordBindVAO(vao); }
@@ -624,9 +626,6 @@ public class DisplayListManager {
final Matrix4f singleTransform = new Matrix4f();
applyTransformOp(singleTransform, x, y, z, op, rotationAxis);
if (currentRecorder != null) currentRecorder.recordMultMatrix(singleTransform);
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
GLStateManager.getMatrixStack().mul(singleTransform);
}
return;
}
@@ -660,10 +659,6 @@ public class DisplayListManager {
if (currentRecorder != null) {
currentRecorder.recordMultMatrix(matrix);
}
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
GLStateManager.getMatrixStack().mul(matrix);
}
return;
}
@@ -826,17 +821,34 @@ public class DisplayListManager {
final boolean hasDraws = accumulatedDraws != null && !accumulatedDraws.isEmpty();
if (hasCommands || hasDraws) {
// Phase 1: Compile format-based VBOs (shared by both optimized and unoptimized paths)
final DisplayListVBO compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
try {
final CommandBuffer finalBuffer = new CommandBuffer();
CommandBufferBuilder.buildFromRawBuffer(rawCommandBuffer, accumulatedDraws, finalBuffer);
// Build to CommandBuffer - optimize raw buffer to final buffer
final CommandBuffer finalBuffer = new CommandBuffer();
CommandBufferBuilder.buildFromRawBuffer(rawCommandBuffer, accumulatedDraws, finalBuffer);
final IndexedDrawBatchBuilder indexedBuilder = currentRecorder.getIndexedDraws();
final List<IndexedDrawBatch> indexedBatches;
if (indexedBuilder.isEmpty()) {
indexedBatches = Collections.emptyList();
} else {
final CommandRecorder paused = pauseRecording();
try {
indexedBatches = indexedBuilder.build();
} finally {
try {
for (IndexedDrawCapture c : indexedBuilder.getCaptures()) {
c.freeBuffers();
}
} finally {
resumeRecording(paused);
}
}
}
// Free the recorder (and its buffer) after optimization
currentRecorder.free();
compiled = new CompiledDisplayList(finalBuffer.toBuffer(), finalBuffer.getComplexObjects(), compiledBuffers);
compiled = new CompiledDisplayList(finalBuffer.toBuffer(), finalBuffer.getComplexObjects(), compiledBuffers, indexedBatches);
} finally {
currentRecorder.free();
}
} else {
// Free the recorder even if empty
if (currentRecorder != null) {
@@ -845,8 +857,10 @@ public class DisplayListManager {
// Empty display list - per OpenGL spec, still valid after glNewList/glEndList
compiled = CompiledDisplayList.EMPTY;
}
// Store the compiled list (even if empty - an empty list is still a valid list)
displayListCache.put(glListId, compiled);
final CompiledDisplayList previous = displayListCache.put(glListId, compiled);
if (previous != null && previous != CompiledDisplayList.EMPTY && previous != compiled) {
previous.delete();
}
// Log compilation details if enabled (before context restoration changes glListId)
if (LOG_DISPLAY_LIST_COMPILATION) {
@@ -899,7 +913,7 @@ public class DisplayListManager {
}
// Merge the previous draw call if possible
final AccumulatedDraw previous = accumulatedDraws.get(accumulatedDraws.size() - 1);
final AccumulatedDraw previous = accumulatedDraws.getLast();
if (previous.format == tessellator.getVertexFormat() && previous.stateGeneration == stateGeneration) {
previous.mergeDraw(tessellator, copyLast);
return;
@@ -969,7 +983,7 @@ public class DisplayListManager {
// ==================== DEBUG LOGGING ====================
public static void logCompiledDisplayList(int listId, CompiledDisplayList compiled, StackTraceElement[] stackTrace) {
LOGGER.debug(getCompiledDisplayListString(listId, compiled, stackTrace));
GLStateManager.LOGGER.debug(getCompiledDisplayListString(listId, compiled, stackTrace));
}
public static String getCompiledDisplayListString(int listId, CompiledDisplayList compiled, StackTraceElement[] stackTrace) {
@@ -1,14 +1,16 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
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;
@@ -35,10 +37,10 @@ public class FeedbackManager {
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
private static final IntBuffer attribBuf = ByteBuffer.allocateDirect(16 * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
@FunctionalInterface
private interface IndexReader {
int read(int i);
}
@@ -48,7 +50,7 @@ public class FeedbackManager {
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
if (renderMode == GL11.GL_FEEDBACK) {
AngelicaTweaker.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
return;
}
feedbackBuffer = buffer;
@@ -58,19 +60,14 @@ public class FeedbackManager {
}
public static int glRenderMode(int mode) {
int result = 0;
switch (renderMode) {
case GL11.GL_FEEDBACK:
result = (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
break;
case GL11.GL_RENDER:
case GL11.GL_SELECT:
result = 0;
break;
}
int result = switch (renderMode) {
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
default -> 0;
};
if (mode == GL11.GL_SELECT) {
AngelicaTweaker.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
GLStateManager.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
}
feedbackCount = 0;
@@ -123,55 +120,60 @@ public class FeedbackManager {
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 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();
int stride = queryPositionStride();
if (stride == 0) stride = 12; // tightly packed 3 floats
final long posOffset = queryPositionOffset();
final int stride = positionStride();
final long posOffset = positionOffset();
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
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();
int stride = queryPositionStride();
if (stride == 0) stride = 12;
final int stride = positionStride();
final int elementSize = elementSizeForType(type);
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
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 = readIndex(indexBuf, type, i);
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = queryPositionOffset();
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> readIndex(indexBuf, type, i) - base, stride);
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
}
public static void processDrawElements(int mode, ByteBuffer indices) {
@@ -202,8 +204,7 @@ public class FeedbackManager {
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
int stride = queryPositionStride();
if (stride == 0) stride = 12;
final int stride = positionStride();
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
@@ -221,12 +222,6 @@ public class FeedbackManager {
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
}
private static int queryPositionStride() {
attribBuf.clear();
GL20.glGetVertexAttrib(0, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE, attribBuf);
return attribBuf.get(0);
}
private static int computeMask(int type) {
return switch (type) {
case GL11.GL_2D -> 0;
@@ -238,24 +233,6 @@ public class FeedbackManager {
};
}
private static int elementSizeForType(int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> 1;
case GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_UNSIGNED_INT -> 4;
default -> 4;
};
}
private static int readIndex(ByteBuffer buf, int type, int i) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> buf.get(i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> buf.getShort(i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> buf.getInt(i * 4);
default -> 0;
};
}
private static void ensureReadbackCapacity(int bytes) {
if (readbackBuf.capacity() < bytes) {
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
@@ -268,18 +245,13 @@ public class FeedbackManager {
}
}
private static long queryPositionOffset() {
final ByteBuffer buf = GL20.glGetVertexAttribPointer(0, GL20.GL_VERTEX_ATTRIB_ARRAY_POINTER, 8);
return buf == null ? 0L : buf.getLong(0);
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = queryPositionOffset();
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
}
private static void emitVertex(int vertexIndex, int stride) {
@@ -373,6 +345,27 @@ public class FeedbackManager {
}
break;
case GL11.GL_QUAD_STRIP:
for (int i = 0; i + 3 < count; i += 2) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(4);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 3), stride);
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_POLYGON:
if (count >= 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(count);
for (int i = 0; i < count; i++) {
emitVertex(reader.read(i), stride);
}
}
break;
default:
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
}
@@ -5,10 +5,7 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.ARBDebugOutputCallback;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import org.lwjgl.opengl.EXTBlendColor;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -16,155 +13,72 @@ import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug;
import org.lwjgl.opengl.KHRDebugCallback;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
import static org.lwjgl.opengl.ARBDebugOutput.glDebugMessageCallbackARB;
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()) {
LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
GLStateManager.LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
return 0;
}
return setupDebugMessageCallbackImpl();
}
public static Throwable filterStackTrace(Throwable throwable, int offset) {
StackTraceElement[] elems = throwable.getStackTrace();
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];
}
StackTraceElement[] newElems = new StackTraceElement[j];
System.arraycopy(filtered, 0, newElems, 0, j);
throwable.setStackTrace(newElems);
return throwable;
}
private static String buildStackTrace() {
StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
StringBuilder sb = new StringBuilder();
for (StackTraceElement elem : elems) {
sb.append("\n\t").append(elem.toString());
}
return sb.toString();
}
private static void logDebugMessage(int id, String source, String type, String severity, String message) {
String fullMessage = String.format("[GL] %s %s (0x%X) from %s: %s%s", severity, type, id, source, message, buildStackTrace());
if ("HIGH".equals(severity)) {
LOGGER.error(fullMessage);
} else if ("MEDIUM".equals(severity)) {
LOGGER.warn(fullMessage);
} else {
LOGGER.info(fullMessage);
}
}
private static void logDebugMessageAMD(int id, String category, String severity, String message) {
String fullMessage = String.format("[GL] %s %s (0x%X): %s%s", severity, category, id, message, buildStackTrace());
if ("HIGH".equals(severity)) {
LOGGER.error(fullMessage);
} else if ("MEDIUM".equals(severity)) {
LOGGER.warn(fullMessage);
} else {
LOGGER.info(fullMessage);
}
}
/**
* Sets up debug callbacks
* @return 0 for failure, 1 for success, 2 for restart required.
*/
private static int setupDebugMessageCallbackImpl() {
if (GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_KHR_debug) {
LOGGER.info("[GL] Using OpenGL 4.3 for error logging.");
final KHRDebugCallback proc = new KHRDebugCallback((source, type, id, severity, message) -> {
logDebugMessage(id, getDebugSource(source), getDebugType(type), getDebugSeverity(severity), message);
});
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, null, false);
GL43.glDebugMessageCallback(proc);
// Enable synchronous debug output so errors are reported immediately with accurate stack traces
GL11.glEnable(GL43.GL_DEBUG_OUTPUT_SYNCHRONOUS);
if ((GL11.glGetInteger(GL30.GL_CONTEXT_FLAGS) & GL43.GL_CONTEXT_FLAG_DEBUG_BIT) == 0) {
LOGGER.warn("[GL] Warning: A non-debug context may not produce any debug output.");
GL11.glEnable(GL43.GL_DEBUG_OUTPUT);
return 2;
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 = "";
}
return 1;
} else if (GLStateManager.capabilities.GL_ARB_debug_output) {
LOGGER.info("[GL] Using ARB_debug_output for error logging.");
final ARBDebugOutputCallback proc = new ARBDebugOutputCallback((source, type, id, severity, message) -> {
logDebugMessage(id, getSourceARB(source), getTypeARB(type), getSeverityARB(severity), message);
});
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, null, false);
ARBDebugOutput.glDebugMessageCallbackARB(proc);
filtered[j++] = elems[i];
}
final StackTraceElement[] newElems = new StackTraceElement[j];
System.arraycopy(filtered, 0, newElems, 0, j);
throwable.setStackTrace(newElems);
return throwable;
}
// Enable synchronous debug output so errors are reported immediately with accurate stack traces
GL11.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
return 1;
} else if (GLStateManager.capabilities.GL_AMD_debug_output) {
LOGGER.info("[GL] Using AMD_debug_output for error logging.");
final AMDDebugOutputCallback proc = new AMDDebugOutputCallback((id, category, severity, message) -> {
logDebugMessageAMD(id, getCategoryAMD(category), getSeverityAMD(severity), message);
});
AMDDebugOutput.glDebugMessageEnableAMD(0, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
AMDDebugOutput.glDebugMessageEnableAMD(0, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
AMDDebugOutput.glDebugMessageEnableAMD(0, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
AMDDebugOutput.glDebugMessageEnableAMD(0, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, null, false);
AMDDebugOutput.glDebugMessageCallbackAMD(proc);
return 1;
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 {
LOGGER.info("[GL] No debug output implementation is available, cannot return debug info.");
return 0;
GLStateManager.LOGGER.info(fullMessage);
}
}
public static int disableDebugMessages() {
if (GLStateManager.capabilities.OpenGL43) {
GL43.glDebugMessageCallback(null);
return 1;
} else if (GLStateManager.capabilities.GL_KHR_debug) {
KHRDebug.glDebugMessageCallback(null);
if (GLStateManager.capabilities.OpenGL30 && (GL11.glGetInteger(GL30.GL_CONTEXT_FLAGS) & 2) == 0) {
GL11.glDisable(GL43.GL_DEBUG_OUTPUT);
}
return 1;
} else if (GLStateManager.capabilities.GL_ARB_debug_output) {
glDebugMessageCallbackARB(null);
return 1;
} else if (GLStateManager.capabilities.GL_AMD_debug_output) {
AMDDebugOutput.glDebugMessageCallbackAMD(null);
return 1;
} else {
LOGGER.info("[GL] No debug output implementation is available, cannot disable debug info.");
return 0;
}
}
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) {
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";
@@ -174,9 +88,9 @@ public final class GLDebug {
case GL43.GL_DEBUG_SOURCE_OTHER -> "OTHER";
default -> String.format("Unknown [0x%X]", source);
};
}
}
private static String getDebugType(int type) {
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";
@@ -187,9 +101,9 @@ public final class GLDebug {
case GL43.GL_DEBUG_TYPE_MARKER -> "MARKER";
default -> String.format("Unknown [0x%X]", type);
};
}
}
private static String getDebugSeverity(int severity) {
private static String getDebugSeverity(int severity) {
return switch (severity) {
case GL43.GL_DEBUG_SEVERITY_NOTIFICATION -> "NOTIFICATION";
case GL43.GL_DEBUG_SEVERITY_HIGH -> "HIGH";
@@ -197,65 +111,9 @@ public final class GLDebug {
case GL43.GL_DEBUG_SEVERITY_LOW -> "LOW";
default -> String.format("Unknown [0x%X]", severity);
};
}
private static String getSourceARB(int source) {
return switch (source) {
case ARBDebugOutput.GL_DEBUG_SOURCE_API_ARB -> "API";
case ARBDebugOutput.GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB -> "WINDOW SYSTEM";
case ARBDebugOutput.GL_DEBUG_SOURCE_SHADER_COMPILER_ARB -> "SHADER COMPILER";
case ARBDebugOutput.GL_DEBUG_SOURCE_THIRD_PARTY_ARB -> "THIRD PARTY";
case ARBDebugOutput.GL_DEBUG_SOURCE_APPLICATION_ARB -> "APPLICATION";
case ARBDebugOutput.GL_DEBUG_SOURCE_OTHER_ARB -> "OTHER";
default -> String.format("Unknown [0x%X]", source);
};
}
private static String getTypeARB(int type) {
return switch (type) {
case ARBDebugOutput.GL_DEBUG_TYPE_ERROR_ARB -> "ERROR";
case ARBDebugOutput.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB -> "DEPRECATED BEHAVIOR";
case ARBDebugOutput.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB -> "UNDEFINED BEHAVIOR";
case ARBDebugOutput.GL_DEBUG_TYPE_PORTABILITY_ARB -> "PORTABILITY";
case ARBDebugOutput.GL_DEBUG_TYPE_PERFORMANCE_ARB -> "PERFORMANCE";
case ARBDebugOutput.GL_DEBUG_TYPE_OTHER_ARB -> "OTHER";
default -> String.format("Unknown [0x%X]", type);
};
}
private static String getSeverityARB(int severity) {
return switch (severity) {
case ARBDebugOutput.GL_DEBUG_SEVERITY_HIGH_ARB -> "HIGH";
case ARBDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_ARB -> "MEDIUM";
case ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB -> "LOW";
default -> String.format("Unknown [0x%X]", severity);
};
}
private static String getCategoryAMD(int category) {
return switch (category) {
case AMDDebugOutput.GL_DEBUG_CATEGORY_API_ERROR_AMD -> "API ERROR";
case AMDDebugOutput.GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD -> "WINDOW SYSTEM";
case AMDDebugOutput.GL_DEBUG_CATEGORY_DEPRECATION_AMD -> "DEPRECATION";
case AMDDebugOutput.GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD -> "UNDEFINED BEHAVIOR";
case AMDDebugOutput.GL_DEBUG_CATEGORY_PERFORMANCE_AMD -> "PERFORMANCE";
case AMDDebugOutput.GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD -> "SHADER COMPILER";
case AMDDebugOutput.GL_DEBUG_CATEGORY_APPLICATION_AMD -> "APPLICATION";
case AMDDebugOutput.GL_DEBUG_CATEGORY_OTHER_AMD -> "OTHER";
default -> String.format("Unknown [0x%X]", category);
};
}
private static String getSeverityAMD(int severity) {
return switch (severity) {
case AMDDebugOutput.GL_DEBUG_SEVERITY_HIGH_AMD -> "HIGH";
case AMDDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_AMD -> "MEDIUM";
case AMDDebugOutput.GL_DEBUG_SEVERITY_LOW_AMD -> "LOW";
default -> String.format("Unknown [0x%X]", severity);
};
}
public static String getMatrixModeName(int mode) {
public static String getMatrixModeName(int mode) {
return switch (mode) {
case GL11.GL_MODELVIEW -> "MODELVIEW";
case GL11.GL_PROJECTION -> "PROJECTION";
@@ -263,9 +121,9 @@ public final class GLDebug {
case GL11.GL_COLOR -> "COLOR";
default -> String.format("0x%X", mode);
};
}
}
public static String getCapabilityName(int cap) {
public static String getCapabilityName(int cap) {
return switch (cap) {
case GL11.GL_ALPHA_TEST -> "ALPHA_TEST";
case GL11.GL_BLEND -> "BLEND";
@@ -295,9 +153,9 @@ public final class GLDebug {
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", cap);
};
}
}
public static String getComparisonFuncName(int func) {
public static String getComparisonFuncName(int func) {
return switch (func) {
case GL11.GL_NEVER -> "NEVER";
case GL11.GL_LESS -> "LESS";
@@ -309,9 +167,9 @@ public final class GLDebug {
case GL11.GL_ALWAYS -> "ALWAYS";
default -> String.format("0x%X", func);
};
}
}
public static String getBlendFactorName(int factor) {
public static String getBlendFactorName(int factor) {
return switch (factor) {
case GL11.GL_ZERO -> "ZERO";
case GL11.GL_ONE -> "ONE";
@@ -330,17 +188,17 @@ public final class GLDebug {
case GL11.GL_SRC_ALPHA_SATURATE -> "SRC_ALPHA_SATURATE";
default -> String.format("0x%X", factor);
};
}
}
public static String getShadeModelName(int mode) {
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) {
public static String getTextureTargetName(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
@@ -348,9 +206,9 @@ public final class GLDebug {
case GL13.GL_TEXTURE_CUBE_MAP -> "TEXTURE_CUBE_MAP";
default -> String.format("0x%X", target);
};
}
}
public static String getTexturePnameName(int pname) {
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";
@@ -367,9 +225,9 @@ public final class GLDebug {
case GL14.GL_GENERATE_MIPMAP -> "GENERATE_MIPMAP";
default -> String.format("0x%X", pname);
};
}
}
public static String getTextureFormatName(int format) {
public static String getTextureFormatName(int format) {
return switch (format) {
case GL11.GL_RED -> "RED";
case GL11.GL_GREEN -> "GREEN";
@@ -392,9 +250,9 @@ public final class GLDebug {
case GL11.GL_LUMINANCE8_ALPHA8 -> "LUMINANCE8_ALPHA8";
default -> String.format("0x%X", format);
};
}
}
public static String getDataTypeName(int type) {
public static String getDataTypeName(int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
case GL11.GL_BYTE -> "BYTE";
@@ -411,9 +269,9 @@ public final class GLDebug {
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
default -> String.format("0x%X", type);
};
}
}
public static String getLightName(int light) {
public static String getLightName(int light) {
return switch (light) {
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
@@ -425,9 +283,9 @@ public final class GLDebug {
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", light);
};
}
}
public static String getLightPnameName(int pname) {
public static String getLightPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
@@ -441,9 +299,9 @@ public final class GLDebug {
case GL11.GL_QUADRATIC_ATTENUATION -> "QUADRATIC_ATTENUATION";
default -> String.format("0x%X", pname);
};
}
}
public static String getMaterialPnameName(int pname) {
public static String getMaterialPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
@@ -454,18 +312,18 @@ public final class GLDebug {
case GL11.GL_COLOR_INDEXES -> "COLOR_INDEXES";
default -> String.format("0x%X", pname);
};
}
}
public static String getFaceName(int face) {
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) {
public static String getColorMaterialModeName(int mode) {
return switch (mode) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
@@ -474,9 +332,9 @@ public final class GLDebug {
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
default -> String.format("0x%X", mode);
};
}
}
public static String getFogPnameName(int pname) {
public static String getFogPnameName(int pname) {
return switch (pname) {
case GL11.GL_FOG_MODE -> "FOG_MODE";
case GL11.GL_FOG_DENSITY -> "FOG_DENSITY";
@@ -486,9 +344,9 @@ public final class GLDebug {
case GL11.GL_FOG_COLOR -> "FOG_COLOR";
default -> String.format("0x%X", pname);
};
}
}
public static String getLightModelPnameName(int 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";
@@ -496,9 +354,9 @@ public final class GLDebug {
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> "LIGHT_MODEL_COLOR_CONTROL";
default -> String.format("0x%X", pname);
};
}
}
public static String getClearMaskString(int mask) {
public static String getClearMaskString(int mask) {
StringBuilder sb = new StringBuilder();
boolean first = true;
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
@@ -520,18 +378,18 @@ public final class GLDebug {
sb.append("ACCUM");
}
return sb.toString();
}
}
public static String getCullFaceName(int mode) {
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) {
public static String getLogicOpName(int opcode) {
return switch (opcode) {
case GL11.GL_CLEAR -> "CLEAR";
case GL11.GL_AND -> "AND";
@@ -551,9 +409,9 @@ public final class GLDebug {
case GL11.GL_SET -> "SET";
default -> String.format("0x%X", opcode);
};
}
}
public static String getFramebufferStatusName(int status) {
public static String getFramebufferStatusName(int status) {
return switch (status) {
case GL30.GL_FRAMEBUFFER_COMPLETE -> "COMPLETE";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT -> "INCOMPLETE_ATTACHMENT";
@@ -565,9 +423,9 @@ public final class GLDebug {
case 0 -> "NO_FRAMEBUFFER_BOUND";
default -> String.format("UNKNOWN(0x%X)", status);
};
}
}
public static String getInternalFormatName(int format) {
public static String getInternalFormatName(int format) {
return switch (format) {
// Basic
case GL11.GL_RGBA -> "RGBA";
@@ -602,33 +460,37 @@ public final class GLDebug {
case GL11.GL_RGB10_A2 -> "RGB10_A2";
default -> String.format("0x%X", format);
};
}
}
private static DebugState debugState;
private static DebugState debugState;
private interface DebugState {
void nameObject(int id, int object, String name);
void pushGroup(String name);
void popGroup();
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 class KHRDebugState implements DebugState {
private static final int ID = 0;
private int depth = 0;
private static final int maxDepth = GL11.glGetInteger(KHRDebug.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
private static final int maxNameLength = GL11.glGetInteger(KHRDebug.GL_MAX_LABEL_LENGTH);
private 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) {
KHRDebug.glObjectLabel(id, object, name);
}
@Override
public void nameObject(int id, int object, String name) {
BackendManager.RENDER_BACKEND.objectLabel(id, object, name);
}
@Override
public void pushGroup(String 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");
@@ -639,46 +501,51 @@ public final class GLDebug {
throw new RuntimeException(sb.toString());
}
groupStack[depth] = name;
KHRDebug.glPushDebugGroup(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, ID, name);
BackendManager.RENDER_BACKEND.pushDebugGroup(GL43.GL_DEBUG_SOURCE_APPLICATION, ID, name);
depth++;
}
}
@Override
public void popGroup() {
@Override
public void popGroup() {
if (depth - 1 < 0) {
throw new RuntimeException("Stack underflow");
}
KHRDebug.glPopDebugGroup();
BackendManager.RENDER_BACKEND.popDebugGroup();
depth--;
groupStack[depth] = null;
}
}
@Override
public void debugMessage(String message) {
KHRDebug.glDebugMessageInsert(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, KHRDebug.GL_DEBUG_TYPE_MARKER, ID, KHRDebug.GL_DEBUG_SEVERITY_NOTIFICATION, 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 "";
if (program == 0) return "";
return KHRDebug.glGetObjectLabel(glProgram, program, maxNameLength);
return BackendManager.RENDER_BACKEND.getObjectLabel(glProgram, program);
}
}
private static class UnsupportedDebugState implements DebugState {
@Override
public void nameObject(int id, int object, String name) {
}
private static class UnsupportedDebugState implements DebugState {
@Override
public void pushGroup(String name) {
}
@Override
public void nameObject(int id, int object, String name) {
}
@Override
public void popGroup() {
}
@Override
public void pushGroup(String name) {
}
@Override
public void popGroup() {
}
@Override
public void debugMessage(String name) {
@@ -691,40 +558,43 @@ public final class GLDebug {
}
}
public static void initDebugState() {
if (GLStateManager.capabilities.GL_KHR_debug || GLStateManager.capabilities.OpenGL43) {
debugState = new KHRDebugState();
} else {
debugState = new UnsupportedDebugState();
}
}
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()) {
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()) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.pushGroup(group);
}
}
public static void popGroup() {
if(debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.popGroup();
}
}
public static void debugMessage(String message) {
if(debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
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()) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
return debugState.getObjectLabel(glProgram, program);
}
return "";
}
}
@@ -36,7 +36,14 @@ public class GlslTransformUtils {
/** 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"))
// Some reserved words to always rename
0, List.of(
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"),
new ReservedWordRename(Pattern.compile("\\bnew\\b"), RENAMED_PREFIX + "new")
),
400, List.of(
new ReservedWordRename(Pattern.compile("\\bsampler\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
)
);
public static String replaceTexture(String input) {
@@ -55,7 +62,8 @@ public class GlslTransformUtils {
public static String renameReservedWords(String source, int targetVersion) {
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
if (targetVersion < entry.getKey()) {
// 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());
}
@@ -66,6 +74,7 @@ public class GlslTransformUtils {
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;
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.glsm;
/**
* Tessellator data needed by {@link com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer}.
*/
public interface ITessellatorData {
boolean isDrawing();
void setDrawing(boolean drawing);
int getVertexCount();
int[] getRawBuffer();
int getRawBufferIndex();
int getRawBufferSize();
void setRawBufferSize(int size);
void setRawBuffer(int[] buffer);
int getDrawMode();
boolean hasTexture();
boolean hasColor();
boolean hasNormals();
boolean hasBrightness();
void angelica$reset();
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -8,6 +9,7 @@ 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;
@@ -46,7 +48,7 @@ public final class QuadConverter {
}
if (eboId == 0) {
eboId = GL15.glGenBuffers();
eboId = RENDER_BACKEND.genBuffers();
}
// Generate index data: 6 ints per quad
@@ -66,8 +68,8 @@ public final class QuadConverter {
ptr += 24;
}
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
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;
@@ -86,11 +88,11 @@ public final class QuadConverter {
final int quadCount = vertexCount / 4;
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
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;
GL11.glDrawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
}
/**
@@ -108,23 +110,23 @@ public final class QuadConverter {
final int prevEbo = GLStateManager.getBoundEBO();
if (scratchEboId == 0) {
scratchEboId = GL15.glGenBuffers();
scratchEboId = RENDER_BACKEND.genBuffers();
}
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
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;
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
GL15.glBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
GL11.glDrawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
memFree(dst);
}
@@ -142,50 +144,13 @@ public final class QuadConverter {
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex;
switch (type) {
case GL11.GL_UNSIGNED_INT: bytesPerIndex = 4; break;
case GL11.GL_UNSIGNED_SHORT: bytesPerIndex = 2; break;
case GL11.GL_UNSIGNED_BYTE: bytesPerIndex = 1; break;
default: return;
}
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
// Read source indices from caller's EBO
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
final long srcAddr = memAddress0(src);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, 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);
}
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
@@ -202,16 +167,8 @@ public final class QuadConverter {
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);
}
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
@@ -227,16 +184,8 @@ public final class QuadConverter {
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);
}
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
}
@@ -252,31 +201,7 @@ public final class QuadConverter {
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);
}
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
@@ -286,14 +211,140 @@ public final class QuadConverter {
*/
public static void destroy() {
if (eboId != 0) {
GL15.glDeleteBuffers(eboId);
GLStateManager.glDeleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
GL15.glDeleteBuffers(scratchEboId);
GLStateManager.glDeleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
}
/**
* Read one index at {@code srcAddr + i * sizeof(srcType)} and return its
* zero-extended unsigned value.
*/
private static int readIndex(long srcAddr, int srcType, int i) {
return switch (srcType) {
case GL11.GL_UNSIGNED_BYTE -> memGetByte(srcAddr + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> memGetShort(srcAddr + i * 2L) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> memGetInt(srcAddr + i * 4L);
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(srcType));
};
}
/**
* Write one index at {@code dstAddr + i * sizeof(dstType)}.
*/
private static void writeIndex(long dstAddr, int dstType, int i, int value) {
switch (dstType) {
case GL11.GL_UNSIGNED_SHORT -> memPutShort(dstAddr + i * 2L, (short) value);
case GL11.GL_UNSIGNED_INT -> memPutInt(dstAddr + i * 4L, value);
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(dstType));
}
}
/**
* Read {@code quadCount} quads from {@code srcAddr} and emit 6 triangle indices
* per quad at {@code dstAddr} using {@code GL_LAST_VERTEX_CONVENTION}:
* {@code (a,b,d), (b,c,d)}. v3 is the provoking vertex for both triangles;
* diagonal runs b→d. Matches Mesa's {@code do_quad} LAST path.
*
* <p>Caller allocates dst of size {@code quadCount * 6 * sizeof(dstType)}.
*
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT}
* / {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Widening only.
*/
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount) {
triangulateQuads(srcAddr, srcType, dstAddr, dstType, quadCount, 0);
}
/**
* Same as {@link #triangulateQuads(long, int, long, int, int)} but subtracts
* {@code minVtx} from every emitted index, rebasing to a 0-based buffer in
* one pass.
*/
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount, int minVtx) {
for (int i = 0; i < quadCount; i++) {
final int a = readIndex(srcAddr, srcType, i * 4) - minVtx;
final int b = readIndex(srcAddr, srcType, i * 4 + 1) - minVtx;
final int c = readIndex(srcAddr, srcType, i * 4 + 2) - minVtx;
final int d = readIndex(srcAddr, srcType, i * 4 + 3) - minVtx;
final int o = i * 6;
writeIndex(dstAddr, dstType, o, a);
writeIndex(dstAddr, dstType, o + 1, b);
writeIndex(dstAddr, dstType, o + 2, d);
writeIndex(dstAddr, dstType, o + 3, b);
writeIndex(dstAddr, dstType, o + 4, c);
writeIndex(dstAddr, dstType, o + 5, d);
}
}
/**
* Copy {@code count} indices from {@code srcAddr} to {@code dstAddr}, converting
* from {@code srcType} to {@code dstType}.
*
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Widening only; narrowing is rejected by
* {@link #writeIndex(long, int, int, int) writeIndex}.
*/
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count) {
widenIndices(srcAddr, srcType, dstAddr, dstType, count, 0);
}
/**
* Same as {@link #widenIndices(long, int, long, int, int)} but subtracts
* {@code minVtx} from every written index in one pass.
*/
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count, int minVtx) {
for (int i = 0; i < count; i++) {
writeIndex(dstAddr, dstType, i, readIndex(srcAddr, srcType, i) - minVtx);
}
}
/**
* Scan {@code count} indices at {@code srcAddr} of {@code srcType}. Returns
* {@code (max << 32) | (min & 0xFFFFFFFFL)} or {@code -1L} for an unsupported
* type or empty range.
*/
public static long scanMinMaxIndex(long srcAddr, int srcType, int count) {
if (count <= 0) return -1L;
long min = Long.MAX_VALUE;
long max = Long.MIN_VALUE;
switch (srcType) {
case GL11.GL_UNSIGNED_BYTE -> {
for (int i = 0; i < count; i++) {
final int v = memGetByte(srcAddr + i) & 0xFF;
if (v < min) min = v;
if (v > max) max = v;
}
}
case GL11.GL_UNSIGNED_SHORT -> {
for (int i = 0; i < count; i++) {
final int v = memGetShort(srcAddr + i * 2L) & 0xFFFF;
if (v < min) min = v;
if (v > max) max = v;
}
}
case GL11.GL_UNSIGNED_INT -> {
for (int i = 0; i < count; i++) {
final long v = memGetInt(srcAddr + i * 4L) & 0xFFFFFFFFL;
if (v < min) min = v;
if (v > max) max = v;
}
// readIndex / writeIndex / rebase all route UINT through signed int.
// Reject the > 2^31 slice so the caller bails rather than silently
// emitting negative indices. MC never hits this in practice.
if (max > Integer.MAX_VALUE) return -1L;
}
default -> {
return -1L;
}
}
return (max << 32) | (min & 0xFFFFFFFFL);
}
}
@@ -0,0 +1,538 @@
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.hooks.GLSMInitConfig;
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.vendorIsIntel() && GLStateManager.isWindows()) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("Detected Intel drivers on Windows, disabling DSA.");
} else if (!GLStateManager.getInitConfig().isDSAEnabled()) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("enableDSA is set to false, disabling DSA.");
} else if (GLStateManager.capabilities.OpenGL45) {
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
GLStateManager.LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
try {
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
dsaState = new DSAARB();
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
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) {
GLStateManager.glDeleteBuffers(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;
GLStateManager.glDeleteSamplers(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;
}
}
@@ -2,8 +2,6 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.MultMatrixCmd;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import java.util.ArrayDeque;
@@ -20,8 +18,6 @@ import java.util.List;
* </ul>
*/
class TransformOptimizer {
private static final Logger LOGGER = LogManager.getLogger("TransformOptimizer");
private final Matrix4f accumulated = new Matrix4f(); // Current accumulated transform
private final Matrix4f lastEmitted = new Matrix4f(); // Last emitted transform
private final Deque<Matrix4f> stack = new ArrayDeque<>(); // For Push/Pop
@@ -53,7 +49,7 @@ class TransformOptimizer {
// After Pop, GL state is restored to the pushed value so lastEmitted should also reflect this
lastEmitted.set(accumulated);
} else {
LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
GLStateManager.LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
accumulated.identity();
lastEmitted.identity();
}
@@ -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);
}
@@ -0,0 +1,325 @@
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();
/** Returns true if the current thread has a valid render context. Safe to call from any thread. */
public abstract boolean hasContext();
/** 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, IntBuffer data, int usage);
public abstract void bufferData(int target, DoubleBuffer 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 void bufferSubData(int target, long offset, ShortBuffer data);
public abstract void bufferSubData(int target, long offset, IntBuffer data);
public abstract void bufferSubData(int target, long offset, FloatBuffer data);
public abstract void bufferSubData(int target, long offset, DoubleBuffer 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 void getBufferSubData(int target, long offset, ShortBuffer data);
public abstract void getBufferSubData(int target, long offset, IntBuffer data);
public abstract void getBufferSubData(int target, long offset, FloatBuffer data);
public abstract void getBufferSubData(int target, long offset, DoubleBuffer 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,142 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Cylinder;
import static org.lwjgl.opengl.GL11.GL_LINES;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Cylinder}.
*/
@SuppressWarnings("unused")
public class AngelicaCylinder extends Cylinder {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float baseRadius, float topRadius, float height, int slices, int stacks) {
final float da, dr, dz, nz, nsign;
float r, x, y, z;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
da = 2.0f * PI / slices;
dr = (topRadius - baseRadius) / stacks;
dz = height / stacks;
nz = (baseRadius - topRadius) / height;
if (super.drawStyle == GLU_POINT) {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glVertex3f(x * r, y * r, z);
z += dz;
r += dr;
}
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// Draw rings
if (super.drawStyle == GLU_LINE) {
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * r, y * r, z);
}
GLStateManager.glEnd();
z += dz;
r += dr;
}
} else {
// draw one ring at each end
if (baseRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
}
GLStateManager.glEnd();
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
}
}
// draw length lines
GLStateManager.glBegin(GL_LINES);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_FILL) {
final float ds = 1.0f / slices;
final float dt = 1.0f / stacks;
float t = 0.0f;
z = 0.0f;
r = baseRadius;
for (int j = 0; j < stacks; j++) {
float s = 0.0f;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
final float angle = (i == slices) ? 0.0f : i * da;
x = sin(angle);
y = cos(angle);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * r, y * r, z);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t + dt);
GLStateManager.glVertex3f(x * (r + dr), y * (r + dr), z + dz);
s += ds;
}
GLStateManager.glEnd();
r += dr;
t += dt;
z += dz;
}
}
}
}
@@ -0,0 +1,134 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Disk;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Disk}.
*/
@SuppressWarnings("unused")
public class AngelicaDisk extends Disk {
private static final float PI = (float) Math.PI;
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops) {
final float da, dr;
if (super.normals != GLU_NONE) {
GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
}
da = 2.0f * PI / slices;
dr = (outerRadius - innerRadius) / loops;
switch (super.drawStyle) {
case GLU_FILL -> {
final float dtc = 2.0f * outerRadius;
float sa, ca;
float r1 = innerRadius;
for (int l = 0; l < loops; l++) {
final float r2 = r1 + dr;
if (super.orientation == GLU_OUTSIDE) {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = 0; s <= slices; s++) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f + sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f + sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
} else {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = slices; s >= 0; s--) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f - sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f - sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
}
r1 = r2;
}
}
case GLU_LINE -> {
/* draw loops */
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int s = 0; s < slices; s++) {
final float a = s * da;
GLStateManager.glVertex2f(r * sin(a), r * cos(a));
}
GLStateManager.glEnd();
}
/* draw spokes */
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
GLStateManager.glBegin(GL_LINE_STRIP);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius * l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
}
GLStateManager.glEnd();
}
case GLU_SILHOUETTE -> {
if (innerRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0.0f; a < 2.0 * PI; a += da) {
final float x = innerRadius * sin(a);
final float y = innerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0; a < 2.0f * PI; a += da) {
final float x = outerRadius * sin(a);
final float y = outerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
default -> {
}
}
}
}
@@ -0,0 +1,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();
}
}
}
@@ -1,18 +1,18 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.GL45;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public class DSAARB extends DSAUnsupported {
@Override
public void generateMipmaps(int texture, int target) {
ARBDirectStateAccess.glGenerateTextureMipmap(texture);
RENDER_BACKEND.generateTextureMipmap(texture);
}
@Override
@@ -30,12 +30,12 @@ public class DSAARB extends DSAUnsupported {
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
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) {
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
RENDER_BACKEND.textureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
@@ -43,7 +43,7 @@ public class DSAARB extends DSAUnsupported {
param = GLStateManager.remapTexClamp(pname, param);
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
ARBDirectStateAccess.glTextureParameteri(texture, pname, param);
RENDER_BACKEND.textureParameteri(texture, target, pname, param);
}
@Override
@@ -51,38 +51,38 @@ public class DSAARB extends DSAUnsupported {
param = GLStateManager.remapTexClamp(pname, param);
if(!GLStateManager.updateTexParameterfCache(target, texture, pname, param)) return;
ARBDirectStateAccess.glTextureParameterf(texture, pname, param);
RENDER_BACKEND.textureParameterf(texture, target, pname, param);
}
@Override
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
GLStateManager.remapTexClampBuffer(pname, params);
ARBDirectStateAccess.glTextureParameter(texture, pname, params);
RENDER_BACKEND.textureParameteriv(texture, target, pname, params);
}
@Override
public void readBuffer(int framebuffer, int buffer) {
ARBDirectStateAccess.glNamedFramebufferReadBuffer(framebuffer, buffer);
RENDER_BACKEND.namedFramebufferReadBuffer(framebuffer, buffer);
}
@Override
public void drawBuffers(int framebuffer, IntBuffer buffers) {
ARBDirectStateAccess.glNamedFramebufferDrawBuffers(framebuffer, buffers);
RENDER_BACKEND.namedFramebufferDrawBuffers(framebuffer, buffers);
}
@Override
public int getTexParameteri(int texture, int target, int pname) {
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> ARBDirectStateAccess.glGetTextureParameteri(texture, pname));
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname));
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
return ARBDirectStateAccess.glGetTextureLevelParameteri(texture, level, 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) {
ARBDirectStateAccess.glCopyTextureSubImage2D(destTexture, i, i1, i2, i3, i4, width, height);
RENDER_BACKEND.copyTextureSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
}
@Override
@@ -92,7 +92,7 @@ public class DSAARB extends DSAUnsupported {
if (texture == 0) {
super.bindTextureToUnit(unit, texture);
} else {
ARBDirectStateAccess.glBindTextureUnit(unit, texture);
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture);
}
}
@@ -104,44 +104,44 @@ public class DSAARB extends DSAUnsupported {
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = GL45.glCreateBuffers();
GL45.glNamedBufferData(buffer, data, 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) {
ARBDirectStateAccess.glBlitNamedFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, 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) {
ARBDirectStateAccess.glNamedFramebufferTexture(fb, attachment, texture, levels);
RENDER_BACKEND.namedFramebufferTexture(fb, attachment, texture, levels);
}
@Override
public int createFramebuffer() {
return ARBDirectStateAccess.glCreateFramebuffers();
return RENDER_BACKEND.createFramebuffers();
}
@Override
public int createTexture(int target) {
return ARBDirectStateAccess.glCreateTextures(target);
return RENDER_BACKEND.createTextures(target);
}
@Override
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
ARBDirectStateAccess.glTextureStorage1D(texture, levels, internalFormat, width);
RENDER_BACKEND.textureStorage1D(texture, levels, internalFormat, width);
}
@Override
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
ARBDirectStateAccess.glTextureStorage2D(texture, levels, internalFormat, width, height);
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) {
ARBDirectStateAccess.glTextureStorage3D(texture, levels, internalFormat, width, height, depth);
RENDER_BACKEND.textureStorage3D(texture, levels, internalFormat, width, height, depth);
}
}
@@ -1,18 +1,18 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import org.lwjgl.opengl.EXTDirectStateAccess;
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) {
EXTDirectStateAccess.glTextureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, 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) {
EXTDirectStateAccess.glTextureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
RENDER_BACKEND.textureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
}
@@ -1,15 +1,13 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
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.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL42;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -20,40 +18,40 @@ public class DSAUnsupported implements DSAAccess {
@Override
public void generateMipmaps(int texture, int target) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GL30.glGenerateMipmap(target);
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();
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GL11.glBindTexture(target, cachedBinding); // Restore to cache value
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();
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
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();
GL11.glBindTexture(target, texture);
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
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();
GL11.glBindTexture(target, texture);
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
RENDER_BACKEND.bindTexture(target, cachedBinding);
}
@Override
@@ -77,13 +75,13 @@ public class DSAUnsupported implements DSAAccess {
@Override
public void readBuffer(int framebuffer, int buffer) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
GL11.glReadBuffer(buffer);
RENDER_BACKEND.readBuffer(buffer);
}
@Override
public void drawBuffers(int framebuffer, IntBuffer buffers) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
GL20.glDrawBuffers(buffers);
RENDER_BACKEND.drawBuffers(buffers);
}
@Override
@@ -95,9 +93,9 @@ public class DSAUnsupported implements DSAAccess {
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
final int previous = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
final int result = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, pname);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previous);
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;
}
@@ -105,7 +103,7 @@ public class DSAUnsupported implements DSAAccess {
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);
GL11.glCopyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
RENDER_BACKEND.copyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
}
@@ -123,9 +121,9 @@ public class DSAUnsupported implements DSAAccess {
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = GL15.glGenBuffers();
final int buffer = RENDER_BACKEND.genBuffers();
GLStateManager.glBindBuffer(target, buffer);
RenderSystem.bufferData(target, data, usage);
RENDER_BACKEND.bufferData(target, data, usage);
GLStateManager.glBindBuffer(target, 0);
return buffer;
@@ -136,13 +134,13 @@ public class DSAUnsupported implements DSAAccess {
int bufferChoice, int filter) {
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, source);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, dest);
GL30.glBlitFramebuffer(offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
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);
GL30.glFramebufferTexture2D(fbtarget, attachment, target, texture, levels);
RENDER_BACKEND.framebufferTexture2D(fbtarget, attachment, target, texture, levels);
}
@Override
@@ -154,32 +152,32 @@ public class DSAUnsupported implements DSAAccess {
@Override
public int createTexture(int target) {
final int texture = GL11.glGenTextures();
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 = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_1D);
GL11.glBindTexture(target, texture);
GL42.glTexStorage1D(target, levels, internalFormat, width);
GL11.glBindTexture(target, previous);
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() : GL11.glGetInteger(GL31.GL_TEXTURE_BINDING_RECTANGLE);
GL11.glBindTexture(target, texture);
GL42.glTexStorage2D(target, levels, internalFormat, width, height);
GL11.glBindTexture(target, previous);
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 = GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D);
GL11.glBindTexture(target, texture);
GL42.glTexStorage3D(target, levels, internalFormat, width, height, depth);
GL11.glBindTexture(target, previous);
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);
}
}
@@ -4,8 +4,6 @@ import java.util.Arrays;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -34,8 +32,6 @@ import org.lwjgl.opengl.GL13;
* 42-46: arg2 Alpha
*/
public final class FragmentKey {
private static final Logger LOGGER = LogManager.getLogger("FragmentKey");
public static final int TEX_ENV_MODULATE = 0;
public static final int TEX_ENV_REPLACE = 1;
public static final int TEX_ENV_ADD = 2;
@@ -297,7 +293,7 @@ public final class FragmentKey {
case GL13.GL_PRIMARY_COLOR -> SRC_PRIMARY_COLOR;
case GL13.GL_PREVIOUS -> SRC_PREVIOUS;
default -> {
LOGGER.debug("Unknown TexEnv source 0x{}, falling back to SRC_PREVIOUS", Integer.toHexString(glSource));
GLStateManager.LOGGER.debug("Unknown TexEnv source 0x{}, falling back to SRC_PREVIOUS", Integer.toHexString(glSource));
yield SRC_PREVIOUS;
}
};
@@ -85,7 +85,7 @@ public final class FragmentShaderGenerator {
private static void emitTexEnvChain(StringBuilder sb, FragmentKey key) {
if (!key.textureEnabled()) {
sb.append(" // No texture — vertex color only\n");
sb.append(" // No texture -- vertex color only\n");
sb.append(" vec4 color = v_Color;\n");
return;
}
@@ -273,21 +273,27 @@ public final class FragmentShaderGenerator {
sb.append(" // Alpha test\n");
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
sb.append(" float qa = round(color.a * 255.0);\n");
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");
sb.append(" if (qa >= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_EQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa != qr) discard;\n");
}
case GL11.GL_LEQUAL ->
sb.append(" if (color.a > u_AlphaRef) discard;\n");
sb.append(" if (qa > u_AlphaRef * 255.0) 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");
sb.append(" if (qa <= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_NOTEQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa == qr) discard;\n");
}
case GL11.GL_GEQUAL ->
sb.append(" if (color.a < u_AlphaRef) discard;\n");
sb.append(" if (qa < u_AlphaRef * 255.0) discard;\n");
case GL11.GL_ALWAYS -> {}
}
}
@@ -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");
}
}
@@ -2,22 +2,24 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import lombok.Getter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug;
import java.util.concurrent.atomic.AtomicInteger;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* A compiled and linked FFP emulation program (vertex + fragment shader pair). Owns the GL program handle and caches uniform locations.
* A compiled and linked FFP emulation program (vertex + fragment + optional geometry shaders). Owns the GL program handle and caches uniform locations.
*/
public class Program {
private static final Logger LOGGER = LogManager.getLogger("FFPProgram");
private static final AtomicInteger PROGRAM_COUNTER = new AtomicInteger();
@Getter private final int programId;
@@ -82,6 +84,10 @@ public class Program {
// 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;
@@ -151,6 +157,10 @@ public class Program {
// 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);
@@ -164,77 +174,98 @@ public class Program {
}
private int loc(String name) {
return GL20.glGetUniformLocation(programId, name);
return RENDER_BACKEND.getUniformLocation(programId, name);
}
public void destroy() {
GL20.glDeleteProgram(programId);
GLStateManager.glDeleteProgram(programId);
}
/**
* Compile a shader, link it into a program, and return the FFPProgram.
* 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) {
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 int vs = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + Long.toHexString(vk.pack()));
final int fs = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
final String vkHex = Long.toHexString(vk.pack());
final boolean hasGeom = geomSrc != null;
final int program = GL20.glCreateProgram();
GL20.glAttachShader(program, vs);
GL20.glAttachShader(program, fs);
GL20.glLinkProgram(program);
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);
}
final String log = RenderSystem.getProgramInfoLog(program);
if (!log.isEmpty()) {
LOGGER.warn("FFP program link log (vk=0x{}, fk={}): {}", Long.toHexString(vk.pack()), fk, log);
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;
}
final int linkStatus = GL20.glGetProgrami(program, GL20.GL_LINK_STATUS);
if (linkStatus != GL11.GL_TRUE) {
GL20.glDeleteProgram(program);
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
throw new RuntimeException("FFP shader link failed (vk=0x" + Long.toHexString(vk.pack()) + ", fk=" + fk + "): " + log);
}
final String debugName = "FFP(v=0x" + Long.toHexString(vk.pack()) + ",f=" + id + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
// Detach and delete individual shaders — they're linked into the program
GL20.glDetachShader(program, vs);
GL20.glDetachShader(program, fs);
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
final Program ffpProgram = new Program(program, vk, fk);
final int previousProgram = GLStateManager.getActiveProgram();
GL20.glUseProgram(program);
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) GL20.glUniform1i(ffpProgram.locSampler[i], i);
}
GL20.glUseProgram(previousProgram);
return ffpProgram;
}
private static int compileShader(int type, String src, String name) {
final int shader = GL20.glCreateShader(type);
GL20.glShaderSource(shader, src);
GL20.glCompileShader(shader);
final RenderBackend backend = RENDER_BACKEND;
final int shader = backend.createShader(type);
backend.shaderSource(shader, src);
backend.compileShader(shader);
final String typeName = (type == GL20.GL_VERTEX_SHADER) ? "vertex" : "fragment";
final String typeName = switch (type) {
case GL20.GL_VERTEX_SHADER -> "vertex";
case GL20.GL_FRAGMENT_SHADER -> "fragment";
case GL32.GL_GEOMETRY_SHADER -> "geometry";
case GL40.GL_TESS_CONTROL_SHADER -> "tess_control";
case GL40.GL_TESS_EVALUATION_SHADER -> "tess_evaluation";
case GL43.GL_COMPUTE_SHADER -> "compute";
default -> "unknown(0x" + Integer.toHexString(type) + ")";
};
GLDebug.nameObject(KHRDebug.GL_SHADER, shader, name + "(" + typeName + ")");
final String log = RenderSystem.getShaderInfoLog(shader);
final String log = backend.getShaderInfoLog(shader, backend.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
if (!log.isEmpty()) {
LOGGER.warn("FFP {} shader compilation log for {}: {}", typeName, name, log);
GLStateManager.LOGGER.warn("FFP {} shader compilation log for {}: {}", typeName, name, log);
}
final int result = GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS);
final int result = backend.getShaderi(shader, GL20.GL_COMPILE_STATUS);
if (result != GL11.GL_TRUE) {
LOGGER.error("FFP {} shader source:\n{}", typeName, src);
GL20.glDeleteShader(shader);
GLStateManager.LOGGER.error("FFP {} shader source:\n{}", typeName, src);
backend.deleteShader(shader);
throw new RuntimeException("FFP " + typeName + " shader compilation failed for " + name + ": " + log);
}
@@ -2,8 +2,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Setter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -26,12 +25,12 @@ import java.util.IdentityHashMap;
* </pre>
*/
public class ShaderCache {
private static final Logger LOGGER = LogManager.getLogger("FFPShaderCache");
private static final int SLOT_STRIDE = 6;
private static final int INITIAL_CAPACITY = 64; // power of 2
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
// Open-addressed table — parallel arrays
private long[] keys;
@@ -108,7 +107,8 @@ public class ShaderCache {
uniqueFragCount++;
}
final Program program = Program.create(vk, fk, vertSrc, fragSrc);
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;
@@ -121,11 +121,14 @@ public class ShaderCache {
setLastUsed(vkPacked, fkPacked, fkLen, program);
LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, count);
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;
@@ -216,6 +219,10 @@ public class ShaderCache {
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 {
@@ -225,7 +232,7 @@ public class ShaderCache {
Files.writeString(file, source, StandardCharsets.UTF_8);
}
} catch (IOException e) {
LOGGER.warn("Failed to dump FFP shader to {}: {}", dumpDir, e.getMessage());
GLStateManager.LOGGER.warn("Failed to dump FFP shader to {}: {}", dumpDir, e.getMessage());
}
}
@@ -244,10 +251,13 @@ public class ShaderCache {
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
}
}
LOGGER.info("Dumped {} vertex and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), seen.size(), outputDir);
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) {
LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
GLStateManager.LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
}
}
@@ -264,9 +274,10 @@ public class ShaderCache {
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
LOGGER.debug("FFP shader cache cleared");
GLStateManager.LOGGER.debug("FFP shader cache cleared");
}
public int getProgramCount() {
@@ -1,33 +1,33 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import java.util.Arrays;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.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 net.coderbot.iris.gl.blending.BlendModeStorage;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL20;
import java.nio.file.Paths;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* FFP shader manager
*/
public class ShaderManager {
private static final Logger LOGGER = LogManager.getLogger("FFPShaderManager");
private static final class Holder { static final ShaderManager INSTANCE = new ShaderManager(); }
private static final class Holder {
static final ShaderManager INSTANCE = new ShaderManager();
}
private final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
@@ -52,12 +52,7 @@ public class ShaderManager {
private ShaderManager() {
vaoVertexFlags.defaultReturnValue(-1);
boolean isDev = false;
try {
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
isDev = Boolean.TRUE.equals(deobfEnv);
} catch (Exception ignored) {}
if (isDev || Boolean.parseBoolean(System.getProperty("angelica.ffp.dumpShaders", "false"))) {
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) {
cache.setDumpDir(Paths.get("ffp_shaders"));
}
}
@@ -75,7 +70,7 @@ public class ShaderManager {
return false;
});
LOGGER.info("FFP shader emulation enabled");
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
public void disable() {
@@ -95,11 +90,10 @@ public class ShaderManager {
currentFKLen = 0;
}
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
GLStateManager.flushDeferredVertexAttribs();
BlendModeStorage.flushDeferredBlend();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
if (!active) {
if (enabled) {
@@ -149,7 +143,7 @@ public class ShaderManager {
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
GL20.glUseProgram(currentProgram.getProgramId());
RENDER_BACKEND.useProgram(currentProgram.getProgramId());
}
private void uploadUniforms() {
@@ -171,10 +165,10 @@ public class ShaderManager {
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 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);
@@ -190,7 +184,8 @@ public class ShaderManager {
public void destroy() {
cache.destroy();
uniforms.destroy();
TessellatorStreamingDrawer.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;
@@ -6,16 +6,16 @@ import com.gtnewhorizons.angelica.glsm.states.FogState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import net.minecraft.client.renderer.OpenGlHelper;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Uploads GLSM cached state to the active FFP program's uniforms.
@@ -60,6 +60,9 @@ public class Uniforms {
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.
@@ -116,10 +119,10 @@ public class Uniforms {
}
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
if (programChanged || OpenGlHelper.lastBrightnessX != lastLightmapX || OpenGlHelper.lastBrightnessY != lastLightmapY) {
if (programChanged || GLSMConfig.lastBrightnessX != lastLightmapX || GLSMConfig.lastBrightnessY != lastLightmapY) {
uploadCurrentLightmapCoord(program);
lastLightmapX = OpenGlHelper.lastBrightnessX;
lastLightmapY = OpenGlHelper.lastBrightnessY;
lastLightmapX = GLSMConfig.lastBrightnessX;
lastLightmapY = GLSMConfig.lastBrightnessY;
}
}
@@ -144,6 +147,11 @@ public class Uniforms {
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) {
@@ -154,7 +162,7 @@ public class Uniforms {
if (mvChanged) {
if (program.locModelViewMatrix != -1) {
mv.get(mat4Buf);
GL20.glUniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
}
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
@@ -163,14 +171,14 @@ public class Uniforms {
}
if (program.locNormalMatrix != -1) {
normalMatrix.get(mat3Buf);
GL20.glUniformMatrix3(program.locNormalMatrix, false, 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();
GL20.glUniform1f(program.locNormalScale, scale);
RENDER_BACKEND.uniform1f(program.locNormalScale, scale);
}
}
@@ -178,7 +186,7 @@ public class Uniforms {
if (projChanged) {
if (program.locProjectionMatrix != -1) {
proj.get(mat4Buf);
GL20.glUniformMatrix4(program.locProjectionMatrix, false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locProjectionMatrix, false, mat4Buf);
}
}
@@ -186,7 +194,7 @@ public class Uniforms {
if ((mvChanged || projChanged) && program.locMVPMatrix != -1) {
proj.mul(mv, mvpMatrix);
mvpMatrix.get(mat4Buf);
GL20.glUniformMatrix4(program.locMVPMatrix, false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
}
// Texture matrices
@@ -195,14 +203,14 @@ public class Uniforms {
if (program.locTextureMatrix0 != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
texMat.get(mat4Buf);
GL20.glUniformMatrix4(program.locTextureMatrix0, false, 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);
GL20.glUniformMatrix4(program.locLightmapTextureMatrix, false, mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locLightmapTextureMatrix, false, mat4Buf);
}
}
}
@@ -230,7 +238,7 @@ public class Uniforms {
uploadVec4(program.locMaterialDiffuse, mat.diffuse);
uploadVec4(program.locMaterialSpecular, mat.specular);
if (program.locMaterialShininess != -1) {
GL20.glUniform1f(program.locMaterialShininess, mat.shininess);
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
}
if (vk.light0Enabled()) {
@@ -269,7 +277,7 @@ public class Uniforms {
}
if (program.locMaterialShininess != -1) {
GL20.glUniform1f(program.locMaterialShininess, mat.shininess);
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
}
// Per-light products
@@ -299,7 +307,7 @@ public class Uniforms {
vec3Buf.put(lightVal.y * materialVal.y);
vec3Buf.put(lightVal.z * materialVal.z);
vec3Buf.flip();
GL20.glUniform3(loc, vec3Buf);
RENDER_BACKEND.uniform3(loc, vec3Buf);
}
private void uploadCurrentColor(Program program) {
@@ -312,7 +320,7 @@ public class Uniforms {
vec4Buf.put(color.getBlue());
vec4Buf.put(color.getAlpha());
vec4Buf.flip();
GL20.glUniform4(program.locCurrentColor, vec4Buf);
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
}
private void uploadCurrentTexCoord(Program program) {
@@ -323,8 +331,8 @@ public class Uniforms {
private void uploadCurrentLightmapCoord(Program program) {
if (program.locCurrentLightmapCoord == -1) return;
GL20.glUniform2f(program.locCurrentLightmapCoord,
OpenGlHelper.lastBrightnessX, OpenGlHelper.lastBrightnessY);
RENDER_BACKEND.uniform2f(program.locCurrentLightmapCoord,
GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY);
}
private void uploadCurrentNormal(Program program) {
@@ -336,7 +344,7 @@ public class Uniforms {
vec3Buf.put(normal.y);
vec3Buf.put(normal.z);
vec3Buf.flip();
GL20.glUniform3(program.locCurrentNormal, vec3Buf);
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
}
private void uploadTexGen(Program program) {
@@ -376,7 +384,7 @@ public class Uniforms {
vec4Buf.put(plane[2]);
vec4Buf.put(plane[3]);
vec4Buf.flip();
GL20.glUniform4(loc, vec4Buf);
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
private void uploadClipPlanes(Program program) {
@@ -387,7 +395,7 @@ public class Uniforms {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
GL20.glUniform4(program.locClipPlanes, clipPlaneBuf);
RENDER_BACKEND.uniform4(program.locClipPlanes, clipPlaneBuf);
}
private void uploadFragmentUniforms(Program program) {
@@ -395,7 +403,7 @@ public class Uniforms {
// Alpha test reference
if (fk.alphaTestEnabled() && program.locAlphaRef != -1) {
GL20.glUniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
RENDER_BACKEND.uniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
}
// Per-unit tex env color
@@ -408,7 +416,7 @@ public class Uniforms {
vec4Buf.put(envState.envColorB);
vec4Buf.put(envState.envColorA);
vec4Buf.flip();
GL20.glUniform4(program.locTexEnvColor[i], vec4Buf);
RENDER_BACKEND.uniform4(program.locTexEnvColor[i], vec4Buf);
}
}
@@ -436,7 +444,7 @@ public class Uniforms {
vec4Buf.put((float)(density / LN2)); // [2]: density/ln(2)
vec4Buf.put((float)(density / SQRT_LN2)); // [3]: density/sqrt(ln(2))
vec4Buf.flip();
GL20.glUniform4(program.locFogParams, vec4Buf);
RENDER_BACKEND.uniform4(program.locFogParams, vec4Buf);
}
if (program.locFogColor != -1) {
@@ -446,7 +454,22 @@ public class Uniforms {
vec4Buf.put((float) fog.getFogColor().z);
vec4Buf.put(fog.getFogAlpha());
vec4Buf.flip();
GL20.glUniform4(program.locFogColor, vec4Buf);
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;
}
}
@@ -458,7 +481,7 @@ public class Uniforms {
vec4Buf.put(v.z);
vec4Buf.put(v.w);
vec4Buf.flip();
GL20.glUniform4(loc, vec4Buf);
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
public void destroy() {
@@ -42,6 +42,7 @@ public final class VertexKey {
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;
@@ -92,6 +93,8 @@ public final class VertexKey {
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; }
@@ -223,6 +226,11 @@ public final class VertexKey {
bits |= (1L << BIT_CLIP_PLANES);
}
// Wide line GS emulation
if (GLStateManager.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
return bits;
}
@@ -248,10 +256,10 @@ public final class VertexKey {
@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]",
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());
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
}
}
@@ -37,7 +37,15 @@ public final class VertexShaderGenerator {
}
sb.append("}\n");
return sb.toString();
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) {
@@ -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();
}
@@ -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,33 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.hooks.events.*;
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);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.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();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
private GLSMHooks() {}
}
@@ -0,0 +1,112 @@
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 final boolean enableDSA;
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;
this.enableDSA = builder.enableDSA;
}
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 boolean isDSAEnabled() { return enableDSA; }
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 boolean enableDSA = false;
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 Builder enableDSA(boolean enableDSA) {
this.enableDSA = enableDSA;
return this;
}
public GLSMInitConfig build() {
return new GLSMInitConfig(this);
}
}
}
@@ -0,0 +1,9 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
/**
* AlphaTest changed - Listeners should query GLSM alpha state directly if they care what changed.
*/
public final class AlphaStateChangeEvent extends MutableEvent {
}
@@ -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;
}
@@ -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 {}
@@ -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;
}
@@ -0,0 +1,9 @@
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;
public boolean postBind;
}
@@ -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;
}
@@ -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;
}
@@ -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,32 @@
package com.gtnewhorizons.angelica.glsm.loading;
import java.util.List;
/**
* Verifies required classes/resources are present on the classpath at load time.
* Fails fast with a descriptive error message if a dependency is missing.
*/
public final class DependencyVerifier {
/**
* A single dependency check: a classpath resource that must exist.
* @param resourcePath Resource path to check (e.g. "/com/example/SomeClass.class")
* @param errorMessage Human-readable error message if missing (include download URL)
*/
public record Check(String resourcePath, String errorMessage) {}
/**
* Verify all checks pass. Throws RuntimeException on first failure.
* @param anchor Class whose classloader to use for resource lookups
* @param checks Dependency checks to run
*/
public static void verify(Class<?> anchor, List<Check> checks) {
for (Check check : checks) {
if (anchor.getResource(check.resourcePath()) == null) {
throw new RuntimeException(check.errorMessage());
}
}
}
private DependencyVerifier() {}
}
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class EarlyRedirectorCore {
private final GLSMRedirector core;
private final String[] targets;
private final String[] exclusions;
public EarlyRedirectorCore(String... extraExclusions) {
this.core = new GLSMRedirector();
this.targets = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone();
final List<String> excl = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
if (extraExclusions != null && extraExclusions.length > 0) {
Collections.addAll(excl, extraExclusions);
}
this.exclusions = excl.toArray(new String[0]);
}
public byte[] transform(String transformedName, byte[] basicClass) {
if (basicClass == null) return null;
if (!startsWithAny(transformedName, targets)) return basicClass;
if (startsWithAny(transformedName, exclusions)) return basicClass;
if (!core.shouldTransform(basicClass)) return basicClass;
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (core.transformClassNode(transformedName, cn)) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
return basicClass;
}
private static boolean startsWithAny(String name, String[] prefixes) {
for (String p : prefixes) {
if (name.startsWith(p)) return true;
}
return false;
}
}
@@ -0,0 +1,78 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.loading.TransformerNarrower.NarrowRule;
import java.util.List;
public final class EcosystemNarrowRules {
public static final List<NarrowRule> ALL = List.of(
new NarrowRule("DragonAPI", "Reika.DragonAPI.ASM", List.of(
"Reika.DragonAPI.ASM.Patchers",
"Reika.DragonAPI.ASM.Profiling",
"Reika.DragonAPI.ASM.APIStripper",
"Reika.DragonAPI.ASM.ClassReparenter",
"Reika.DragonAPI.ASM.DependentMethodStripper",
"Reika.DragonAPI.ASM.DragonAPIClassTransformer",
"Reika.DragonAPI.ASM.FMLItemBlockPatch",
"Reika.DragonAPI.ASM.FluidNamePatch",
"Reika.DragonAPI.ASM.InterfaceInjector",
"Reika.DragonAPI.ASM.SpecialBiomePlacement",
"Reika.DragonAPI.ASM.StructureLootHooks"
)),
new NarrowRule("Xaeros_common", "xaero.common.core", List.of(
"xaero.common.core.transformer"
)),
new NarrowRule("Xaeros_map", "xaero.map.core", List.of(
"xaero.map.core.transformer"
)),
new NarrowRule("AdvancedLightsabers", "com.fiskmods.lightsabers.asm", List.of(
"com.fiskmods.lightsabers.asm.ALLoadingPlugin",
"com.fiskmods.lightsabers.asm.ASMHooks",
"com.fiskmods.lightsabers.asm.transformers"
)),
new NarrowRule("Ears", "com.unascribed.ears", List.of(
"com.unascribed.ears.asm",
"com.unascribed.ears.common.agent"
)),
new NarrowRule("Alfheim", "alfheim.common.core.asm.hook", List.of(
"alfheim.common.core.asm.hook.AlfheimFieldHookHandler",
"alfheim.common.core.asm.hook.AlfheimHPHooks",
"alfheim.common.core.asm.hook.Botania18AndUpBackport",
"alfheim.common.core.asm.hook.ElementalDamageAdapter",
"alfheim.common.core.asm.hook.integration",
"alfheim.common.core.asm.hook.extender.FurnaceExtender",
"alfheim.common.core.asm.hook.extender.ItemAuraRingExtender",
"alfheim.common.core.asm.hook.extender.ItemLensExtender",
"alfheim.common.core.asm.hook.extender.ItemTwigWandExtender",
"alfheim.common.core.asm.hook.extender.LightRelayExtender",
"alfheim.common.core.asm.hook.extender.PureDaisyExtender",
"alfheim.common.core.asm.hook.extender.QuartzExtender",
"alfheim.common.core.asm.hook.extender.RelicHooks",
"alfheim.common.core.asm.hook.extender.SparkExtender",
"alfheim.common.core.asm.hook.fixes.BotaniaGlowingRenderFixes",
"alfheim.common.core.asm.hook.fixes.CorporeaInputFix",
"alfheim.common.core.asm.hook.fixes.GodAttributesHooks",
"alfheim.common.core.asm.hook.fixes.RecipeAncientWillsFix"
))
);
public static final String[] LWJGL3IFY_EXCLUSIONS_SHARED = {
"org.embeddedt.embeddium",
"org.taumc.celeritas",
"com.mitchej123.lwjgl.lwjgl3",
"com.mitchej123.glsm",
"com.gtnewhorizons.angelica.lwjgl3",
};
public static final String[] EARLY_REDIRECTOR_TARGETS = {
"cn.tesseract.mycelium.",
};
private EcosystemNarrowRules() {}
}
@@ -0,0 +1,66 @@
package com.gtnewhorizons.angelica.glsm.loading;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Narrows overly-broad transformer exclusions that prevent the GLSM redirector from
* transforming GL calls in mod code. Some coremods register exclusions covering their
* entire package tree, which inadvertently shields rendering code from redirection.
* <p>
* This utility removes the broad exclusion and re-adds narrower ones that cover only
* the coremod's own transformer/ASM classes, leaving rendering code exposed.
* <p>
* Operates on a {@code Set<String>} (the transformer exceptions set from LaunchClassLoader)
* and a {@code Map<String, Object>} (the Launch blackboard). Callers extract these from
* the Forge/launchwrapper infrastructure.
*/
public final class TransformerNarrower {
private static final Logger LOGGER = LogManager.getLogger("GLSM");
/**
* A rule that replaces a broad transformer exclusion with narrower ones.
* @param name Human-readable name for logging (e.g. "DragonAPI")
* @param broad The broad exclusion to remove (e.g. "Reika.DragonAPI.ASM")
* @param narrow Narrower exclusions to add in its place
*/
public record NarrowRule(String name, String broad, List<String> narrow) {}
/**
* Apply narrowing rules to the transformer exceptions set.
*
* @param exceptions The mutable Set from LaunchClassLoader.transformerExceptions
* @param blackboard The Launch.blackboard map (checked for disable flags)
* @param keyPrefix Blackboard key prefix for per-rule opt-out (e.g. "angelica" or "umbra").
* Rule "DragonAPI" checks key "{prefix}.narrow.DragonAPI". If explicitly
* set to Boolean.FALSE, the rule is skipped.
* @param callerName Name for log messages (e.g. "Angelica" or "Umbra")
* @param rules The narrowing rules to apply
* @return number of rules that were applied
*/
public static int narrow(Set<String> exceptions, Map<String, Object> blackboard,
String keyPrefix, String callerName, List<NarrowRule> rules) {
int applied = 0;
for (NarrowRule rule : rules) {
final String key = keyPrefix + ".narrow." + rule.name();
if (Boolean.FALSE.equals(blackboard.get(key))) {
continue;
}
if (exceptions.remove(rule.broad())) {
for (String narrow : rule.narrow()) {
exceptions.add(narrow);
}
LOGGER.info("[{}] Narrowed {} transformer exclusion to allow GL redirection", callerName, rule.broad());
applied++;
}
}
return applied;
}
private TransformerNarrower() {}
}
@@ -0,0 +1,178 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public final class AttribSnapshot {
public record AttribDesc(int location, int size, int type, boolean normalized, int stride, long offset, int sourceVboId, ByteBuffer readBuffer, long readBufferBaseOffset) {
public int effectiveStride() {
return stride != 0 ? stride : size * VertexAttribState.Attrib.glTypeSizeBytes(type);
}
public int typeSizeBytes() {
return VertexAttribState.Attrib.glTypeSizeBytes(type);
}
}
private final AttribDesc[] perLocation;
private final List<ByteBuffer> allocatedBuffers;
private boolean freed;
private AttribSnapshot(AttribDesc[] perLocation, List<ByteBuffer> allocatedBuffers) {
this.perLocation = perLocation;
this.allocatedBuffers = allocatedBuffers;
}
public AttribDesc get(int location) {
return (location >= 0 && location < perLocation.length) ? perLocation[location] : null;
}
public int maxLocations() {
return perLocation.length;
}
public void free() {
if (freed) return;
freed = true;
for (ByteBuffer buf : allocatedBuffers) {
MemoryUtilities.memFree(buf);
}
allocatedBuffers.clear();
}
/**
* Snapshot attribs over a tight vertex window {@code [firstVertex, firstVertex+vertexCount)}.
*
* <p>Hot path (single VBO backing every enabled attrib — the typical draw) runs without
* allocating any hashmaps. A second distinct VBO promotes to the map-backed fallback.
*/
public static AttribSnapshot snapshot(int firstVertex, int vertexCount) {
final AttribDesc[] out = new AttribDesc[VertexAttribState.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
int rangeSoleVboId = 0;
long rangeSoleStart = Long.MAX_VALUE;
long rangeSoleEnd = Long.MIN_VALUE;
Int2ObjectOpenHashMap<long[]> vboRanges = null;
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * VertexAttribState.Attrib.glTypeSizeBytes(a.type);
final long start = a.offset + (long) firstVertex * stride;
final long end = a.offset + lastVtx * stride + typeBytes;
if (vboRanges != null) {
long[] r = vboRanges.get(a.vboId);
if (r == null) {
vboRanges.put(a.vboId, new long[] { start, end });
} else {
if (start < r[0]) r[0] = start;
if (end > r[1]) r[1] = end;
}
} else if (rangeSoleVboId == 0 || rangeSoleVboId == a.vboId) {
rangeSoleVboId = a.vboId;
if (start < rangeSoleStart) rangeSoleStart = start;
if (end > rangeSoleEnd) rangeSoleEnd = end;
} else {
// Spill: second distinct vboId. Promote the accumulated range to the map.
vboRanges = new Int2ObjectOpenHashMap<>();
vboRanges.put(rangeSoleVboId, new long[] { rangeSoleStart, rangeSoleEnd });
vboRanges.put(a.vboId, new long[] { start, end });
}
}
// Pass 2 — readback cache. Same spill pattern.
int readSoleVboId = 0;
ByteBuffer readSoleBuf = null;
long readSoleOffset = 0;
Int2ObjectOpenHashMap<ByteBuffer> vboBuffers = null;
Int2LongOpenHashMap vboReadOffsets = null;
try {
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
if (a.vboId != 0) {
ByteBuffer buf;
long readOffset;
if (vboBuffers != null) {
buf = vboBuffers.get(a.vboId);
readOffset = buf != null ? vboReadOffsets.get(a.vboId) : 0;
} else if (readSoleVboId == a.vboId) {
buf = readSoleBuf;
readOffset = readSoleOffset;
} else {
buf = null;
readOffset = 0;
}
if (buf == null) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) {
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
continue;
}
final long rStart;
final long rEnd;
if (vboRanges != null) {
final long[] range = vboRanges.get(a.vboId);
rStart = range[0];
rEnd = range[1];
} else {
rStart = rangeSoleStart;
rEnd = rangeSoleEnd;
}
final long ro = Math.max(0, rStart);
final int rs = (int) (Math.min(bufferSize, rEnd) - ro);
if (rs <= 0) continue;
buf = MemoryUtilities.memAlloc(rs);
GLStateManager.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, ro, buf);
allocated.add(buf);
readOffset = ro;
if (vboBuffers != null) {
vboBuffers.put(a.vboId, buf);
vboReadOffsets.put(a.vboId, ro);
} else if (readSoleVboId == 0) {
readSoleVboId = a.vboId;
readSoleBuf = buf;
readSoleOffset = ro;
} else {
vboBuffers = new Int2ObjectOpenHashMap<>();
vboReadOffsets = new Int2LongOpenHashMap();
vboBuffers.put(readSoleVboId, readSoleBuf);
vboReadOffsets.put(readSoleVboId, readSoleOffset);
vboBuffers.put(a.vboId, buf);
vboReadOffsets.put(a.vboId, ro);
}
}
out[i] = new AttribDesc(i, a.size, a.type, a.normalized, a.stride, a.offset, a.vboId, buf, readOffset);
} else if (a.clientPointer != null) {
out[i] = new AttribDesc(i, a.size, a.type, a.normalized, a.stride, a.clientPointer.position(), 0, a.clientPointer, 0L);
}
}
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
return new AttribSnapshot(out, allocated);
}
}
@@ -600,23 +600,6 @@ public final class CommandBuffer {
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);
@@ -490,10 +490,10 @@ public final class CommandBufferExecutor {
// Restore attributes based on flags
if ((flags & VertexFlags.COLOR_BIT) != 0) {
final int color = memGetInt(ptr + 8);
byte a = (byte) ((color >> 24) & 0xFF);
byte r = (byte) ((color >> 16) & 0xFF);
byte g = (byte) ((color >> 8) & 0xFF);
byte b = (byte) ((color & 0xFF));
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) {
@@ -530,14 +530,6 @@ public final class CommandBufferExecutor {
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;
@@ -1,18 +1,12 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.Deque;
public final class CommandBufferProcessor {
private static final Logger LOGGER = LogManager.getLogger("CommandBufferProcessor");
private static final Matrix4f tempMatrix = new Matrix4f();
private CommandBufferProcessor() {}
@@ -156,8 +150,6 @@ public final class CommandBufferProcessor {
// 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());
@@ -168,7 +160,7 @@ public final class CommandBufferProcessor {
out.writeComplexRef((DisplayListCommand) obj);
}
default -> LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
default -> GLStateManager.LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
}
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatchBuilder;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
import org.joml.Matrix4f;
import java.nio.FloatBuffer;
@@ -11,6 +13,7 @@ import java.nio.IntBuffer;
*/
public final class CommandRecorder {
private final CommandBuffer buffer;
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
private int commandCount;
public CommandRecorder() {
@@ -26,7 +29,19 @@ public final class CommandRecorder {
return commandCount;
}
public IndexedDrawBatchBuilder getIndexedDraws() {
return indexedDraws;
}
public void recordIndexedDrawCapture(IndexedDrawCapture capture) {
indexedDraws.add(capture);
recordComplexCommand(capture.placeholder);
}
public void free() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
buffer.free();
}
@@ -347,16 +362,6 @@ public final class CommandRecorder {
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++;
@@ -2,12 +2,15 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
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 java.util.Collections;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
@@ -26,16 +29,18 @@ 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);
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList());
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
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO ownedVbos) {
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
this.complexObjects = complexObjects;
this.ownedVbos = ownedVbos;
this.indexedBatches = indexedBatches;
}
/**
@@ -51,7 +56,6 @@ public final class CompiledDisplayList {
* 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) {
@@ -60,11 +64,18 @@ public final class CompiledDisplayList {
}
}
// Close VBOs
// Close VBOs from immediate-mode DisplayListVBOBuilder.
if (ownedVbos != null) {
ownedVbos.delete();
}
// Release shared (VAO, VBO, EBO) triples from IndexedDrawBatchBuilder.
if (indexedBatches != null) {
for (IndexedDrawBatch batch : indexedBatches) {
batch.delete();
}
}
// Free the command buffer (off-heap memory)
if (commandBuffer != null) {
memFree(commandBuffer);
@@ -89,6 +100,10 @@ public final class CompiledDisplayList {
return ownedVbos;
}
public List<IndexedDrawBatch> getIndexedBatches() {
return indexedBatches;
}
// === Test inspection methods ===
/**
@@ -75,6 +75,12 @@ public final class DisplayListVBOBuilder {
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 if (data.drawMode == GL11.GL_QUAD_STRIP) {
// GL_QUAD_STRIP is removed in core profile; an even-length GL_TRIANGLE_STRIP produces the same quads
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_STRIP, start, vertexCount & ~1);
} else if (data.drawMode == GL11.GL_POLYGON) {
// GL_POLYGON is removed in core profile; convert to GL_TRIANGLE_FAN which is equivalent for convex polygons
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_FAN, start, vertexCount);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount);
}
@@ -97,8 +97,6 @@ public final class GLCommand {
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)
/**
@@ -219,7 +217,7 @@ public final class GLCommand {
// 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;
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
// Four int commands (20 bytes)
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
@@ -227,8 +225,6 @@ public final class GLCommand {
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;
@@ -0,0 +1,297 @@
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 org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* 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 class AttribReader {
ByteBuffer data;
int stride;
int baseOffset;
int size;
int loc;
int type;
boolean normalized;
}
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 AttribSnapshot snap = AttribSnapshot.snapshot(first, count);
try {
prepareReaders(snap);
if (positionReaderIndex < 0) return null;
return readVerticesFromBuffer(mode, first, count);
} finally {
snap.free();
}
}
private static void prepareReaders(AttribSnapshot snap) {
nonPositionCount = 0;
positionReaderIndex = -1;
for (int i = 0; i < NUM_ATTRIBS; i++) {
final AttribSnapshot.AttribDesc d = snap.get(i);
if (d == null) continue;
final AttribReader r = readers[i];
r.data = d.readBuffer();
r.stride = d.effectiveStride();
r.baseOffset = (int) (d.offset() - d.readBufferBaseOffset());
r.size = d.size();
r.loc = i;
r.type = d.type();
r.normalized = d.normalized();
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();
}
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(
readComponent(r, base, 0),
readComponent(r, base, 1),
(r.size >= 3) ? readComponent(r, base, 2) : 0.0f);
}
}
private static void emitAttrib(DirectTessellator t, AttribReader r, int base) {
if (r.loc == LOC_NORMAL) {
t.setNormal(
readComponent(r, base, 0),
readComponent(r, base, 1),
readComponent(r, base, 2));
} else if (r.loc == LOC_UV0) {
t.setTextureUV(
readComponent(r, base, 0),
(r.size >= 2) ? readComponent(r, base, 1) : 0.0f);
} else if (r.loc == LOC_UV1) {
if (r.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.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 = readComponent(r, base, 0);
final float cg = readComponent(r, base, 1);
final float cb = readComponent(r, base, 2);
final float ca = (r.size >= 4) ? readComponent(r, base, 3) : 1.0f;
t.setColorRGBA((int) (cr * 255.0f), (int) (cg * 255.0f), (int) (cb * 255.0f), (int) (ca * 255.0f));
}
}
private static float readComponent(AttribReader r, int base, int component) {
return VertexAttribState.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
}
}
@@ -0,0 +1,64 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import java.util.Arrays;
public final class AttribLayoutKey {
final int[] locations;
final int[] sizes;
final int[] types;
final boolean[] normalized;
private final int[] offsets;
private final int stride;
private final int hash;
public AttribLayoutKey(int[] locations, int[] sizes, int[] types, boolean[] normalized) {
this.locations = locations;
this.sizes = sizes;
this.types = types;
this.normalized = normalized;
final int n = locations.length;
final int[] off = new int[n];
int base = 0;
int maxAlign = 1;
for (int i = 0; i < n; i++) {
final int typeSize = VertexAttribState.Attrib.glTypeSizeBytes(types[i]);
if (typeSize > maxAlign) maxAlign = typeSize;
base = (base + typeSize - 1) & -typeSize;
off[i] = base;
base += sizes[i] * typeSize;
}
this.offsets = off;
this.stride = (base + maxAlign - 1) & -maxAlign;
int h = Arrays.hashCode(locations);
h = 31 * h + Arrays.hashCode(sizes);
h = 31 * h + Arrays.hashCode(types);
h = 31 * h + Arrays.hashCode(normalized);
this.hash = h;
}
public int offset(int i) {
return offsets[i];
}
public int stride() {
return stride;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof AttribLayoutKey k)) return false;
return Arrays.equals(locations, k.locations)
&& Arrays.equals(sizes, k.sizes)
&& Arrays.equals(types, k.types)
&& Arrays.equals(normalized, k.normalized);
}
@Override
public int hashCode() {
return hash;
}
}
@@ -0,0 +1,37 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
public final class BatchedIndexedDrawCmd implements DisplayListCommand {
private int sharedVAO;
private int drawMode;
private int indexCount;
private int indexType;
private long indexByteOffset;
public void fill(int sharedVAO, int drawMode, int indexCount, int indexType, long indexByteOffset) {
this.sharedVAO = sharedVAO;
this.drawMode = drawMode;
this.indexCount = indexCount;
this.indexType = indexType;
this.indexByteOffset = indexByteOffset;
}
public int getSharedVAO() { return sharedVAO; }
public int getDrawMode() { return drawMode; }
public int getIndexCount() { return indexCount; }
public int getIndexType() { return indexType; }
public long getIndexByteOffset() { return indexByteOffset; }
@Override
public void execute() {
if (sharedVAO == 0) return;
GLStateManager.glBindVertexArray(sharedVAO);
GLStateManager.glDrawElements(drawMode, indexCount, indexType, indexByteOffset);
}
@Override
public String toString() {
return "BatchedIndexedDrawCmd(vao=" + sharedVAO + ", mode=" + drawMode + ", count=" + indexCount + ", type=" + indexType + ", offset=" + indexByteOffset + ")";
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
public final class IndexedDrawBatch {
private int sharedVAO;
private int sharedVBO;
private int sharedEBO;
private boolean deleted;
public IndexedDrawBatch(int sharedVAO, int sharedVBO, int sharedEBO) {
this.sharedVAO = sharedVAO;
this.sharedVBO = sharedVBO;
this.sharedEBO = sharedEBO;
}
public int getSharedVAO() { return sharedVAO; }
public int getSharedVBO() { return sharedVBO; }
public int getSharedEBO() { return sharedEBO; }
public void delete() {
if (deleted) return;
deleted = true;
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
sharedVAO = sharedVBO = sharedEBO = 0;
}
@Override
public String toString() {
return "IndexedDrawBatch(vao=" + sharedVAO + ", vbo=" + sharedVBO + ", ebo=" + sharedEBO + ")";
}
}
@@ -0,0 +1,171 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
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.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class IndexedDrawBatchBuilder {
private final List<IndexedDrawCapture> captures = new ArrayList<>();
public boolean isEmpty() {
return captures.isEmpty();
}
public void add(IndexedDrawCapture capture) {
captures.add(capture);
}
public List<IndexedDrawCapture> getCaptures() {
return captures;
}
public List<IndexedDrawBatch> build() {
final List<IndexedDrawBatch> out = new ArrayList<>();
if (captures.isEmpty()) return out;
final int prevVAO = GLStateManager.getBoundVAO();
final int prevVBO = GLStateManager.getBoundVBO();
final int prevEBO = GLStateManager.getBoundEBO();
final List<GroupResult> results = new ArrayList<>();
boolean success = false;
try {
final Object2ObjectLinkedOpenHashMap<AttribLayoutKey, List<IndexedDrawCapture>> groups = new Object2ObjectLinkedOpenHashMap<>();
for (IndexedDrawCapture c : captures) {
groups.computeIfAbsent(c.layoutKey, k -> new ArrayList<>()).add(c);
}
for (List<IndexedDrawCapture> group : groups.values()) {
results.add(buildGroup(group));
}
// Placeholder fill is deferred until every group's GL work has succeeded.
for (GroupResult r : results) r.commit();
for (GroupResult r : results) out.add(r.batch);
success = true;
} finally {
if (!success) {
for (GroupResult r : results) r.batch.delete();
}
GLStateManager.glBindVertexArray(prevVAO);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
return out;
}
private record GroupResult(IndexedDrawBatch batch, List<IndexedDrawCapture> captures,
int[] drawModes, int[] indexCounts, long[] indexOffsets) {
void commit() {
final int sharedVAO = batch.getSharedVAO();
for (int i = 0; i < captures.size(); i++) {
captures.get(i).placeholder.fill(sharedVAO, drawModes[i], indexCounts[i], GL11.GL_UNSIGNED_INT, indexOffsets[i]);
}
}
}
private static GroupResult buildGroup(List<IndexedDrawCapture> group) {
long totalVertexBytes = 0;
long totalIndexBytes = 0;
for (IndexedDrawCapture c : group) {
totalVertexBytes += (long) c.vertexCount * c.layoutKey.stride();
totalIndexBytes += (long) c.indexCount * 4; // always GL_UNSIGNED_INT
}
if (totalVertexBytes > Integer.MAX_VALUE || totalIndexBytes > Integer.MAX_VALUE) {
throw new IllegalStateException("batch exceeds 2GiB — split needed");
}
int sharedVAO = 0;
int sharedVBO = 0;
int sharedEBO = 0;
ByteBuffer eboData = null;
boolean success = false;
final int n = group.size();
final int[] drawModes = new int[n];
final int[] indexCounts = new int[n];
final long[] indexOffsets = new long[n];
try {
sharedVAO = GLStateManager.glGenVertexArrays();
sharedVBO = GLStateManager.glGenBuffers();
sharedEBO = GLStateManager.glGenBuffers();
eboData = MemoryUtilities.memAlloc((int) totalIndexBytes);
GLStateManager.glBindVertexArray(sharedVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, totalVertexBytes, GL15.GL_STATIC_DRAW);
int vboBytePos = 0;
int eboBytePos = 0;
int vertexBase = 0;
final long eboAddr = memAddress0(eboData);
for (int g = 0; g < n; g++) {
final IndexedDrawCapture c = group.get(g);
final int vBytes = c.vertexCount * c.layoutKey.stride();
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, vboBytePos, c.vertexData);
final long srcIdxAddr = memAddress0(c.indexData);
final int idxBytes = c.indexCount * 4;
if (vertexBase == 0) {
// First capture (or a group that starts at 0) — indices are already
// rebased to [0..vertexCount) in IndexedDrawCapture, so a raw bulk copy
// is correct and strictly faster than the per-index loop.
memCopy(srcIdxAddr, eboAddr + eboBytePos, idxBytes);
} else {
final long dstBase = eboAddr + eboBytePos;
for (int i = 0; i < c.indexCount; i++) {
memPutInt(dstBase + i * 4L, memGetInt(srcIdxAddr + i * 4L) + vertexBase);
}
}
drawModes[g] = c.drawMode;
indexCounts[g] = c.indexCount;
indexOffsets[g] = eboBytePos;
vboBytePos += vBytes;
eboBytePos += idxBytes;
vertexBase += c.vertexCount;
}
eboData.position(0).limit((int) totalIndexBytes);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboData, GL15.GL_STATIC_DRAW);
final AttribLayoutKey layout = group.get(0).layoutKey;
final int stride = layout.stride();
for (int i = 0; i < layout.locations.length; i++) {
final int loc = layout.locations[i];
final int size = layout.sizes[i];
final int type = layout.types[i];
GLStateManager.glVertexAttribPointer(loc, size, type, layout.normalized[i], stride, layout.offset(i));
GLStateManager.glEnableVertexAttribArray(loc);
}
success = true;
return new GroupResult(new IndexedDrawBatch(sharedVAO, sharedVBO, sharedEBO),
group, drawModes, indexCounts, indexOffsets);
} finally {
if (eboData != null) MemoryUtilities.memFree(eboData);
if (!success) {
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
}
}
}
}
@@ -0,0 +1,242 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.recording.AttribSnapshot;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Set;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
/** {@link #create} returns null on any recoverable validation failure; callers must not record the placeholder. */
public final class IndexedDrawCapture {
public final AttribLayoutKey layoutKey;
public final int drawMode;
public final int vertexCount;
public final int indexCount;
public final ByteBuffer vertexData;
public final ByteBuffer indexData;
public final BatchedIndexedDrawCmd placeholder;
private boolean freed;
public IndexedDrawCapture(AttribLayoutKey layoutKey, int drawMode, int vertexCount, int indexCount,
ByteBuffer vertexData, ByteBuffer indexData,
BatchedIndexedDrawCmd placeholder) {
this.layoutKey = layoutKey;
this.drawMode = drawMode;
this.vertexCount = vertexCount;
this.indexCount = indexCount;
this.vertexData = vertexData;
this.indexData = indexData;
this.placeholder = placeholder;
}
public void freeBuffers() {
if (freed) return;
freed = true;
if (vertexData != null) MemoryUtilities.memFree(vertexData);
if (indexData != null) MemoryUtilities.memFree(indexData);
}
public boolean isFreed() {
return freed;
}
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
private static void warnOnce(String key, String fmt, Object... args) {
if (WARN_ONCE.add(key)) GLStateManager.LOGGER.warn(fmt, args);
}
public static IndexedDrawCapture create(int mode, int indicesCount, int srcIndexType, long indicesOffset, int eboId) {
if (indicesCount == 0) return null;
if (eboId == 0) {
warnOnce("no-ebo", "[IndexedDrawCapture] glDrawElements in display list with no EBO bound — skipping (mode={} count={} offset={})", mode, indicesCount, indicesOffset);
return null;
}
if (mode == GL11.GL_QUADS && (indicesCount % 4) != 0) {
warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
return null;
}
final int srcIndexSize = VertexAttribState.Attrib.glTypeSizeBytes(srcIndexType);
if (srcIndexSize <= 0) return null;
final int prevEBO = GLStateManager.getBoundEBO();
final int prevVBO = GLStateManager.getBoundVBO();
ByteBuffer eboSrc = null;
AttribSnapshot snap = null;
ByteBuffer vertexData = null;
ByteBuffer indexData = null;
boolean success = false;
try {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboBufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboBufferSize <= 0) {
warnOnce("ebo-size", "[IndexedDrawCapture] GL_BUFFER_SIZE={} for EBO {}", eboBufferSize, eboId);
return null;
}
final long eboReadSize = (long) indicesCount * srcIndexSize;
if (indicesOffset < 0 || indicesOffset + eboReadSize > eboBufferSize) {
warnOnce("ebo-range", "[IndexedDrawCapture] EBO range {}+{} exceeds buffer size {} — skipping", indicesOffset, eboReadSize, eboBufferSize);
return null;
}
eboSrc = MemoryUtilities.memAlloc((int) eboReadSize);
GLStateManager.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesOffset, eboSrc);
final long eboSrcAddr = MemoryUtilities.memAddress0(eboSrc);
final long minMax = QuadConverter.scanMinMaxIndex(eboSrcAddr, srcIndexType, indicesCount);
if (minMax == -1L) {
warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
return null;
}
final int minVtx = (int) (minMax & 0xFFFFFFFFL);
final int maxVtx = (int) (minMax >>> 32);
final int vertexCount = maxVtx - minVtx + 1;
snap = AttribSnapshot.snapshot(minVtx, vertexCount);
int enabledCount = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
if (snap.get(i) != null) enabledCount++;
}
if (enabledCount == 0) {
warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
return null;
}
final int[] locations = new int[enabledCount];
final int[] sizes = new int[enabledCount];
final int[] types = new int[enabledCount];
final boolean[] normalized = new boolean[enabledCount];
int k = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final AttribSnapshot.AttribDesc d = snap.get(i);
if (d == null) continue;
locations[k] = i;
sizes[k] = d.size();
types[k] = d.type();
normalized[k] = d.normalized();
k++;
}
final AttribLayoutKey layoutKey = new AttribLayoutKey(locations, sizes, types, normalized);
final int tightStride = layoutKey.stride();
ByteBuffer sharedSrcBuffer = null;
long sharedSrcBase = 0;
int sharedSrcStride = -1;
boolean tightSrc = true;
for (int a = 0; a < enabledCount; a++) {
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
final int relOffset = (int) (d.offset() - d.readBufferBaseOffset());
if (a == 0) {
sharedSrcBuffer = d.readBuffer();
sharedSrcStride = d.effectiveStride();
sharedSrcBase = MemoryUtilities.memAddress0(sharedSrcBuffer) + relOffset + (long) minVtx * sharedSrcStride;
if (relOffset != layoutKey.offset(0)) tightSrc = false;
} else if (d.readBuffer() != sharedSrcBuffer || d.effectiveStride() != sharedSrcStride || relOffset != layoutKey.offset(a)) {
tightSrc = false;
}
}
if (tightSrc && sharedSrcStride != tightStride) tightSrc = false;
vertexData = MemoryUtilities.memAlloc(vertexCount * tightStride);
final long dstAddr = MemoryUtilities.memAddress0(vertexData);
if (tightSrc) {
MemoryUtilities.memCopy(sharedSrcBase, dstAddr, (long) vertexCount * tightStride);
} else {
for (int a = 0; a < enabledCount; a++) {
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
final int attribBytes = d.size() * d.typeSizeBytes();
final int srcStride = d.effectiveStride();
final long srcStart = MemoryUtilities.memAddress0(d.readBuffer()) + (d.offset() - d.readBufferBaseOffset()) + (long) minVtx * srcStride;
final long dstStart = dstAddr + layoutKey.offset(a);
copyStrided(srcStart, srcStride, dstStart, tightStride, attribBytes, vertexCount);
}
}
final int bakedDrawMode;
final int bakedIndexCount;
if (mode == GL11.GL_QUADS) {
final int quadCount = indicesCount / 4;
bakedIndexCount = quadCount * 6;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.triangulateQuads(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
bakedDrawMode = GL11.GL_TRIANGLES;
} else {
bakedIndexCount = indicesCount;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.widenIndices(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
bakedDrawMode = mode;
}
final BatchedIndexedDrawCmd placeholder = new BatchedIndexedDrawCmd();
final IndexedDrawCapture out = new IndexedDrawCapture(layoutKey, bakedDrawMode, vertexCount, bakedIndexCount, vertexData, indexData, placeholder);
success = true;
return out;
} finally {
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
if (snap != null) snap.free();
if (!success) {
if (vertexData != null) MemoryUtilities.memFree(vertexData);
if (indexData != null) MemoryUtilities.memFree(indexData);
}
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
}
/**
* Copy {@code attribBytes} per vertex from {@code src} (stride {@code srcStride}) to
* {@code dst} (stride {@code dstStride}) for {@code vertexCount} vertices.
*
* <p>Specializes on the common attribute sizes (4 / 8 / 12 / 16) so the hot path is
* one or two {@code memPut} calls per vertex instead of a JNI-ish {@code memCopy}
* dispatch. Falls through to {@code memCopy} for exotic sizes.
*/
private static void copyStrided(long src, int srcStride, long dst, int dstStride, int attribBytes, int vertexCount) {
switch (attribBytes) {
case 4 -> {
for (int v = 0; v < vertexCount; v++) {
memPutInt(dst, memGetInt(src));
src += srcStride; dst += dstStride;
}
}
case 8 -> {
for (int v = 0; v < vertexCount; v++) {
memPutLong(dst, memGetLong(src));
src += srcStride; dst += dstStride;
}
}
case 12 -> {
for (int v = 0; v < vertexCount; v++) {
memPutLong(dst, memGetLong(src));
memPutInt(dst + 8, memGetInt(src + 8));
src += srcStride; dst += dstStride;
}
}
case 16 -> {
for (int v = 0; v < vertexCount; v++) {
memPutLong(dst, memGetLong(src));
memPutLong(dst + 8, memGetLong(src + 8));
src += srcStride; dst += dstStride;
}
}
default -> {
for (int v = 0; v < vertexCount; v++) {
MemoryUtilities.memCopy(src, dst, attribBytes);
src += srcStride; dst += dstStride;
}
}
}
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
import org.joml.Matrix4f;
@@ -12,7 +11,6 @@ import java.nio.FloatBuffer;
* Command: glMultMatrix(matrix)
* Multiplies the current matrix by the specified matrix.
*/
@Desugar
public record MultMatrixCmd(Matrix4f matrix, FloatBuffer buffer) implements DisplayListCommand {
/**
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
@@ -16,7 +15,6 @@ 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.
*/
@Desugar
public record TexImage2DCmd(
int target,
int level,
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
@@ -16,7 +15,6 @@ 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.
*/
@Desugar
public record TexSubImage2DCmd(
int target,
int level,
@@ -0,0 +1,657 @@
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.Handle;
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.InvokeDynamicInsnNode;
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.Collections;
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()
// glEnable/Disable - Special cased in GLSMRedirector, but included here for the external API
.add("glEnable")
.add("glDisable")
.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();
}
/** The internal-name prefix used to match GL version classes (e.g., {@code "org/lwjgl/opengl/GL"}). */
public static String getGLPrefix() { return GL_PREFIX; }
/** The internal name of the redirect target class. */
public static String getTargetClassName() { return GLStateManager; }
/** Method redirects for GL-prefix classes. Key: original method name, Value: GLStateManager method name. */
public static Map<String, String> getGLPrefixMethodRedirects() {
return Collections.unmodifiableMap(glMethodRedirects);
}
/** Method redirects for named (non-GL-prefix) classes. Key: internal class name, Value: per-method redirect map. */
public static Map<String, Map<String, String>> getNamedClassMethodRedirects() {
return Collections.unmodifiableMap(methodRedirects);
}
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 : (List<MethodNode>) 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;
}
}
} else if (node instanceof InvokeDynamicInsnNode dynNode) {
// Redirect method handles in invokedynamic bootstrap arguments
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
if (redirects == null) continue;
final String glsmName = redirects.get(handle.getName());
if (glsmName == null) continue;
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
LOGGER.info("Redirecting invokedynamic handle in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, handle.getName(), handle.getDesc(), glsmName, handle.getDesc());
}
dynNode.bsmArgs[i] = new Handle(handle.getTag(), GLStateManager, glsmName, handle.getDesc());
changed = true;
redirectInMethod = true;
}
}
}
if (ASSERT_MAIN_THREAD && redirectInMethod && !transformedName.startsWith(SplashProgress) && !(transformedName.startsWith(MinecraftClient) && ExcludedMinecraftMainThreadChecks.contains(mn.name))) {
mn.instructions.insert(new MethodInsnNode(Opcodes.INVOKESTATIC, GLStateManager, "assertMainThread", "()V", false));
}
}
return changed;
}
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;
}
}
}
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import org.lwjgl.opengl.GL13;
/**
@@ -45,9 +45,9 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
final int currentUnit = GLStateManager.getActiveTextureUnit();
final boolean needsSwitch = currentUnit != unitIndex;
if (needsSwitch) GL13.glActiveTexture(GL13.GL_TEXTURE0 + unitIndex);
if (enabled) GL11.glEnable(glCap); else GL11.glDisable(glCap);
if (needsSwitch) GL13.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
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);
}
}
}
Loaded 100 of 551 files, more files were not shown because too many files have changed in this diff. Show more