Compare commits

..
Author SHA1 Message Date
MustafarYasmin 580b72829d Merge branch 'core-profile' into core-profile-dsa 2026-02-27 12:54:03 +01:00
Jason Mitchell f31161fcc7 OSX 2026-02-26 18:54:33 -08:00
Jason Mitchell 6e6d90aceb Handle frag only shaders and converge vert shaders on ffp names where applicable 2026-02-26 18:31:33 -08:00
Jason Mitchell d96c10d52f Fix botania shaders
maybe implement glClipPlane
2026-02-26 16:23:36 -08:00
MustafarYasmin ef3f500b1c Small opt 2026-02-26 23:21:55 +01:00
MustafarYasmin be2f76e23e Sync binding 2026-02-26 23:20:04 +01:00
MustafarYasmin 6ef0c77848 Add more DSA support for glTexImage2D and glTexSubImage2D.
Remove some redundant checks.
DSA handle long instead of different buffers now, buffers get converted in GLSM this is to reduce duplicate code in DSA and reduce overhead.
2026-02-26 20:49:44 +01:00
MustafarYasmin 67656e6342 Fix shader 2026-02-26 19:33:04 +01:00
Jason Mitchell 09f1cc7f01 Handle universium shader 2026-02-26 09:16:26 -08:00
Jason Mitchell c8619a5e27 Cloud tweaks 2026-02-25 21:56:25 -08:00
Jason Mitchell bca80d5518 J8 fixes/workarounds 2026-02-25 17:12:36 -08:00
Jason Mitchell 4c0c0970aa More compat
* Update CompatShaderTransformer - tested against all glsl in MM & GT5u; handle `texture` attrib/uniform upgrading in glGetAttribLocation and glGetUniformLocation
* Use reflection to reuse the ContextAttribs and request 4.6 as well
* GLU emulation
2026-02-25 16:04:07 -08:00
user e1d150f4d5 add glTextureImage2DEXT DSA 2026-02-25 14:03:56 -08:00
user 28f62ca04b Get highest GL version(excluding 4.6 since lwjgl2 troll)
# Conflicts:
#	build.gradle.kts
#	src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/MixinForgeHooksClient_CoreProfile.java
2026-02-25 14:03:56 -08:00
Jason Mitchell 75c5b0f26f Handle ARBShaderObjects 2026-02-25 10:22:26 -08:00
Jason Mitchell a62fae73c3 Small optimizations
* Font Glyph: Don't check and get, just get with default
* Cache FrameBuffer Bindings
* Only track AO state when we're under celeritas
2026-02-24 21:56:56 -08:00
Jason Mitchell 16a0e1b8a4 Support glScissor in DisplayLists & emulate glLineStipple 2026-02-24 21:17:24 -08:00
Jason Mitchell 91a45c2b69 Persistent Streaming Buffer for the TessellatorStreamingDrawer 2026-02-24 20:20:12 -08:00
Jason Mitchell 3deec73d3e Address comments - vertex flags, and implement glInterleavedArrays 2026-02-24 17:38:06 -08:00
Jason Mitchell c0ea254d62 Address comments about SubVBO.render() 2026-02-24 08:34:38 -08:00
Jason Mitchell dea84d4279 Subset of glTexGen, better legacy shader compatability, and better client array state handling 2026-02-24 07:52:45 -08:00
Jason MitchellandDeath Fuel 7a2286f3d5 Update src/main/resources/assets/angelica/shaders/fontFilter.fsh
Co-authored-by: Death Fuel <[email protected]>
2026-02-24 07:52:45 -08:00
Jason Mitchell c4c44bd14e Move default VAO to Angelica 2026-02-24 07:52:45 -08:00
Jason Mitchell 936d6f82b3 Splash crash fix 2026-02-24 07:52:45 -08:00
Jason Mitchell 6225fcd344 Target OpenGL 3.3 core profile
* Emulates (most of) the Fixed Function Pipeline - with heavy inspiration from Mesa
2026-02-24 07:52:42 -08:00
451 changed files with 6437 additions and 19794 deletions

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@
/.vs/
/.vscode/
/run/
build/
/build/
/eclipse/
.classpath
.project
+4 -33
View File
@@ -7,9 +7,6 @@ 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")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
@@ -21,7 +18,8 @@ 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) {
@@ -48,49 +46,22 @@ 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, downgradeShadowImplForTest)
dependsOn(downgradeEmbedOnlyForTest)
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")
}
+7 -20
View File
@@ -47,8 +47,6 @@ 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
@@ -56,7 +54,6 @@ configurations {
compileClasspath.extendsFrom(embedOnly)
testCompileClasspath.extendsFrom(embedOnly)
testRuntimeClasspath.extendsFrom(embedOnly)
}
@@ -69,18 +66,13 @@ 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.36:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
shadowImplementation("net.minecraftforge:eventbus:7.0-beta.15")
api("com.github.GTNewHorizons:GTNHLib:0.9.10:dev")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
@@ -90,8 +82,8 @@ dependencies {
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.2-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.2-GTNH") { transitive = false }
// Because who doesn't want NEI
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
@@ -119,16 +111,16 @@ dependencies {
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5634_H261"))
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5593_H261"))
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:CoreTweaks:0.3.3.6-GTNH")
// For testing alternative splash screens, can be sourced from https://github.com/MalTeeez/ModernSplash/releases
//runtimeOnly(rfg.deobf(files("dependencies/modernsplash-1.7.10-1.2.2-dev.jar")))
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
@@ -136,9 +128,6 @@ 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'))
@@ -149,8 +138,6 @@ 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')
}
}
-192
View File
@@ -1,192 +0,0 @@
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
`java-library`
`maven-publish`
}
apply(plugin = "xyz.wagyourtail.jvmdowngrader")
version = rootProject.version
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
withSourcesJar()
}
// RFG variant attributes: the root Angelica project uses RetroFuturaGradle which expects
// these attributes on dependencies. Without them, Gradle can't resolve the subproject variant.
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
configurations {
listOf("apiElements", "runtimeElements").forEach { name ->
named(name) {
attributes {
attribute(rfgObfAttr, "mcp")
attribute(rfgTransformedAttr, true)
}
}
}
}
// Compile stubs: MC class signatures needed to resolve GTNHLib's type hierarchy.
// Not included in the published jar — real MC classes are on the classpath at runtime.
sourceSets {
create("stubs") {
java.srcDir("src/stubs/java")
}
}
// Exclude stubs from all output jars
tasks.withType<Jar>().configureEach {
exclude("net/minecraft/**")
}
repositories {
maven {
name = "Mojang"
url = uri("https://libraries.minecraft.net")
}
maven {
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
}
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
}
maven {
name = "taumc"
url = uri("https://maven.taumc.org/releases")
}
maven {
name = "WagYourTail"
url = uri("https://maven.wagyourtail.xyz/releases")
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
}
mavenCentral()
}
dependencies {
// MC compile stubs
compileOnly(sourceSets["stubs"].output)
// LWJGL
compileOnly("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
compileOnly("org.lwjgl.lwjgl:lwjgl_util:${property("lwjglVersion")}")
compileOnly("org.ow2.asm:asm-tree:9.7")
// Our Deps
api("com.github.GTNewHorizons:GTNHLib:${property("gtnhlibVersion")}:dev")
api("net.minecraftforge:eventbus:${property("eventbusVersion")}")
compileOnly("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
implementation("org.taumc:glsl-transformation-lib:${property("glslTransformLibVersion")}") { exclude(module = "antlr4") }
implementation("org.antlr:antlr4-runtime:${property("antlr4RuntimeVersion")}")
implementation("org.anarres:jcpp:${property("jcppVersion")}")
compileOnly("org.projectlombok:lombok:${property("lombokVersion")}") { isTransitive = false }
annotationProcessor("org.projectlombok:lombok:${property("lombokVersion")}")
compileOnly("org.jetbrains:annotations:${property("jetbrainsAnnotationsVersion")}")
compileOnly("org.apache.logging.log4j:log4j-api:${property("log4jVersion")}")
// Deps (normally from MC) - compile only, but needed at test runtime
compileOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}")
// Test
testImplementation(sourceSets["stubs"].output)
testImplementation(platform("org.junit:junit-bom:${property("junitBomVersion")}"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.apache.logging.log4j:log4j-core:${property("log4jVersion")}")
testRuntimeOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:${property("celeritasVersion")}") { isTransitive = false }
testRuntimeOnly("xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:${property("jvmDowngraderVersion")}:downgraded-8")
testRuntimeOnly("org.apache.commons:commons-lang3:${property("commonsLang3Version")}")
}
val depsToDowngrade by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
depsToDowngrade("net.minecraftforge:eventbus:${property("eventbusVersion")}")
depsToDowngrade("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
}
tasks.withType<DowngradeFiles>().configureEach {
downgradeTo.set(JavaVersion.VERSION_1_8)
multiReleaseOriginal.set(false)
multiReleaseVersions.set(emptySet())
}
val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = depsToDowngrade
}
val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["main"].output.classesDirs.plus(sourceSets["stubs"].output.classesDirs)
classpath = sourceSets["main"].compileClasspath
dependsOn(tasks.named("classes"), tasks.named("stubsClasses"))
}
val downgradeTestClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["test"].output.classesDirs
classpath = sourceSets["test"].compileClasspath
dependsOn(tasks.named("testClasses"))
}
tasks.test {
useJUnitPlatform()
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
}
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeDepsForTest)
val mainClassesDirs = sourceSets["main"].output.classesDirs
.plus(sourceSets["stubs"].output.classesDirs)
val testClassesDirs = sourceSets["test"].output.classesDirs
val downgradedTest = files(downgradeTestClasses.map { it.outputCollection })
val downgradedMain = files(downgradeMainClasses.map { it.outputCollection })
val downgradedDeps = files(downgradeDepsForTest.map { it.outputCollection })
setTestClassesDirs(downgradedTest)
classpath = downgradedTest
.plus(downgradedMain)
.plus(downgradedDeps)
.plus(classpath.minus(mainClassesDirs).minus(testClassesDirs).minus(depsToDowngrade))
val extractNatives = rootProject.tasks.named("extractNatives2")
dependsOn(extractNatives)
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
groupId = "com.gtnewhorizons.angelica"
artifactId = "glsm"
version = rootProject.version.toString()
from(components["java"])
}
}
repositories {
if (System.getenv("MAVEN_USER") != null) {
maven {
name = "GTNHMaven"
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
credentials {
username = System.getenv("MAVEN_USER")
password = System.getenv("MAVEN_PASSWORD")
}
}
}
}
}
-14
View File
@@ -1,14 +0,0 @@
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,414 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
/**
* Per-program compat uniform location cache and upload manager.
*
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader
* programs.
*/
public class CompatUniformManager {
// Uniform location indices within the cached array (package-private for tests)
static final int LOC_MODELVIEW = 0;
static final int LOC_MODELVIEW_INVERSE = 1;
static final int LOC_PROJECTION = 2;
static final int LOC_PROJECTION_INVERSE = 3;
static final int LOC_NORMAL = 4;
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
static final int LOC_FOG_DENSITY = 6;
static final int LOC_FOG_START = 7;
static final int LOC_FOG_END = 8;
static final int LOC_FOG_COLOR = 9;
static final int LOC_IRIS_MODELVIEW = 10;
static final int LOC_IRIS_PROJECTION = 11;
static final int LOC_IRIS_NORMAL = 12;
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
static final int LOC_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;
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
static final int LOC_LIGHT_BASE = 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; // 43
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 48
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
};
private static final String[] MAT_FIELD_NAMES = {
"emission", "ambient", "diffuse", "specular", "shininess",
};
private static final String[] UNIFORM_NAMES;
static {
UNIFORM_NAMES = new String[LOC_COUNT];
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_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];
}
}
for (int fi = 0; fi < MAT_FIELDS; fi++) {
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
}
}
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
// Reusable NIO buffers for upload
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
private static final Matrix3f normalMatrix = new Matrix3f();
private static final Matrix4f scratchMatrix = new Matrix4f();
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
private static final FloatBuffer lightmapMatrixBuf;
static {
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
}
// Dirty tracking: skip uploads when state hasn't changed and program is the same
private static int lastProgram = -1;
private static int lastMvGen = -1;
private static int lastProjGen = -1;
private static int lastTexMatGen = -1;
private static int lastFragmentGen = -1;
private static int lastLightingGen = -1;
private static int lastClipPlaneGen = -1;
private CompatUniformManager() {}
public static void onLinkProgram(int program) {
int[] locs = new int[LOC_COUNT];
boolean hasAny = false;
for (int i = 0; i < LOC_COUNT; i++) {
locs[i] = RENDER_BACKEND.getUniformLocation(program, UNIFORM_NAMES[i]);
if (locs[i] != -1) hasAny = true;
}
if (hasAny) {
programLocations.put(program, locs);
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
public static void onUseProgram(int program) {
if (program == 0) return;
int[] locs = programLocations.get(program);
if (locs == null) return;
final boolean programChanged = program != lastProgram;
lastProgram = program;
// Matrix uniforms — skip if generation unchanged and same program
final int mvGen = GLStateManager.mvGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
uploadMaterial(locs);
}
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
if (mvChanged) {
// ModelView
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
mv.get(mat4Buf);
if (locs[LOC_MODELVIEW] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
}
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
mv.normal(normalMatrix);
normalMatrix.get(mat3Buf);
if (locs[LOC_NORMAL] != -1) {
RENDER_BACKEND.uniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
}
if (locs[LOC_IRIS_NORMAL] != -1) {
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
}
if (projChanged) {
// Projection
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
proj.get(mat4Buf);
if (locs[LOC_PROJECTION] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
}
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
}
if (texMatChanged) {
// Iris lightmap texture matrix (constant)
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
}
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
}
// 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);
}
}
}
private static void uploadFragmentUniforms(int[] locs) {
// Fog uniforms
FogStateStack fog = GLStateManager.getFogState();
if (locs[LOC_FOG_DENSITY] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
}
if (locs[LOC_FOG_START] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_START], fog.getStart());
}
if (locs[LOC_FOG_END] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_END], fog.getEnd());
}
if (locs[LOC_FOG_COLOR] != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y).put((float) fog.getFogColor().z).put(fog.getFogAlpha());
vec4Buf.flip();
RENDER_BACKEND.uniform4(locs[LOC_FOG_COLOR], vec4Buf);
}
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
if (locs[LOC_ALPHA_TEST_REF] != -1) {
final float ref;
if (!GLStateManager.getAlphaTest().isEnabled() || GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
ref = -1.0f;
} else {
ref = GLStateManager.getAlphaState().getReference();
}
RENDER_BACKEND.uniform1f(locs[LOC_ALPHA_TEST_REF], ref);
}
}
/**
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a Same computation as {@code Uniforms.uploadLightingPreComputed()}.
*/
private static void uploadSceneColor(int[] locs) {
final MaterialState mat = GLStateManager.getFrontMaterial();
final LightModelState lm = GLStateManager.getLightModel();
vec4Buf.clear();
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
.put(mat.diffuse.w);
vec4Buf.flip();
RENDER_BACKEND.uniform4(locs[LOC_SCENE_COLOR], vec4Buf);
}
private static void uploadLightSources(int[] locs) {
// Early exit: if the first light field isn't present, no lighting uniforms exist
if (locs[LOC_LIGHT_BASE] == -1) return;
final LightStateStack[] lights = GLStateManager.getLightDataStates();
for (int li = 0; li < 2; li++) {
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
final LightState light = lights[li];
if (locs[base + LF_AMBIENT] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
if (locs[base + LF_DIFFUSE] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
if (locs[base + LF_SPECULAR] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
if (locs[base + LF_POSITION] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
if (locs[base + LF_HALF_VECTOR] != -1) {
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
float hx, hy, hz;
if (light.position.w == 0.0f) {
final float len = (float) Math.sqrt(light.position.x * light.position.x
+ light.position.y * light.position.y
+ light.position.z * light.position.z);
if (len > 0) {
hx = light.position.x / len;
hy = light.position.y / len;
hz = light.position.z / len + 1.0f;
} else {
hx = 0; hy = 0; hz = 1;
}
} else {
hx = 0; hy = 0; hz = 1;
}
final float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
RENDER_BACKEND.uniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
}
if (locs[base + LF_SPOT_DIRECTION] != -1) RENDER_BACKEND.uniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
if (locs[base + LF_SPOT_EXPONENT] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
if (locs[base + LF_SPOT_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
if (locs[base + LF_SPOT_COS_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
if (locs[base + LF_CONSTANT_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
if (locs[base + LF_LINEAR_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
if (locs[base + LF_QUADRATIC_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
}
}
private static void uploadMaterial(int[] locs) {
// Early exit: if the first material field isn't present, no material uniforms exist
if (locs[LOC_MAT_BASE] == -1) return;
final MaterialState mat = GLStateManager.getFrontMaterial();
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
}
private static void uploadClipPlanes(int[] locs) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
}
public static void onDeleteProgram(int program) {
programLocations.remove(program);
}
public static boolean hasProgram(int program) {
return programLocations.containsKey(program);
}
public static int[] getLocations(int program) {
return programLocations.get(program);
}
}
@@ -1,360 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
/**
* Software emulation of GL feedback mode
*/
public class FeedbackManager {
private static final int FB_3D = 0x01;
private static final int FB_4D = 0x02;
private static final int FB_COLOR = 0x04;
private static final int FB_TEXTURE = 0x08;
private static int renderMode = GL11.GL_RENDER;
private static FloatBuffer feedbackBuffer;
private static int feedbackBufferSize;
private static int feedbackMask;
private static int feedbackCount;
private static final Matrix4f mvpMatrix = new Matrix4f();
private static final Vector4f clipVec = new Vector4f();
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
@FunctionalInterface
private interface IndexReader {
int read(int i);
}
public static boolean isFeedbackMode() {
return renderMode == GL11.GL_FEEDBACK;
}
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
if (renderMode == GL11.GL_FEEDBACK) {
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
return;
}
feedbackBuffer = buffer;
feedbackBufferSize = buffer.remaining();
feedbackMask = computeMask(type);
feedbackCount = 0;
}
public static int glRenderMode(int mode) {
int result = switch (renderMode) {
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
default -> 0;
};
if (mode == GL11.GL_SELECT) {
GLStateManager.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
}
feedbackCount = 0;
renderMode = mode;
return result;
}
public static void glPassThrough(float token) {
if (renderMode == GL11.GL_FEEDBACK) {
feedbackToken(GL11.GL_PASS_THROUGH_TOKEN);
feedbackToken(token);
}
}
private static void feedbackToken(float token) {
if (feedbackCount < feedbackBufferSize) {
feedbackBuffer.put(feedbackBuffer.position() + feedbackCount, token);
}
feedbackCount++;
}
private static void feedbackVertex(float winX, float winY, float winZ, float winW) {
feedbackToken(winX);
feedbackToken(winY);
if ((feedbackMask & FB_3D) != 0) feedbackToken(winZ);
if ((feedbackMask & FB_4D) != 0) feedbackToken(winW);
if ((feedbackMask & FB_COLOR) != 0) {
feedbackToken(1f);
feedbackToken(1f);
feedbackToken(1f);
feedbackToken(1f);
}
if ((feedbackMask & FB_TEXTURE) != 0) {
feedbackToken(0f);
feedbackToken(0f);
feedbackToken(0f);
feedbackToken(1f);
}
}
private static void transformAndEmitVertex(float x, float y, float z) {
clipVec.set(x, y, z, 1.0f);
mvpMatrix.transform(clipVec);
final float w = clipVec.w;
final float ndcX = clipVec.x / w;
final float ndcY = clipVec.y / w;
final float ndcZ = clipVec.z / w;
final ViewportState vp = GLStateManager.getViewportState();
final float winX = vp.x + vp.width * (ndcX + 1.0f) * 0.5f;
final float winY = vp.y + vp.height * (ndcY + 1.0f) * 0.5f;
final float winZ = (float) ((vp.depthRangeFar - vp.depthRangeNear) * ndcZ + (vp.depthRangeFar + vp.depthRangeNear)) * 0.5f;
final float winW = 1.0f / w;
feedbackVertex(winX, winY, winZ, winW);
}
private static int positionStride() {
final int s = VertexAttribState.get(0).effectiveStride();
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VertexAttribState.get(0).offset;
}
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final int stride = positionStride();
final long posOffset = positionOffset();
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
emitAllPrimitives(mode, count, i -> i, stride);
}
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final int stride = positionStride();
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
final ByteBuffer indexBuf = indexReadbackBuf;
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < indexCount; i++) {
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
}
public static void processDrawElements(int mode, ByteBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFF);
}
public static void processDrawElements(int mode, ShortBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFFFF);
}
public static void processDrawElements(int mode, IntBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i));
}
public static void processDrawElements(int mode, int count, int type, ByteBuffer indices) {
final int pos = indices.position();
final IndexReader reader = switch (type) {
case GL11.GL_UNSIGNED_BYTE -> i -> indices.get(pos + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> i -> indices.getShort(pos + i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> i -> indices.getInt(pos + i * 4);
default -> i -> 0;
};
processDrawElementsImpl(mode, count, reader);
}
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final int stride = positionStride();
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
final int idx = reader.read(i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
readVerticesFromVBO(minIdx, maxIdx, stride);
final int base = minIdx;
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
}
private static void computeMVP() {
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
}
private static int computeMask(int type) {
return switch (type) {
case GL11.GL_2D -> 0;
case GL11.GL_3D -> FB_3D;
case GL11.GL_3D_COLOR -> FB_3D | FB_COLOR;
case GL11.GL_3D_COLOR_TEXTURE -> FB_3D | FB_COLOR | FB_TEXTURE;
case GL11.GL_4D_COLOR_TEXTURE -> FB_3D | FB_4D | FB_COLOR | FB_TEXTURE;
default -> 0;
};
}
private static void ensureReadbackCapacity(int bytes) {
if (readbackBuf.capacity() < bytes) {
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
}
private static void ensureIndexReadbackCapacity(int bytes) {
if (indexReadbackBuf.capacity() < bytes) {
indexReadbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
}
private static void emitVertex(int vertexIndex, int stride) {
final float x = readbackBuf.getFloat(vertexIndex * stride);
final float y = readbackBuf.getFloat(vertexIndex * stride + 4);
final float z = readbackBuf.getFloat(vertexIndex * stride + 8);
transformAndEmitVertex(x, y, z);
}
private static void emitAllPrimitives(int mode, int count, IndexReader reader, int stride) {
switch (mode) {
case GL11.GL_POINTS:
for (int i = 0; i < count; i++) {
feedbackToken(GL11.GL_POINT_TOKEN);
emitVertex(reader.read(i), stride);
}
break;
case GL11.GL_LINES:
for (int i = 0; i + 1 < count; i += 2) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_LINE_STRIP:
for (int i = 0; i + 1 < count; i++) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_LINE_LOOP:
for (int i = 0; i + 1 < count; i++) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
if (count > 2) {
feedbackToken(GL11.GL_LINE_TOKEN);
emitVertex(reader.read(count - 1), stride);
emitVertex(reader.read(0), stride);
}
break;
case GL11.GL_TRIANGLES:
for (int i = 0; i + 2 < count; i += 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_TRIANGLE_STRIP:
for (int i = 0; i + 2 < count; i++) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
if ((i & 1) == 0) {
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
} else {
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i), stride);
}
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_TRIANGLE_FAN:
for (int i = 1; i + 1 < count; i++) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
emitVertex(reader.read(0), stride);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_QUADS:
for (int i = 0; i + 3 < count; i += 4) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(4);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 2), stride);
emitVertex(reader.read(i + 3), stride);
}
break;
default:
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
}
}
static void reset() {
renderMode = GL11.GL_RENDER;
feedbackBuffer = null;
feedbackBufferSize = 0;
feedbackMask = 0;
feedbackCount = 0;
}
}
@@ -1,531 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3i;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBShaderImageLoadStore;
import org.lwjgl.opengl.ARBShaderStorageBufferObject;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.NVXGpuMemoryInfo;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* This class is responsible for abstracting calls to OpenGL and asserting that calls are run on the render thread.
*/
public class RenderSystem {
private static DSAAccess dsaState;
private static boolean supportsCompute;
private static boolean supportsImageLoadStore;
private static boolean supportsSSBO;
private static boolean supportsBufferStorage;
private static boolean supportsClearTexture;
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
private static final Pattern SEMVER_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)\\.*(?<bugfix>\\d*)(.*)");
private RenderSystem() {}
private static boolean rendererInitialized;
public static void initRenderer() {
if (rendererInitialized) return;
rendererInitialized = true;
try {
if (GLStateManager.capabilities.OpenGL45) {
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
GLStateManager.LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
try {
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
dsaState = new DSAARB();
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
if (dsaState == null) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
}
BackendManager.init();
supportsCompute = supportsCompute();
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
supportsImageLoadStore = GLStateManager.capabilities.OpenGL42
|| GLStateManager.capabilities.GL_ARB_shader_image_load_store
|| GLStateManager.capabilities.GL_EXT_shader_image_load_store;
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
supportsBufferStorage = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage;
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
// Cache maximum image units
if (supportsImageLoadStore) {
if (GLStateManager.capabilities.OpenGL42) {
maxImageUnits = RENDER_BACKEND.getInteger(GL42.GL_MAX_IMAGE_UNITS);
} else if (GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
maxImageUnits = RENDER_BACKEND.getInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS);
} else {
maxImageUnits = RENDER_BACKEND.getInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
}
} else {
maxImageUnits = 0;
}
// Cache maximum SSBO bindings
if (supportsSSBO) {
if (GLStateManager.capabilities.OpenGL43) {
maxSSBOBindings = RENDER_BACKEND.getInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
} else {
maxSSBOBindings = RENDER_BACKEND.getInteger(ARBShaderStorageBufferObject.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
}
} else {
maxSSBOBindings = 0;
}
// Check for sampler objects support (GL 3.3+ or ARB extension)
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
if (supportsSamplerObjects) {
samplers = new int[RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
}
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
GLStateManager.LOGGER.info("Max GLSL version: {}, GPU Shader4: {}", maxGlslVersion, supportsGpuShader4);
GLStateManager.LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
GLStateManager.LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
}
}
}
public static void generateMipmaps(int texture, int mipmapTarget) {
dsaState.generateMipmaps(texture, mipmapTarget);
}
public static void bindAttributeLocation(int program, int index, CharSequence name) {
RENDER_BACKEND.bindAttribLocation(program, index, name);
}
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
GLStateManager.glBindTexture(target, texture);
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
if (target == GL11.GL_TEXTURE_2D && level == 0) {
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
}
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
}
public static void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
GLStateManager.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
}
public static void uniform1f(int location, float v0) {
RENDER_BACKEND.uniform1f(location, v0);
}
public static void uniform1i(int location, int v0) {
RENDER_BACKEND.uniform1i(location, v0);
}
public static void uniform2f(int location, float v0, float v1) {
RENDER_BACKEND.uniform2f(location, v0, v1);
}
public static void uniform2i(int location, int v0, int v1) {
RENDER_BACKEND.uniform2i(location, v0, v1);
}
public static void uniform3f(int location, float v0, float v1, float v2) {
RENDER_BACKEND.uniform3f(location, v0, v1, v2);
}
public static void uniform3i(int location, int v0, int v1, int v2) {RENDER_BACKEND.uniform3i(location, v0, v1, v2);}
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
RENDER_BACKEND.uniform4f(location, v0, v1, v2, v3);
}
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
RENDER_BACKEND.uniform4i(location, v0, v1, v2, v3);
}
public static int getAttribLocation(int programId, String name) {
return RENDER_BACKEND.getAttribLocation(programId, name);
}
public static int getUniformLocation(int programId, String name) {
return RENDER_BACKEND.getUniformLocation(programId, name);
}
public static void texParameteriv(int texture, int target, int pname, IntBuffer params) {
dsaState.texParameteriv(texture, target, pname, params);
}
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
}
public static void texParameteri(int texture, int target, int pname, int param) {
dsaState.texParameteri(texture, target, pname, param);
}
public static void texParameterf(int texture, int target, int pname, float param) {
dsaState.texParameterf(texture, target, pname, param);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
}
public static String getProgramInfoLog(int program) {
return RENDER_BACKEND.getProgramInfoLog(program, RENDER_BACKEND.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
public static String getShaderInfoLog(int shader) {
return RENDER_BACKEND.getShaderInfoLog(shader, RENDER_BACKEND.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
}
public static void drawBuffers(int framebuffer, IntBuffer buffers) {
dsaState.drawBuffers(framebuffer, buffers);
}
public static void readBuffer(int framebuffer, int buffer) {
dsaState.readBuffer(framebuffer, buffer);
}
public static String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType) {
return RENDER_BACKEND.getActiveUniform(program, index, maxLength, sizeType);
}
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
}
public static void bufferData(int target, FloatBuffer data, int usage) {
RENDER_BACKEND.bufferData(target, data, usage);
}
public static int bufferStorage(int target, FloatBuffer data, int usage) {
return dsaState.bufferStorage(target, data, usage);
}
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
}
public static void detachShader(int program, int shader) {
RENDER_BACKEND.detachShader(program, shader);
}
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
}
public static int getTexParameteri(int texture, int target, int pname) {
return dsaState.getTexParameteri(texture, target, pname);
}
public static int getTexLevelParameteri(int texture, int level, int pname) {
return dsaState.getTexLevelParameteri(texture, level, pname);
}
public static void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
RENDER_BACKEND.bindImageTexture(unit, texture, level, layered, layer, access, format);
}
public static int getMaxImageUnits() {
return maxImageUnits;
}
public static boolean supportsImageLoadStore() {
return supportsImageLoadStore;
}
public static boolean supportsSSBO() {
return supportsSSBO;
}
public static boolean supportsBufferStorage() {
return supportsBufferStorage;
}
public static boolean supportsClearTexture() {
return supportsClearTexture;
}
public static int getMaxSSBOBindings() {
return maxSSBOBindings;
}
public static void getProgramiv(int program, int value, IntBuffer storage) {
RENDER_BACKEND.getProgramiv(program, value, storage);
}
public static void dispatchCompute(int workX, int workY, int workZ) {
RENDER_BACKEND.dispatchCompute(workX, workY, workZ);
}
public static void dispatchCompute(Vector3i workGroups) {
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
}
public static void dispatchComputeIndirect(long offset) {
RENDER_BACKEND.dispatchComputeIndirect(offset);
}
public static void bindBuffer(int target, int buffer) {
GLStateManager.glBindBuffer(target, buffer);
}
public static void memoryBarrier(int barriers) {
if (supportsCompute) {
RENDER_BACKEND.memoryBarrier(barriers);
}
}
public static boolean supportsBufferBlending() {
return GLStateManager.capabilities.GL_ARB_draw_buffers_blend || GLStateManager.capabilities.OpenGL40;
}
public static void disableBufferBlend(int buffer) {
RENDER_BACKEND.disablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void enableBufferBlend(int buffer) {
RENDER_BACKEND.enablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
RENDER_BACKEND.blendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
}
public static void bindTextureToUnit(int unit, int texture) {
dsaState.bindTextureToUnit(unit, texture);
}
public static void bindTextureToUnit(int target, int unit, int texture) {
dsaState.bindTextureToUnit(target, unit, texture);
}
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
public static void setupProjectionMatrix(Matrix4f matrix) {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
matrix.get(0, PROJECTION_MATRIX_BUFFER);
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void restoreProjectionMatrix() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopMatrix();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2,
int height2, int bufferChoice, int filter) {
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
}
public static int createFramebuffer() {
return dsaState.createFramebuffer();
}
public static int createTexture(int target) {
return dsaState.createTexture(target);
}
// TODO: Proper notification of compute support
public static boolean supportsCompute() {
try {
return GLStateManager.capabilities.GL_ARB_compute_shader;
} catch (Exception ignored) {
return false;
}
}
public static int createBuffers() {
return RENDER_BACKEND.genBuffers();
}
public static void bindBufferBase(int target, int index, int buffer) {
RENDER_BACKEND.bindBufferBase(target, index, buffer);
}
public static void bufferStorage(int target, long size, int flags) {
RENDER_BACKEND.bufferStorage(target, size, flags);
}
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
buf.asIntBuffer().put(data);
RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, buf);
}
public static void deleteBuffers(int buffer) {
RENDER_BACKEND.deleteBuffers(buffer);
}
public static long getVRAM() {
if (GLStateManager.capabilities.GL_NVX_gpu_memory_info) {
return RENDER_BACKEND.getInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1024L;
} else {
// Default to 4GB if we can't query VRAM
return 4294967296L;
}
}
public static void clearTexImage(int texture, int target, int level, int format, int type) {
RENDER_BACKEND.clearTexImage(texture, level, format, type);
}
public static void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
dsaState.textureStorage1D(texture, target, levels, internalFormat, width);
}
public static void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
dsaState.textureStorage2D(texture, target, levels, internalFormat, width, height);
}
public static void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
dsaState.textureStorage3D(texture, target, levels, internalFormat, width, height, depth);
}
public static int getMaxGlslVersion() {
return maxGlslVersion;
}
public static boolean supportsGpuShader4() {
return supportsGpuShader4;
}
public static boolean supportsSnormFormats() {
return GLStateManager.capabilities.OpenGL31;
}
public static boolean supportsPackedFloatRenderable() {
return GLStateManager.capabilities.OpenGL30 && maxGlslVersion >= 130;
}
public static boolean supportsTesselation() {
return supportsTesselation;
}
public static boolean supportsSamplerObjects() {
return supportsSamplerObjects;
}
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return RENDER_BACKEND.genSamplers();
}
public static void destroySampler(int sampler) {
if (!supportsSamplerObjects || sampler == 0) return;
RENDER_BACKEND.deleteSamplers(sampler);
}
public static void samplerParameteri(int sampler, int pname, int param) {
if (!supportsSamplerObjects || sampler == 0) return;
RENDER_BACKEND.samplerParameteri(sampler, pname, param);
}
public static void bindSamplerToUnit(int unit, int sampler) {
if (!supportsSamplerObjects) return;
if (samplers[unit] == sampler) return;
RENDER_BACKEND.bindSampler(unit, sampler);
samplers[unit] = sampler;
}
public static void unbindAllSamplers() {
if (!supportsSamplerObjects) return;
for (int i = 0; i < samplers.length; i++) {
if (samplers[i] != 0) {
RENDER_BACKEND.bindSampler(i, 0);
samplers[i] = 0;
}
}
}
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Inlined from StandardMacros.getGlVersion(). */
static String parseGlVersionString(String info) {
final Matcher matcher = SEMVER_PATTERN.matcher(Objects.requireNonNull(info));
if (!matcher.matches()) {
throw new IllegalStateException("Could not parse GL version from \"" + info + "\"");
}
final String major = matcher.group("major");
final String minor = matcher.group("minor");
String bugfix = matcher.group("bugfix");
if (bugfix == null || bugfix.isEmpty()) bugfix = "0";
return major + minor + bugfix;
}
}
@@ -1,54 +0,0 @@
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;
}
}
@@ -1,6 +0,0 @@
package com.gtnewhorizons.angelica.glsm.backend;
@FunctionalInterface
public interface DebugMessageHandler {
void handleMessage(int source, int type, int id, int severity, String message);
}
@@ -1,322 +0,0 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
/**
* Abstract rendering backend.
*/
public abstract class RenderBackend {
public abstract void init();
public abstract void shutdown();
public abstract boolean isAvailable();
public abstract String getName();
/** Higher priority backends are preferred. */
public int getPriority() { return 0; }
public abstract int getMinGLSLVersion();
public abstract void flush();
public abstract void finish();
public void onFrameBegin() {}
public void onFrameEnd() {}
public abstract void enable(int cap);
public abstract void enablei(int cap, int index);
public abstract void disable(int cap);
public abstract void disablei(int cap, int index);
public abstract void blendFuncSeparatei(int buf, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
public abstract void blendFunc(int sfactor, int dfactor);
public abstract void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
public abstract void blendEquation(int mode);
public abstract void blendEquationSeparate(int modeRGB, int modeAlpha);
public abstract void blendColor(float red, float green, float blue, float alpha);
public abstract void depthFunc(int func);
public abstract void depthMask(boolean flag);
public abstract void colorMask(boolean red, boolean green, boolean blue, boolean alpha);
public abstract void cullFace(int mode);
public abstract void frontFace(int mode);
public abstract void polygonMode(int face, int mode);
public abstract void polygonOffset(float factor, float units);
public abstract void stencilFunc(int func, int ref, int mask);
public abstract void stencilOp(int sfail, int dpfail, int dppass);
public abstract void stencilMask(int mask);
public abstract void stencilFuncSeparate(int face, int func, int ref, int mask);
public abstract void stencilOpSeparate(int face, int sfail, int dpfail, int dppass);
public abstract void stencilMaskSeparate(int face, int mask);
public abstract void viewport(int x, int y, int width, int height);
public abstract void depthRange(double nearVal, double farVal);
public abstract void scissor(int x, int y, int width, int height);
public abstract void clearColor(float red, float green, float blue, float alpha);
public abstract void clearDepth(double depth);
public abstract void clearStencil(int s);
public abstract void clear(int mask);
public abstract void lineWidth(float width);
public abstract void pointSize(float size);
public abstract void logicOp(int opcode);
public abstract void hint(int target, int mode);
public abstract void drawArrays(int mode, int first, int count);
public abstract void drawElements(int mode, int count, int type, long indices);
public void drawElements(int mode, ByteBuffer indices) {}
public void drawElements(int mode, IntBuffer indices) {}
public void drawElements(int mode, ShortBuffer indices) {}
public void drawElements(int mode, int count, int type, ByteBuffer indices) {}
public abstract void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
public abstract void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size);
public abstract void drawElementsInstanced(int mode, int count, int type, long indices, int primcount);
public abstract void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex);
public abstract void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex);
public abstract void drawBuffer(int mode);
public abstract void dispatchCompute(int numGroupsX, int numGroupsY, int numGroupsZ);
public abstract void dispatchComputeIndirect(long offset);
public abstract int genTextures();
public abstract void genTextures(IntBuffer textures);
public abstract void deleteTextures(int texture);
public abstract void bindTexture(int target, int texture);
public abstract void activeTexture(int texture);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset);
public abstract void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
public abstract void copyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height);
public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {}
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {}
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {}
public void texSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {}
public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {}
public void texSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {}
public void copyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {}
public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {}
public void copyTexSubImage1D(int target, int level, int xoffset, int x, int y, int width) {}
public void copyTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int x, int y, int width, int height) {}
public abstract void texParameteri(int target, int pname, int param);
public abstract void texParameterf(int target, int pname, float param);
public abstract void texParameteriv(int target, int pname, IntBuffer params);
public abstract void texParameterfv(int target, int pname, FloatBuffer params);
public abstract int getTexParameteri(int target, int pname);
public abstract float getTexParameterf(int target, int pname);
public abstract int getTexLevelParameteri(int target, int level, int pname);
public abstract void generateMipmap(int target);
public abstract void pixelStorei(int pname, int param);
public void pixelStoref(int pname, float param) {}
public void sampleCoverage(float value, boolean invert) {}
public abstract int genSamplers();
public abstract void deleteSamplers(int sampler);
public abstract void bindSampler(int unit, int sampler);
public abstract void samplerParameteri(int sampler, int pname, int param);
public abstract void samplerParameterf(int sampler, int pname, float param);
public abstract int genFramebuffers();
public abstract void deleteFramebuffers(int framebuffer);
public abstract void bindFramebuffer(int target, int framebuffer);
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level);
public abstract void framebufferTexture(int target, int attachment, int texture, int level);
public abstract int checkFramebufferStatus(int target);
public abstract void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public abstract void drawBuffers(int buffer);
public abstract void drawBuffers(IntBuffer bufs);
public abstract void readBuffer(int mode);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
public abstract int getFramebufferAttachmentParameteri(int target, int attachment, int pname);
public abstract int createShader(int type);
public abstract void deleteShader(int shader);
public abstract void shaderSource(int shader, CharSequence source);
public abstract void compileShader(int shader);
public abstract int createProgram();
public abstract void deleteProgram(int program);
public abstract void attachShader(int program, int shader);
public abstract void detachShader(int program, int shader);
public abstract void linkProgram(int program);
public abstract void useProgram(int program);
public abstract String getShaderInfoLog(int shader, int maxLength);
public String getShaderInfoLog(int shader) {
return getShaderInfoLog(shader, getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
}
public abstract void getShaderInfoLog(int shader, IntBuffer length, ByteBuffer infoLog);
public abstract String getProgramInfoLog(int program, int maxLength);
public String getProgramInfoLog(int program) {
return getProgramInfoLog(program, getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
public abstract void getProgramInfoLog(int program, IntBuffer length, ByteBuffer infoLog);
public abstract int getShaderi(int shader, int pname);
public abstract int getProgrami(int program, int pname);
public abstract void getProgramiv(int program, int pname, IntBuffer params);
public abstract String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType);
public abstract void getActiveUniform(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name);
public abstract void bindAttribLocation(int program, int index, CharSequence name);
public abstract int getAttribLocation(int program, CharSequence name);
public abstract int getAttribLocation(int program, ByteBuffer name);
public abstract int getUniformLocation(int program, CharSequence name);
public abstract int getUniformLocation(int program, ByteBuffer name);
public boolean isShader(int obj) { return false; }
public boolean isProgram(int obj) { return false; }
public void validateProgram(int program) {}
public void getAttachedShaders(int program, IntBuffer count, IntBuffer shaders) {
if (count != null) count.put(0, 0);
}
public String getShaderSource(int shader, int maxLength) { return ""; }
public abstract void getShaderSource(int shader, IntBuffer length, ByteBuffer source);
public abstract void uniform1i(int location, int v0);
public abstract void uniform1f(int location, float v0);
public void uniform1fv(int location, FloatBuffer values) {}
public abstract void uniform2f(int location, float v0, float v1);
public abstract void uniform2i(int location, int v0, int v1);
public abstract void uniform3f(int location, float v0, float v1, float v2);
public abstract void uniform3i(int location, int v0, int v1, int v2);
public abstract void uniform4f(int location, float v0, float v1, float v2, float v3);
public abstract void uniform4i(int location, int v0, int v1, int v2, int v3);
public abstract void uniform3(int location, FloatBuffer value);
public abstract void uniform4(int location, FloatBuffer value);
public abstract void uniformMatrix3(int location, boolean transpose, FloatBuffer value);
public abstract void uniformMatrix4(int location, boolean transpose, FloatBuffer value);
public void uniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {}
public void getUniformfv(int program, int location, FloatBuffer params) {}
public void getUniformiv(int program, int location, IntBuffer params) {}
public abstract void vertexAttrib2f(int index, float v0, float v1);
public abstract void vertexAttrib3f(int index, float v0, float v1, float v2);
public abstract void vertexAttrib4f(int index, float v0, float v1, float v2, float v3);
public abstract int genBuffers();
public abstract void deleteBuffers(int buffer);
public abstract void deleteBuffers(IntBuffer buffers);
public abstract void bindBuffer(int target, int buffer);
public abstract void bindBufferBase(int target, int index, int buffer);
public abstract void bufferData(int target, long size, int usage);
public abstract void bufferData(int target, ByteBuffer data, int usage);
public abstract void bufferData(int target, FloatBuffer data, int usage);
public abstract void bufferData(int target, ShortBuffer data, int usage);
public abstract void bufferData(int target, 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) {}
}
@@ -1,142 +0,0 @@
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;
}
}
}
}
@@ -1,134 +0,0 @@
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 -> {
}
}
}
}
@@ -1,243 +0,0 @@
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 -> {
}
}
}
}
@@ -1,183 +0,0 @@
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 +0,0 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public class DSAEXT extends DSACore {
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.textureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
RENDER_BACKEND.textureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
}
@@ -1,364 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import java.util.Arrays;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
/**
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
* Per-unit packing (47 bits):
* 0: enabled
* 1-3: mode (MODULATE=0..COMBINE=5)
* 4: needsEnvColor
* 5-8: combineRgb (encoded 0-7)
* 9-12: combineAlpha (encoded 0-7)
* 13-14: scaleShiftRgb
* 15-16: scaleShiftAlpha
* 17-21: arg0 RGB (source:3 + operand:2)
* 22-26: arg1 RGB
* 27-31: arg2 RGB
* 32-36: arg0 Alpha
* 37-41: arg1 Alpha
* 42-46: arg2 Alpha
*/
public final class FragmentKey {
public static final int TEX_ENV_MODULATE = 0;
public static final int TEX_ENV_REPLACE = 1;
public static final int TEX_ENV_ADD = 2;
public static final int TEX_ENV_DECAL = 3;
public static final int TEX_ENV_BLEND = 4;
public static final int TEX_ENV_COMBINE = 5;
public static final int COMBINE_REPLACE = 0;
public static final int COMBINE_MODULATE = 1;
public static final int COMBINE_ADD = 2;
public static final int COMBINE_ADD_SIGNED = 3;
public static final int COMBINE_INTERPOLATE = 4;
public static final int COMBINE_SUBTRACT = 5;
public static final int COMBINE_DOT3_RGB = 6;
public static final int COMBINE_DOT3_RGBA = 7;
public static final int SRC_TEXTURE = 0;
public static final int SRC_CONSTANT = 1;
public static final int SRC_PRIMARY_COLOR = 2;
public static final int SRC_PREVIOUS = 3;
public static final int OP_SRC_COLOR = 0;
public static final int OP_ONE_MINUS_SRC_COLOR = 1;
public static final int OP_SRC_ALPHA = 2;
public static final int OP_ONE_MINUS_SRC_ALPHA = 3;
public static final int FOG_NONE = 0;
public static final int FOG_LINEAR = 1;
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 10;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
private static final int U_NEEDS_ENV_COLOR = 4; // 1 bit
private static final int U_COMBINE_RGB = 5; // 4 bits
private static final int U_COMBINE_ALPHA = 9; // 4 bits
private static final int U_SCALE_RGB = 13; // 2 bits
private static final int U_SCALE_ALPHA = 15; // 2 bits
private static final int U_ARG0_RGB = 17; // 5 bits (source:3 + operand:2)
private static final int U_ARG1_RGB = 22; // 5 bits
private static final int U_ARG2_RGB = 27; // 5 bits
private static final int U_ARG0_ALPHA = 32; // 5 bits
private static final int U_ARG1_ALPHA = 37; // 5 bits
private static final int U_ARG2_ALPHA = 42; // 5 bits
static final int MAX_UNITS = 4;
private final long[] packed;
private final int hash;
private FragmentKey(long[] packed, int hash) {
this.packed = packed;
this.hash = hash;
}
/**
* Pack current GLSM fragment state into pre-allocated output array.
* @param out must be at least MAX_UNITS (4) longs
* @return number of significant longs (1..4)
*/
public static int packFromState(long[] out) {
long global = 0;
// Fog
if (GLStateManager.getFogMode().isEnabled()) {
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
case GL11.GL_LINEAR -> FOG_LINEAR;
case GL11.GL_EXP -> FOG_EXP;
case GL11.GL_EXP2 -> FOG_EXP2;
default -> FOG_NONE;
};
global |= (long) fogMode;
}
// Alpha test
if (GLStateManager.getAlphaTest().isEnabled()) {
global |= (1L << BIT_ALPHA_TEST);
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
}
// Separate specular
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
global |= (1L << BIT_SEPARATE_SPECULAR);
}
// Per-unit state
int highestEnabled = -1;
long unit0Bits = 0;
for (int i = 0; i < MAX_UNITS; i++) {
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled();
if (texEnabled) highestEnabled = i;
final long unitBits = packUnit(i, texEnabled);
if (i == 0) {
unit0Bits = unitBits;
} else {
out[i] = unitBits;
}
}
final int nrEnabled = highestEnabled + 1;
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
global |= (unit0Bits << GLOBAL_BITS);
out[0] = global;
return Math.max(1, nrEnabled);
}
private static long packUnit(int unitIndex, boolean texEnabled) {
if (!texEnabled) return 0;
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
final int mode = encodeTexEnvMode(envState.mode);
long bits = 1L; // enabled
bits |= ((long) (mode & 0x7)) << U_MODE;
boolean needsEnvColor = (mode == TEX_ENV_BLEND);
if (mode == TEX_ENV_COMBINE) {
final int combRgb = encodeCombineFunc(envState.combineRgb);
final int combAlpha = encodeCombineFunc(envState.combineAlpha);
bits |= ((long) (combRgb & 0xF)) << U_COMBINE_RGB;
bits |= ((long) (combAlpha & 0xF)) << U_COMBINE_ALPHA;
bits |= ((long) (encodeScale(envState.scaleRgb) & 0x3)) << U_SCALE_RGB;
bits |= ((long) (encodeScale(envState.scaleAlpha) & 0x3)) << U_SCALE_ALPHA;
// Only pack args actually used by each channel's combine function.
// Unused arg slots stay 0, preventing TexEnvState defaults from inflating the cache.
final int numArgsRgb = combineNumArgs(combRgb);
final int numArgsAlpha = combineNumArgs(combAlpha);
final int maxArgs = Math.max(numArgsRgb, numArgsAlpha);
for (int a = 0; a < maxArgs; a++) {
if (a < numArgsRgb) {
final int srcR = encodeSource(envState.sourceRgb[a]);
final int opR = encodeOperand(envState.operandRgb[a]);
bits |= ((long) ((srcR & 0x7) | ((opR & 0x3) << 3))) << (U_ARG0_RGB + a * 5);
if (srcR == SRC_CONSTANT) needsEnvColor = true;
}
if (a < numArgsAlpha) {
final int srcA = encodeSource(envState.sourceAlpha[a]);
final int opA = encodeOperand(envState.operandAlpha[a]);
bits |= ((long) ((srcA & 0x7) | ((opA & 0x3) << 3))) << (U_ARG0_ALPHA + a * 5);
if (srcA == SRC_CONSTANT) needsEnvColor = true;
}
}
}
if (needsEnvColor) {
bits |= (1L << U_NEEDS_ENV_COLOR);
}
return bits;
}
/** Create FragmentKey from packed scratch data. Only called on cache miss. */
public static FragmentKey fromPacked(long[] scratch, int len) {
final long[] copy = Arrays.copyOf(scratch, len);
return new FragmentKey(copy, Arrays.hashCode(copy));
}
/** Convenience: pack + create. Allocates. For infrequent paths. */
public static FragmentKey fromState() {
final long[] scratch = new long[MAX_UNITS];
final int len = packFromState(scratch);
return fromPacked(scratch, len);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof FragmentKey other)) return false;
if (hash != other.hash) return false;
return Arrays.equals(packed, other.packed);
}
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
}
public boolean unitEnabled(int i) { return (unitBits(i) & 1) != 0; }
public int unitMode(int i) { return (int) ((unitBits(i) >> U_MODE) & 0x7); }
public boolean unitNeedsEnvColor(int i) { return ((unitBits(i) >> U_NEEDS_ENV_COLOR) & 1) != 0; }
public int unitCombineRgb(int i) { return (int) ((unitBits(i) >> U_COMBINE_RGB) & 0xF); }
public int unitCombineAlpha(int i) { return (int) ((unitBits(i) >> U_COMBINE_ALPHA) & 0xF); }
public int unitScaleShiftRgb(int i) { return (int) ((unitBits(i) >> U_SCALE_RGB) & 0x3); }
public int unitScaleShiftAlpha(int i) { return (int) ((unitBits(i) >> U_SCALE_ALPHA) & 0x3); }
public int unitSourceRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5)) & 0x7); }
public int unitOperandRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5 + 3)) & 0x3); }
public int unitSourceAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5)) & 0x7); }
public int unitOperandAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5 + 3)) & 0x3); }
public boolean textureEnabled() {
for (int i = 0; i < nrEnabledUnits(); i++) {
if (unitEnabled(i)) return true;
}
return false;
}
public boolean lightmapEnabled() {
return nrEnabledUnits() > 1 && unitEnabled(1);
}
private static int encodeAlphaFunc(int glFunc) {
return glFunc & 0x7;
}
static int encodeTexEnvMode(int glMode) {
return switch (glMode) {
case GL11.GL_MODULATE -> TEX_ENV_MODULATE;
case GL11.GL_REPLACE -> TEX_ENV_REPLACE;
case GL11.GL_ADD -> TEX_ENV_ADD;
case GL11.GL_DECAL -> TEX_ENV_DECAL;
case GL11.GL_BLEND -> TEX_ENV_BLEND;
case GL13.GL_COMBINE -> TEX_ENV_COMBINE;
default -> TEX_ENV_MODULATE;
};
}
static int encodeCombineFunc(int glFunc) {
return switch (glFunc) {
case GL11.GL_REPLACE -> COMBINE_REPLACE;
case GL11.GL_MODULATE -> COMBINE_MODULATE;
case GL11.GL_ADD -> COMBINE_ADD;
case GL13.GL_ADD_SIGNED -> COMBINE_ADD_SIGNED;
case GL13.GL_INTERPOLATE -> COMBINE_INTERPOLATE;
case GL13.GL_SUBTRACT -> COMBINE_SUBTRACT;
case GL13.GL_DOT3_RGB -> COMBINE_DOT3_RGB;
case GL13.GL_DOT3_RGBA -> COMBINE_DOT3_RGBA;
default -> COMBINE_REPLACE;
};
}
static int encodeSource(int glSource) {
return switch (glSource) {
case GL11.GL_TEXTURE -> SRC_TEXTURE;
case GL13.GL_CONSTANT -> SRC_CONSTANT;
case GL13.GL_PRIMARY_COLOR -> SRC_PRIMARY_COLOR;
case GL13.GL_PREVIOUS -> SRC_PREVIOUS;
default -> {
GLStateManager.LOGGER.debug("Unknown TexEnv source 0x{}, falling back to SRC_PREVIOUS", Integer.toHexString(glSource));
yield SRC_PREVIOUS;
}
};
}
static int encodeOperand(int glOperand) {
return switch (glOperand) {
case GL11.GL_SRC_COLOR -> OP_SRC_COLOR;
case GL11.GL_ONE_MINUS_SRC_COLOR -> OP_ONE_MINUS_SRC_COLOR;
case GL11.GL_SRC_ALPHA -> OP_SRC_ALPHA;
case GL11.GL_ONE_MINUS_SRC_ALPHA -> OP_ONE_MINUS_SRC_ALPHA;
default -> OP_SRC_COLOR;
};
}
/** Number of source/operand args used by the given (encoded) combine function. */
public static int combineNumArgs(int func) {
return switch (func) {
case COMBINE_REPLACE -> 1;
case COMBINE_INTERPOLATE -> 3;
default -> 2; // MODULATE, ADD, ADD_SIGNED, SUBTRACT, DOT3_*
};
}
private static int encodeScale(float scale) {
if (scale >= 4.0f) return 2;
if (scale >= 2.0f) return 1;
return 0;
}
public static int decodeAlphaFunc(int encoded) {
return 0x0200 | encoded;
}
@Override
public String toString() {
final String fogName = switch (fogMode()) {
case FOG_NONE -> "NONE";
case FOG_LINEAR -> "LINEAR";
case FOG_EXP -> "EXP";
case FOG_EXP2 -> "EXP2";
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
} else {
final String modeName = switch (unitMode(i)) {
case TEX_ENV_MODULATE -> "MOD";
case TEX_ENV_REPLACE -> "REPL";
case TEX_ENV_ADD -> "ADD";
case TEX_ENV_DECAL -> "DEC";
case TEX_ENV_BLEND -> "BL";
case TEX_ENV_COMBINE -> "COMB";
default -> "?";
};
sb.append(String.format(" u%d=%s", i, modeName));
}
}
sb.append(']');
return sb.toString();
}
}
@@ -1,318 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.lwjgl.opengl.GL11;
/**
* Generates GLSL 330 core fragment shaders for FFP emulation.
* Supports per-unit texenv chain including GL_COMBINE mode.
* Follows Mesa's ff_fragment_shader.c and st_nir_lower_fog.c.
*/
public final class FragmentShaderGenerator {
private FragmentShaderGenerator() {}
public static String generate(FragmentKey key) {
final StringBuilder sb = new StringBuilder(2048);
sb.append("#version 330 core\n\n");
emitInputs(sb, key);
emitUniforms(sb, key);
sb.append("out vec4 fragColor;\n\n");
sb.append("void main() {\n");
emitTextureSampling(sb, key);
emitTexEnvChain(sb, key);
emitSpecularAdd(sb, key);
emitAlphaTest(sb, key);
emitFog(sb, key);
sb.append(" fragColor = color;\n");
sb.append("}\n");
return sb.toString();
}
private static void emitInputs(StringBuilder sb, FragmentKey key) {
sb.append("// Inputs from vertex shader\n");
sb.append("in vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor;\n");
}
if (key.textureEnabled()) {
sb.append("in vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec4 v_TexCoord1;\n");
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("in float v_FogCoord;\n");
}
sb.append('\n');
}
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
// Samplers for each enabled unit
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
}
}
if (key.alphaTestEnabled()) {
sb.append("uniform float u_AlphaRef;\n");
}
// Per-unit texEnvColor uniforms
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i) && key.unitNeedsEnvColor(i)) {
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
}
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("uniform vec4 u_FogParams;\n");
sb.append("uniform vec4 u_FogColor;\n");
}
sb.append('\n');
}
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
// Unit 1 is the lightmap — uses v_TexCoord1.
// Units 2-3 intentionally share unit 0's texture coordinates since
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
}
}
private static void emitTexEnvChain(StringBuilder sb, FragmentKey key) {
if (!key.textureEnabled()) {
sb.append(" // No texture -- vertex color only\n");
sb.append(" vec4 color = v_Color;\n");
return;
}
// Process units in order; "previous" starts as vertex color
boolean firstUnit = true;
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
final String texVar = "tex" + i + "Color";
final String envColorVar = "u_TexEnvColor" + i;
final String prevVar = firstUnit ? "v_Color" : "color";
final String assign = firstUnit ? " vec4 color = " : " color = ";
if (key.unitMode(i) == FragmentKey.TEX_ENV_COMBINE) {
emitCombineUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
} else {
emitSimpleUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
}
firstUnit = false;
}
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
switch (key.unitMode(unit)) {
case FragmentKey.TEX_ENV_REPLACE -> {
sb.append(" // Unit ").append(unit).append(" (REPLACE)\n");
sb.append(assign).append(texVar).append(";\n");
}
case FragmentKey.TEX_ENV_ADD -> {
sb.append(" // Unit ").append(unit).append(" (ADD)\n");
sb.append(assign).append("vec4(clamp(").append(prevVar).append(".rgb + ").append(texVar).append(".rgb, 0.0, 1.0), ")
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
}
case FragmentKey.TEX_ENV_DECAL -> {
sb.append(" // Unit ").append(unit).append(" (DECAL)\n");
sb.append(assign).append("vec4(mix(").append(prevVar).append(".rgb, ").append(texVar).append(".rgb, ").append(texVar).append(".a), ")
.append(prevVar).append(".a);\n");
}
case FragmentKey.TEX_ENV_BLEND -> {
sb.append(" // Unit ").append(unit).append(" (BLEND)\n");
sb.append(assign).append("vec4(").append(prevVar).append(".rgb * (vec3(1.0) - ").append(texVar).append(".rgb) + ")
.append(envColorVar).append(".rgb * ").append(texVar).append(".rgb, ")
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
}
default -> { // MODULATE
sb.append(" // Unit ").append(unit).append(" (MODULATE)\n");
sb.append(assign).append(prevVar).append(" * ").append(texVar).append(";\n");
}
}
}
/** Emit a GL_COMBINE texenv unit */
private static void emitCombineUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
sb.append(" // Unit ").append(unit).append(" (COMBINE)\n");
final String rgbResult = "combRgb" + unit;
final String alphaResult = "combAlpha" + unit;
if (key.unitCombineRgb(unit) == FragmentKey.COMBINE_DOT3_RGBA) {
sb.append(" vec3 ").append(rgbResult).append(";\n");
sb.append(" {\n");
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
sb.append(" }\n");
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(rgbResult).append(".x);\n");
} else {
sb.append(" vec3 ").append(rgbResult).append(";\n");
sb.append(" float ").append(alphaResult).append(";\n");
sb.append(" {\n");
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, false);
sb.append(" }\n");
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(alphaResult).append(");\n");
}
}
/**
* Emit one channel (RGB or Alpha) of a GL_COMBINE unit.
*/
private static void emitCombineChannel(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, boolean isRgb) {
final String suffix = isRgb ? "Rgb" : "Alpha";
final String type = isRgb ? "vec3" : "float";
final int combineFunc = isRgb ? key.unitCombineRgb(unit) : key.unitCombineAlpha(unit);
final int scaleShift = isRgb ? key.unitScaleShiftRgb(unit) : key.unitScaleShiftAlpha(unit);
// Guard: DOT3 is invalid for alpha channel — fallback to REPLACE
final int effectiveFunc;
if (!isRgb && (combineFunc == FragmentKey.COMBINE_DOT3_RGB || combineFunc == FragmentKey.COMBINE_DOT3_RGBA)) {
effectiveFunc = FragmentKey.COMBINE_REPLACE;
} else {
effectiveFunc = combineFunc;
}
// Determine how many args we need
final int numArgs = combineNumArgs(effectiveFunc);
// Emit argument resolution
for (int a = 0; a < numArgs; a++) {
final int source = isRgb ? key.unitSourceRgb(unit, a) : key.unitSourceAlpha(unit, a);
final int operand = isRgb ? key.unitOperandRgb(unit, a) : key.unitOperandAlpha(unit, a);
final String argName = "arg" + suffix + a + "_" + unit;
final String sourceExpr = resolveSource(source, texVar, envColorVar, prevVar);
final String operandExpr = applyOperand(sourceExpr, operand, isRgb);
sb.append(" ").append(type).append(" ").append(argName).append(" = ").append(operandExpr).append(";\n");
}
// Emit combine operation
final String resultName = "comb" + suffix + unit;
final String a0 = "arg" + suffix + "0_" + unit;
final String a1 = "arg" + suffix + "1_" + unit;
final String a2 = "arg" + suffix + "2_" + unit;
sb.append(" ").append(resultName).append(" = ");
switch (effectiveFunc) {
case FragmentKey.COMBINE_REPLACE -> sb.append(a0);
case FragmentKey.COMBINE_MODULATE -> sb.append(a0).append(" * ").append(a1);
case FragmentKey.COMBINE_ADD -> sb.append(a0).append(" + ").append(a1);
case FragmentKey.COMBINE_ADD_SIGNED ->
sb.append(a0).append(" + ").append(a1).append(" - ").append(isRgb ? "vec3(0.5)" : "0.5");
case FragmentKey.COMBINE_SUBTRACT -> sb.append(a0).append(" - ").append(a1);
case FragmentKey.COMBINE_INTERPOLATE ->
sb.append("mix(").append(a1).append(", ").append(a0).append(", ").append(a2).append(")");
case FragmentKey.COMBINE_DOT3_RGB, FragmentKey.COMBINE_DOT3_RGBA ->
sb.append("vec3(4.0 * dot(").append(a0).append(" - vec3(0.5), ").append(a1).append(" - vec3(0.5)))");
default -> sb.append(a0); // fallback to REPLACE
}
sb.append(";\n");
// Apply scale
if (scaleShift > 0) {
final String scaleFactor = scaleShift == 1 ? "2.0" : "4.0";
sb.append(" ").append(resultName).append(" *= ").append(scaleFactor).append(";\n");
}
// Clamp
sb.append(" ").append(resultName).append(" = clamp(").append(resultName).append(", ")
.append(isRgb ? "vec3(0.0), vec3(1.0)" : "0.0, 1.0").append(");\n");
}
private static String resolveSource(int source, String texVar, String envColorVar, String prevVar) {
return switch (source) {
case FragmentKey.SRC_TEXTURE -> texVar;
case FragmentKey.SRC_CONSTANT -> envColorVar;
case FragmentKey.SRC_PRIMARY_COLOR -> "v_Color";
case FragmentKey.SRC_PREVIOUS -> prevVar;
default -> prevVar;
};
}
private static String applyOperand(String sourceExpr, int operand, boolean isRgb) {
if (isRgb) {
return switch (operand) {
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".rgb";
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(vec3(1.0) - " + sourceExpr + ".rgb)";
case FragmentKey.OP_SRC_ALPHA -> "vec3(" + sourceExpr + ".a)";
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "vec3(1.0 - " + sourceExpr + ".a)";
default -> sourceExpr + ".rgb";
};
} else {
return switch (operand) {
case FragmentKey.OP_SRC_ALPHA -> sourceExpr + ".a";
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "(1.0 - " + sourceExpr + ".a)";
// Per GL spec, alpha channel operands are SRC_ALPHA or ONE_MINUS_SRC_ALPHA only, but handle color operands gracefully
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".a";
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(1.0 - " + sourceExpr + ".a)";
default -> sourceExpr + ".a";
};
}
}
private static int combineNumArgs(int func) {
return FragmentKey.combineNumArgs(func);
}
private static void emitSpecularAdd(StringBuilder sb, FragmentKey key) {
if (key.separateSpecular()) {
sb.append(" // Add separate specular\n");
sb.append(" color.rgb += v_SpecularColor;\n");
}
}
private static void emitAlphaTest(StringBuilder sb, FragmentKey key) {
if (!key.alphaTestEnabled()) return;
sb.append(" // Alpha test\n");
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
sb.append(" float qa = round(color.a * 255.0);\n");
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
switch (func) {
case GL11.GL_NEVER ->
sb.append(" discard;\n");
case GL11.GL_LESS ->
sb.append(" if (qa >= qr) discard;\n");
case GL11.GL_EQUAL ->
sb.append(" if (qa != qr) discard;\n");
case GL11.GL_LEQUAL ->
sb.append(" if (qa > qr) discard;\n");
case GL11.GL_GREATER ->
sb.append(" if (qa <= qr) discard;\n");
case GL11.GL_NOTEQUAL ->
sb.append(" if (qa == qr) discard;\n");
case GL11.GL_GEQUAL ->
sb.append(" if (qa < qr) discard;\n");
case GL11.GL_ALWAYS -> {}
}
}
private static void emitFog(StringBuilder sb, FragmentKey key) {
final int fogMode = key.fogMode();
if (fogMode == FragmentKey.FOG_NONE) return;
sb.append(" // Fog\n");
sb.append(" float f;\n");
switch (fogMode) {
case FragmentKey.FOG_LINEAR ->
sb.append(" f = v_FogCoord * u_FogParams.x + u_FogParams.y;\n");
case FragmentKey.FOG_EXP ->
sb.append(" f = exp2(-(v_FogCoord * u_FogParams.z));\n");
case FragmentKey.FOG_EXP2 -> {
sb.append(" float fogTmp = v_FogCoord * u_FogParams.w;\n");
sb.append(" f = exp2(-(fogTmp * fogTmp));\n");
}
}
sb.append(" f = clamp(f, 0.0, 1.0);\n");
sb.append(" color.rgb = mix(u_FogColor.rgb, color.rgb, f);\n");
}
}
@@ -1,104 +0,0 @@
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");
}
}
@@ -1,294 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Setter;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.IdentityHashMap;
/**
* Open-addressed cache for FFP shader program variants.
* Keyed on (vertexKey, fragmentKey) pairs stored inline as raw longs — zero allocation on cache hits.
* Converges on Mesa's prog_cache.c: hash+memcmp over packed bytes, open addressing.
*
* <p>Layout per slot in {@code keys[]}: {@link #SLOT_STRIDE}=6 longs
* <pre>
* [0]: vertexKeyPacked
* [1]: (fkLen &lt;&lt; 48) | (hash &amp; 0x0000_FFFF_FFFF_FFFF) — never 0 for valid entries (fkLen &gt;= 1)
* [2..5]: fk0..fk3
* </pre>
*/
public class ShaderCache {
private static final int SLOT_STRIDE = 6;
private static final int INITIAL_CAPACITY = 64; // power of 2
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
// Open-addressed table — parallel arrays
private long[] keys;
private Program[] programs;
private String[] fragSources; // fragment source per slot; reused across VK variants via findFragSource
private int capacity;
private int count;
private int uniqueFragCount;
// Fast-path: last-used key (raw longs, no objects)
private long lastVK = Long.MIN_VALUE;
private int lastFKLen;
private long lastFK0, lastFK1, lastFK2, lastFK3;
private Program lastProgram;
private int fragmentDumpCounter;
@Setter private Path dumpDir = null;
public ShaderCache() {
capacity = INITIAL_CAPACITY;
keys = new long[capacity * SLOT_STRIDE];
programs = new Program[capacity];
fragSources = new String[capacity];
}
/**
* Get or create an FFP program for the given state keys.
*
* @param vkPacked packed vertex key
* @param fkPacked pre-allocated scratch array with packed fragment key longs
* @param fkLen number of significant longs in fkPacked (1..4)
*/
public Program getOrCreate(long vkPacked, long[] fkPacked, int fkLen) {
// Fast-path: compare raw longs inline
if (vkPacked == lastVK && fkLen == lastFKLen && lastProgram != null
&& fkPacked[0] == lastFK0
&& (fkLen < 2 || fkPacked[1] == lastFK1)
&& (fkLen < 3 || fkPacked[2] == lastFK2)
&& (fkLen < 4 || fkPacked[3] == lastFK3)) {
return lastProgram;
}
final long hash = hashKey(vkPacked, fkPacked, fkLen);
final long storedWord = ((long) fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFFL);
final int mask = capacity - 1;
int idx = (int) (hash & mask);
while (true) {
final int base = idx * SLOT_STRIDE;
final long slot1 = keys[base + 1];
if (slot1 == EMPTY_MARKER) {
break;
}
if (slot1 == storedWord && keys[base] == vkPacked && keysMatch(base + 2, fkPacked, fkLen)) {
final Program p = programs[idx];
setLastUsed(vkPacked, fkPacked, fkLen, p);
return p;
}
idx = (idx + 1) & mask;
}
if (count >= (capacity * 3) / 4) {
grow();
return getOrCreate(vkPacked, fkPacked, fkLen);
}
final VertexKey vk = VertexKey.fromPacked(vkPacked);
final String vertSrc = getOrGenerateVertex(vk);
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
String fragSrc = findFragSource(fkPacked, fkLen);
if (fragSrc == null) {
fragSrc = FragmentShaderGenerator.generate(fk);
uniqueFragCount++;
}
final String geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
final Program program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
final int insertIdx = findEmptySlot(hash);
final int base = insertIdx * SLOT_STRIDE;
keys[base] = vkPacked;
keys[base + 1] = storedWord;
for (int i = 0; i < fkLen; i++) keys[base + 2 + i] = fkPacked[i];
programs[insertIdx] = program;
fragSources[insertIdx] = fragSrc;
count++;
setLastUsed(vkPacked, fkPacked, fkLen, program);
GLStateManager.LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, count);
if (dumpDir != null) {
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
if (geomSrc != null) {
dumpShader(Long.toHexString(vkPacked), ".geom.glsl", geomSrc);
}
}
return program;
}
private String findFragSource(long[] fkPacked, int fkLen) {
for (int i = 0; i < capacity; i++) {
final int base = i * SLOT_STRIDE;
if (keys[base + 1] == EMPTY_MARKER) continue;
final int storedLen = (int) (keys[base + 1] >>> 48);
if (storedLen != fkLen) continue;
if (keysMatch(base + 2, fkPacked, fkLen)) {
return fragSources[i];
}
}
return null;
}
private boolean keysMatch(int keysOffset, long[] fkPacked, int fkLen) {
for (int i = 0; i < fkLen; i++) {
if (keys[keysOffset + i] != fkPacked[i]) return false;
}
return true;
}
private void setLastUsed(long vkPacked, long[] fkPacked, int fkLen, Program program) {
lastVK = vkPacked;
lastFKLen = fkLen;
lastFK0 = fkPacked[0];
lastFK1 = fkLen >= 2 ? fkPacked[1] : 0;
lastFK2 = fkLen >= 3 ? fkPacked[2] : 0;
lastFK3 = fkLen >= 4 ? fkPacked[3] : 0;
lastProgram = program;
}
private int findEmptySlot(long hash) {
final int mask = capacity - 1;
int idx = (int) (hash & mask);
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
idx = (idx + 1) & mask;
}
return idx;
}
/**
* Hash over (vk + fk longs). Adapted from Mesa's hash_key — mix-rotate-multiply.
*/
private static long hashKey(long vkPacked, long[] fkPacked, int fkLen) {
long h = vkPacked * 0x9E3779B97F4A7C15L;
for (int i = 0; i < fkLen; i++) {
h ^= fkPacked[i] * 0x517CC1B727220A95L;
h = Long.rotateLeft(h, 31) * 0x9E3779B97F4A7C15L;
}
return h | 1L; // ensure nonzero lower bits
}
private void grow() {
final int oldCap = capacity;
final long[] oldKeys = keys;
final Program[] oldPrograms = programs;
final String[] oldFragSources = fragSources;
capacity = oldCap * 2;
keys = new long[capacity * SLOT_STRIDE];
programs = new Program[capacity];
fragSources = new String[capacity];
count = 0;
final int mask = capacity - 1;
for (int i = 0; i < oldCap; i++) {
final int oldBase = i * SLOT_STRIDE;
if (oldKeys[oldBase + 1] == EMPTY_MARKER) continue;
final long storedHash = oldKeys[oldBase + 1] & 0x0000_FFFF_FFFF_FFFFL;
int idx = (int) (storedHash & mask);
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
idx = (idx + 1) & mask;
}
final int newBase = idx * SLOT_STRIDE;
System.arraycopy(oldKeys, oldBase, keys, newBase, SLOT_STRIDE);
programs[idx] = oldPrograms[i];
fragSources[idx] = oldFragSources[i];
count++;
}
}
private String getOrGenerateVertex(VertexKey vk) {
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
}
private String getOrGenerateGeometry(VertexKey vk) {
return geometrySourceCache.computeIfAbsent(vk.pack(), k -> GeometryShaderGenerator.generate(vk));
}
private void dumpShader(String name, String suffix, String source) {
if (dumpDir == null) return;
try {
Files.createDirectories(dumpDir);
final Path file = dumpDir.resolve("ffp_" + name + suffix);
if (!Files.exists(file)) {
Files.writeString(file, source, StandardCharsets.UTF_8);
}
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump FFP shader to {}: {}", dumpDir, e.getMessage());
}
}
/** Dump all cached shaders to disk. */
public void dumpAll(Path outputDir) {
try {
Files.createDirectories(outputDir);
for (var entry : vertexSourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".vert.glsl", entry.getValue());
}
final IdentityHashMap<String, Integer> seen = new IdentityHashMap<>();
for (int i = 0; i < capacity; i++) {
if (fragSources[i] != null && !seen.containsKey(fragSources[i])) {
final int idx = seen.size();
seen.put(fragSources[i], idx);
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
}
}
for (var entry : geometrySourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".geom.glsl", entry.getValue());
}
GLStateManager.LOGGER.info("Dumped {} vertex, {} geometry, and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), geometrySourceCache.size(), seen.size(), outputDir);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
}
}
/** Destroy all cached programs and clear caches. */
public void destroy() {
for (int i = 0; i < capacity; i++) {
if (programs[i] != null) {
programs[i].destroy();
}
}
Arrays.fill(keys, 0);
Arrays.fill(programs, null);
Arrays.fill(fragSources, null);
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
GLStateManager.LOGGER.debug("FFP shader cache cleared");
}
public int getProgramCount() {
return count;
}
public int getVertexVariantCount() {
return vertexSourceCache.size();
}
public int getFragmentVariantCount() {
return uniqueFragCount;
}
}
@@ -1,7 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredAlphaHandler {
boolean isAlphaTestLocked();
void deferAlphaTestToggle(boolean enabled);
void deferAlphaFunc(int function, float reference);
}
@@ -1,8 +0,0 @@
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();
}
@@ -1,7 +0,0 @@
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);
}
@@ -1,12 +0,0 @@
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() {}
}
@@ -1,37 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.hooks.events.BlendFuncChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.FogStateChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.LightmapCoordsEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.ProgramChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureBindEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureDeleteEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureUnitStateEvent;
import net.minecraftforge.eventbus.api.bus.EventBus;
public final class GLSMHooks {
public static DeferredBlendHandler blendHandler;
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
public static final EventBus<TextureDeleteEvent> TEXTURE_DELETE = EventBus.create(TextureDeleteEvent.class);
public static final EventBus<TextureUnitStateEvent> TEXTURE_UNIT_STATE = EventBus.create(TextureUnitStateEvent.class);
public static final EventBus<ProgramChangeEvent> PROGRAM_CHANGE = EventBus.create(ProgramChangeEvent.class);
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
// Reusable event instances
public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
public static final TextureDeleteEvent textureDeleteEvent = new TextureDeleteEvent();
public static final TextureUnitStateEvent textureUnitStateEvent = new TextureUnitStateEvent();
public static final ProgramChangeEvent programChangeEvent = new ProgramChangeEvent();
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
private GLSMHooks() {}
}
@@ -1,103 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import java.util.function.Consumer;
public final class GLSMInitConfig {
private final boolean lwjglDebug;
private final StreamingUploader.UploadStrategy streamingUploadStrategy;
private final boolean framebufferSupported;
private final boolean fboEnabled;
private final Consumer<DirectTessellator> directDrawer;
private final Runnable streamingDrawerDestroy;
private final int displayWidth;
private final int displayHeight;
private final Runnable postInitCallback;
private GLSMInitConfig(Builder builder) {
this.lwjglDebug = builder.lwjglDebug;
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.framebufferSupported = builder.framebufferSupported;
this.fboEnabled = builder.fboEnabled;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
}
public static Builder builder() {
return new Builder();
}
public boolean isLwjglDebug() { return lwjglDebug; }
public StreamingUploader.UploadStrategy getStreamingUploadStrategy() { return streamingUploadStrategy; }
public boolean isFramebufferSupported() { return framebufferSupported; }
public boolean isFboEnabled() { return fboEnabled; }
public Consumer<DirectTessellator> getDirectDrawer() { return directDrawer; }
public Runnable getStreamingDrawerDestroy() { return streamingDrawerDestroy; }
public int getDisplayWidth() { return displayWidth; }
public int getDisplayHeight() { return displayHeight; }
public Runnable getPostInitCallback() { return postInitCallback; }
public static final class Builder {
private boolean lwjglDebug = false;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private boolean framebufferSupported = false;
private boolean fboEnabled = false;
private Consumer<DirectTessellator> directDrawer = null;
private Runnable streamingDrawerDestroy = null;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
private Builder() {}
public Builder lwjglDebug(boolean lwjglDebug) {
this.lwjglDebug = lwjglDebug;
return this;
}
public Builder streamingUploadStrategy(StreamingUploader.UploadStrategy strategy) {
this.streamingUploadStrategy = strategy;
return this;
}
public Builder framebufferSupported(boolean framebufferSupported) {
this.framebufferSupported = framebufferSupported;
return this;
}
public Builder fboEnabled(boolean fboEnabled) {
this.fboEnabled = fboEnabled;
return this;
}
public Builder directDrawer(Consumer<DirectTessellator> directDrawer) {
this.directDrawer = directDrawer;
return this;
}
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
this.streamingDrawerDestroy = streamingDrawerDestroy;
return this;
}
public Builder displaySize(int width, int height) {
this.displayWidth = width;
this.displayHeight = height;
return this;
}
public Builder postInitCallback(Runnable callback) {
this.postInitCallback = callback;
return this;
}
public GLSMInitConfig build() {
return new GLSMInitConfig(this);
}
}
}
@@ -1,10 +0,0 @@
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;
}
@@ -1,6 +0,0 @@
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 {}
@@ -1,8 +0,0 @@
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;
}
@@ -1,8 +0,0 @@
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;
}
@@ -1,7 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureBindEvent extends MutableEvent {
public int textureId;
}
@@ -1,7 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureDeleteEvent extends MutableEvent {
public int textureId;
}
@@ -1,8 +0,0 @@
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;
}
@@ -1,449 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
* Used both during display list compilation and for live immediate mode emulation in core profile.
*/
public final class ImmediateModeRecorder {
private static final DirectTessellator tessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
/** Separate tessellator for the splash thread. Nulled after splash completes. */
private static DirectTessellator splashTessellator;
private ImmediateModeRecorder() {
}
public static void initSplashTessellator() {
splashTessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
}
public static void destroySplashTessellator() {
splashTessellator = null;
}
private static DirectTessellator tessellator() {
final DirectTessellator splash = splashTessellator;
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
return splash;
}
return tessellator;
}
public static DirectTessellator getInternalTessellator() {
return tessellator();
}
public static boolean isDrawing() {
return tessellator().isDrawing;
}
/**
* Set the current texture coordinate.
* Called by GLStateManager.glTexCoord*.
*/
public static void setTexCoord(float s, float t) {
tessellator().setTextureUV(s, t);
}
/**
* Set the current lightmap coordinate as packed brightness.
* Called by GLStateManager.glMultiTexCoord2f for unit 1 during recording/drawing.
*/
public static void setLightmapCoord(float s, float t) {
tessellator().setBrightness(((int) t << 16) | ((int) s & 0xFFFF));
}
/**
* Set the current normal vector.
* Called by GLStateManager.glNormal*.
*/
public static void setNormal(float x, float y, float z) {
tessellator().setNormal(x, y, z);
}
/**
* Set the current color on the internal tessellator.
* Called by GLStateManager.glColor* when inside a glBegin/glEnd block.
*/
public static void setColor(float r, float g, float b, float a) {
tessellator().setColorRGBA((int) (r * 255.0f), (int) (g * 255.0f), (int) (b * 255.0f), (int) (a * 255.0f));
}
/**
* Start recording a new primitive.
* Resets the tessellator to clear any data from the previous draw.
*
* @param primitiveType GL primitive type (GL_QUADS, GL_TRIANGLES, etc.)
*/
public static void begin(int primitiveType) {
final DirectTessellator t = tessellator();
t.reset();
t.startDrawing(primitiveType);
}
/**
* Start recording a new primitive with initial color sync from GLSM cache.
* Used for live immediate mode emulation only (not display list recording).
*/
public static void beginLive(int primitiveType) {
final DirectTessellator t = tessellator();
t.reset();
t.startDrawing(primitiveType);
final var c = GLStateManager.getColor();
t.setColorRGBA((int) (c.getRed() * 255.0f), (int) (c.getGreen() * 255.0f), (int) (c.getBlue() * 255.0f), (int) (c.getAlpha() * 255.0f));
}
/**
* End the current primitive, convert to quads/lines, and return them immediately.
* This allows the caller to create AccumulatedDraw at the correct command position.
*
* @return Result containing quads, lines, and flags, or null if no geometry was produced
*/
public static DirectTessellator end() {
final DirectTessellator t = tessellator();
if (!t.isDrawing) {
throw new IllegalStateException("glEnd called without glBegin");
}
t.isDrawing = false;
if (t.isEmpty()) return null;
return t;
}
/**
* Record a vertex with current attributes.
* Color is read from GLStateManager at call time.
*/
public static void vertex(float x, float y, float z) {
final DirectTessellator t = tessellator();
if (!t.isDrawing) {
throw new IllegalStateException("glVertex called outside glBegin/glEnd");
}
t.addVertex(x, y, z);
}
/**
* Check if any immediate mode geometry has been captured.
*
* <p>Note: With immediate flush mode, this will typically return false as geometry
* is returned from end() immediately. This method is kept for edge cases
* where primitives might fail to convert (e.g., incomplete quads).
*/
public static boolean hasGeometry() {
return tessellator().vertexCount != 0;
}
/**
* Reset all state including current texcoord/normal.
* Called when starting a new display list.
*/
public static void reset() {
tessellator().reset();
}
private static final int LOC_POSITION = Usage.POSITION.getAttributeLocation();
private static final int LOC_COLOR = Usage.COLOR.getAttributeLocation();
private static final int LOC_UV0 = Usage.PRIMARY_UV.getAttributeLocation();
private static final int LOC_UV1 = Usage.SECONDARY_UV.getAttributeLocation();
private static final int LOC_NORMAL = Usage.NORMAL.getAttributeLocation();
private static final int NUM_ATTRIBS = 1 + Arrays.stream(Usage.values())
.mapToInt(Usage::getAttributeLocation)
.max().orElse(0);
private static final ByteBuffer[] scratchAttribBuffers = new ByteBuffer[NUM_ATTRIBS];
private static final long[] scratchAttribBaseOffsets = new long[NUM_ATTRIBS];
private static final int[] scratchVBOIds = new int[NUM_ATTRIBS];
private static final ByteBuffer[] scratchVBOBuffers = new ByteBuffer[NUM_ATTRIBS];
private static final long[] scratchVBOReadOffsets = new long[NUM_ATTRIBS];
private static final class AttribReader {
ByteBuffer data;
int stride;
int baseOffset;
int size;
int loc;
VertexAttribState.Attrib attrib;
}
private static final AttribReader[] readers = new AttribReader[NUM_ATTRIBS];
private static int nonPositionCount;
private static final int[] nonPositionIndices = new int[NUM_ATTRIBS - 1];
private static int positionReaderIndex = -1;
static {
for (int i = 0; i < NUM_ATTRIBS; i++) {
readers[i] = new AttribReader();
}
}
/**
* Process a glDrawArrays call during display list recording.
* Reads vertex data from VBOs or client memory referenced by VertexAttribState and converts to immediate mode geometry.
*/
public static DirectTessellator processDrawArraysFromAttribs(int mode, int first, int count) {
final int readVBOCount = readbackAttribVBOs(first, count);
try {
prepareReaders();
if (positionReaderIndex < 0) return null;
return readVerticesFromBuffer(mode, first, count);
} finally {
freeReadbackBuffers(readVBOCount);
}
}
/**
* Process a glDrawElements call during display list recording when a VBO is bound.
*/
public static DirectTessellator processDrawElementsFromVBO(int mode, int indicesCount, int indexType, long indicesOffset, int eboId) {
if (eboId == 0) return null;
final int readVBOCount = readbackAttribVBOs(-1, 0);
try {
prepareReaders();
if (positionReaderIndex < 0) return null;
final int prevEBO = GLStateManager.getBoundEBO();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboSize <= 0) {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
return null;
}
final int indexSize = VertexAttribState.Attrib.glTypeSizeBytes(indexType);
final int eboReadOffset = (int) indicesOffset;
final int eboReadSize = indicesCount * indexSize;
if (eboReadOffset + eboReadSize > eboSize) {
GLStateManager.LOGGER.warn("[EBO] Read range {}+{} exceeds buffer size {}", eboReadOffset, eboReadSize, eboSize);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
return null;
}
final ByteBuffer eboData = MemoryUtilities.memAlloc(eboReadSize);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboReadOffset, eboData);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
try {
return readVerticesFromBufferIndexed(mode, eboData, indexType, 0, indicesCount);
} finally {
MemoryUtilities.memFree(eboData);
}
} finally {
freeReadbackBuffers(readVBOCount);
}
}
/**
* Read data for all enabled attribs into scratchAttribBuffers.
* For VBO-backed attribs, reads VBO data. For client-memory attribs, uses the client pointer directly.
*/
private static int readbackAttribVBOs(int firstVertex, int vertexCount) {
int readVBOCount = 0;
final int prevVBO = GLStateManager.getBoundVBO();
final boolean partialRead = firstVertex >= 0;
for (int i = 0; i < NUM_ATTRIBS; i++) {
scratchAttribBuffers[i] = null;
scratchAttribBaseOffsets[i] = 0;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
if (a.vboId != 0) {
// VBO-backed attrib — read from GPU
ByteBuffer existing = null;
int existingIdx = -1;
for (int j = 0; j < readVBOCount; j++) {
if (scratchVBOIds[j] == a.vboId) {
existing = scratchVBOBuffers[j];
existingIdx = j;
break;
}
}
if (existing != null) {
scratchAttribBuffers[i] = existing;
scratchAttribBaseOffsets[i] = scratchVBOReadOffsets[existingIdx];
} else {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) {
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
continue;
}
final long readOffset;
final int readSize;
if (partialRead) {
final int stride = a.effectiveStride();
long rangeStart = a.offset + (long) firstVertex * stride;
long rangeEnd = a.offset + (long) (firstVertex + vertexCount - 1) * stride + (long) a.size * a.typeSizeBytes();
for (int k = 0; k < NUM_ATTRIBS; k++) {
if (k == i) continue;
final VertexAttribState.Attrib ak = VertexAttribState.get(k);
if (!ak.enabled || ak.vboId != a.vboId) continue;
final int ks = ak.effectiveStride();
rangeStart = Math.min(rangeStart, ak.offset + (long) firstVertex * ks);
rangeEnd = Math.max(rangeEnd, ak.offset + (long) (firstVertex + vertexCount - 1) * ks
+ (long) ak.size * ak.typeSizeBytes());
}
rangeStart = Math.max(0, rangeStart);
rangeEnd = Math.min(bufferSize, rangeEnd);
readOffset = rangeStart;
readSize = (int) (rangeEnd - rangeStart);
} else {
readOffset = 0;
readSize = bufferSize;
}
if (readSize <= 0) continue;
final ByteBuffer buf = MemoryUtilities.memAlloc(readSize);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, readOffset, buf);
scratchAttribBuffers[i] = buf;
scratchAttribBaseOffsets[i] = readOffset;
scratchVBOIds[readVBOCount] = a.vboId;
scratchVBOReadOffsets[readVBOCount] = readOffset;
scratchVBOBuffers[readVBOCount] = buf;
readVBOCount++;
}
} else if (a.clientPointer != null) {
// Client-memory attrib — use pointer directly (no allocation needed)
scratchAttribBuffers[i] = a.clientPointer;
}
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
return readVBOCount;
}
private static void freeReadbackBuffers(int readVBOCount) {
for (int i = 0; i < readVBOCount; i++) {
MemoryUtilities.memFree(scratchVBOBuffers[i]);
scratchVBOBuffers[i] = null;
}
}
private static void prepareReaders() {
nonPositionCount = 0;
positionReaderIndex = -1;
for (int i = 0; i < NUM_ATTRIBS; i++) {
final ByteBuffer buf = scratchAttribBuffers[i];
if (buf == null) continue;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
final AttribReader r = readers[i];
r.data = buf;
r.stride = a.effectiveStride();
r.baseOffset = (int) (a.offset - scratchAttribBaseOffsets[i]);
r.size = a.size;
r.loc = i;
r.attrib = a;
if (i == LOC_POSITION) {
positionReaderIndex = i;
} else {
nonPositionIndices[nonPositionCount++] = i;
}
}
}
private static DirectTessellator readVerticesFromBuffer(int mode, int first, int count) {
begin(mode);
final DirectTessellator t = tessellator();
for (int i = first; i < first + count; i++) {
readVertexFromBuffer(t, i);
}
return end();
}
private static DirectTessellator readVerticesFromBufferIndexed(int mode, ByteBuffer eboData, int indexType, int eboOffset, int indicesCount) {
begin(mode);
final DirectTessellator t = tessellator();
for (int i = 0; i < indicesCount; i++) {
final int index = readIndex(eboData, indexType, eboOffset, i);
readVertexFromBuffer(t, index);
}
return end();
}
public static int readIndex(ByteBuffer ebo, int indexType, int eboOffset, int i) {
return switch (indexType) {
case GL11.GL_UNSIGNED_BYTE -> ebo.get(eboOffset + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> ebo.getShort(eboOffset + i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> ebo.getInt(eboOffset + i * 4);
default -> i;
};
}
private static void readVertexFromBuffer(DirectTessellator t, int vertexIndex) {
for (int i = 0; i < nonPositionCount; i++) {
final AttribReader r = readers[nonPositionIndices[i]];
final int base = r.baseOffset + vertexIndex * r.stride;
emitAttrib(t, r, base);
}
if (positionReaderIndex >= 0) {
final AttribReader r = readers[positionReaderIndex];
final int base = r.baseOffset + vertexIndex * r.stride;
t.addVertex(
r.attrib.readComponent(r.data, base, 0),
r.attrib.readComponent(r.data, base, 1),
(r.size >= 3) ? r.attrib.readComponent(r.data, base, 2) : 0.0f);
}
}
private static void emitAttrib(DirectTessellator t, AttribReader r, int base) {
if (r.loc == LOC_NORMAL) {
t.setNormal(
r.attrib.readComponent(r.data, base, 0),
r.attrib.readComponent(r.data, base, 1),
r.attrib.readComponent(r.data, base, 2));
} else if (r.loc == LOC_UV0) {
t.setTextureUV(
r.attrib.readComponent(r.data, base, 0),
(r.size >= 2) ? r.attrib.readComponent(r.data, base, 1) : 0.0f);
} else if (r.loc == LOC_UV1) {
if (r.attrib.type == GL11.GL_SHORT) {
final int s = r.data.getShort(base) & 0xFFFF;
final int u = (r.size >= 2) ? (r.data.getShort(base + 2) & 0xFFFF) : 0;
t.setBrightness((u << 16) | s);
} else if (r.attrib.type == GL11.GL_FLOAT) {
final int s = (int) r.data.getFloat(base);
final int u = (r.size >= 2) ? (int) r.data.getFloat(base + 4) : 0;
t.setBrightness((u << 16) | (s & 0xFFFF));
}
} else if (r.loc == LOC_COLOR) {
final float cr = r.attrib.readComponent(r.data, base, 0);
final float cg = r.attrib.readComponent(r.data, base, 1);
final float cb = r.attrib.readComponent(r.data, base, 2);
final float ca = (r.size >= 4) ? r.attrib.readComponent(r.data, base, 3) : 1.0f;
t.setColorRGBA((int) (cr * 255.0f), (int) (cg * 255.0f), (int) (cb * 255.0f), (int) (ca * 255.0f));
}
}
}
@@ -1,639 +0,0 @@
package com.gtnewhorizons.angelica.glsm.redirect;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import java.util.ArrayList;
import java.util.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 : cn.methods) {
if (isOpenGlHelper && (mn.name.equals("glBlendFunc") || mn.name.equals("func_148821_a"))) {
continue;
}
boolean redirectInMethod = false;
for (AbstractInsnNode node : mn.instructions.toArray()) {
if (node instanceof TypeInsnNode tNode) {
// Redirect NEW and CHECKCAST for quadric classes
if (tNode.getOpcode() == Opcodes.NEW || tNode.getOpcode() == Opcodes.CHECKCAST) {
final String redirect = typeRedirects.get(tNode.desc);
if (redirect != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting {} in {} from {} to {}", tNode.getOpcode() == Opcodes.NEW ? "NEW" : "CHECKCAST", transformedName, tNode.desc, redirect);
}
tNode.desc = redirect;
changed = true;
}
}
} else if (node instanceof MethodInsnNode mNode) {
if (mNode.desc.equals("(I)V") && mNode.owner.startsWith(GL_PREFIX) && (mNode.name.equals("glEnable") || mNode.name.equals("glDisable"))) {
final AbstractInsnNode prevNode = node.getPrevious();
String name = null;
if (prevNode instanceof LdcInsnNode ldcNode) {
name = glCapRedirects.get(((Integer) ldcNode.cst));
} else if (prevNode instanceof IntInsnNode intNode) {
name = glCapRedirects.get(intNode.operand);
}
if (name != null) {
if (mNode.name.equals("glEnable")) {
name = "enable" + name;
} else {
name = "disable" + name;
}
}
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
if (name == null) {
LOGGER.info("Redirecting call in {} from {}.{}(I)V to GLStateManager.{}(I)V", transformedName, shortOwner, mNode.name, mNode.name);
} else {
LOGGER.info("Redirecting call in {} from {}.{}(I)V to GLStateManager.{}()V", transformedName, shortOwner, mNode.name, name);
}
}
mNode.owner = GLStateManager;
if (name != null) {
mNode.name = name;
mNode.desc = "()V";
mn.instructions.remove(prevNode);
}
changed = true;
redirectInMethod = true;
} else if (mNode.name.equals("makeCurrent") && mNode.owner.startsWith(Drawable)) {
mNode.setOpcode(Opcodes.INVOKESTATIC);
mNode.owner = GLStateManager;
mNode.desc = "(L" + Drawable + ";)V";
mNode.itf = false;
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.makeCurrent()", transformedName);
}
} else {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
if (redirects != null) {
final String glsmName = redirects.get(mNode.name);
if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
}
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
}
}
}
// Redirect <init> calls for quadric classes
if (mNode.getOpcode() == Opcodes.INVOKESPECIAL && mNode.name.equals("<init>")) {
final String redirect = typeRedirects.get(mNode.owner);
if (redirect != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting <init> in {} from {} to {}", transformedName, mNode.owner, redirect);
}
mNode.owner = redirect;
changed = true;
}
}
}
}
if (ASSERT_MAIN_THREAD && redirectInMethod && !transformedName.startsWith(SplashProgress) && !(transformedName.startsWith(MinecraftClient) && ExcludedMinecraftMainThreadChecks.contains(mn.name))) {
mn.instructions.insert(new MethodInsnNode(Opcodes.INVOKESTATIC, GLStateManager, "assertMainThread", "()V", false));
}
}
return changed;
}
private static final class RedirectMap<K> extends HashMap<K, K> {
public static RedirectMap<String> newMap() {
return new RedirectMap<>();
}
public RedirectMap<K> add(K name) {
this.put(name, name);
return this;
}
public RedirectMap<K> add(K name, K newName) {
this.put(name, newName);
return this;
}
}
}
@@ -1,110 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
/**
* Per-texture-unit GL_TEXTURE_ENV state for FFP emulation. Tracks the simple texenv mode and all GL_COMBINE sub-parameters.
*/
public class TexEnvState implements IStateStack<TexEnvState> {
public int mode;
public int combineRgb;
public int combineAlpha;
public final int[] sourceRgb = new int[3];
public final int[] sourceAlpha = new int[3];
public final int[] operandRgb = new int[3];
public final int[] operandAlpha = new int[3];
public float scaleRgb;
public float scaleAlpha;
public float envColorR;
public float envColorG;
public float envColorB;
public float envColorA;
private final TexEnvState[] stack;
private int pointer;
private final boolean isStackEntry;
public TexEnvState() {
this(false);
}
private TexEnvState(boolean isStackEntry) {
this.isStackEntry = isStackEntry;
if (isStackEntry) {
stack = null;
} else {
stack = new TexEnvState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new TexEnvState(true);
}
}
reset();
}
public void copyFrom(TexEnvState other) {
this.mode = other.mode;
this.combineRgb = other.combineRgb;
this.combineAlpha = other.combineAlpha;
System.arraycopy(other.sourceRgb, 0, this.sourceRgb, 0, 3);
System.arraycopy(other.sourceAlpha, 0, this.sourceAlpha, 0, 3);
System.arraycopy(other.operandRgb, 0, this.operandRgb, 0, 3);
System.arraycopy(other.operandAlpha, 0, this.operandAlpha, 0, 3);
this.scaleRgb = other.scaleRgb;
this.scaleAlpha = other.scaleAlpha;
this.envColorR = other.envColorR;
this.envColorG = other.envColorG;
this.envColorB = other.envColorB;
this.envColorA = other.envColorA;
}
public void reset() {
mode = GL11.GL_MODULATE;
combineRgb = GL11.GL_MODULATE;
combineAlpha = GL11.GL_MODULATE;
sourceRgb[0] = GL11.GL_TEXTURE; sourceRgb[1] = GL13.GL_PREVIOUS; sourceRgb[2] = GL13.GL_CONSTANT;
sourceAlpha[0] = GL11.GL_TEXTURE; sourceAlpha[1] = GL13.GL_PREVIOUS; sourceAlpha[2] = GL13.GL_CONSTANT;
operandRgb[0] = GL11.GL_SRC_COLOR; operandRgb[1] = GL11.GL_SRC_COLOR; operandRgb[2] = GL11.GL_SRC_ALPHA;
operandAlpha[0] = GL11.GL_SRC_ALPHA; operandAlpha[1] = GL11.GL_SRC_ALPHA; operandAlpha[2] = GL11.GL_SRC_ALPHA;
scaleRgb = 1.0f;
scaleAlpha = 1.0f;
envColorR = 0.0f; envColorG = 0.0f; envColorB = 0.0f; envColorA = 0.0f;
}
@Override
public TexEnvState push() {
if (stack == null) throw new IllegalStateException("Cannot push stack entry");
if (pointer >= stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].copyFrom(this);
return this;
}
@Override
public TexEnvState pop() {
if (stack == null) throw new IllegalStateException("Cannot pop stack entry");
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
copyFrom(stack[--pointer]);
return this;
}
@Override
public boolean isEmpty() {
return pointer == 0;
}
public boolean isCombineMode() {
return mode == GL13.GL_COMBINE;
}
}
@@ -1,166 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
/**
* Per-VAO vertex attribute pointer state tracking (Mesa-style).
*/
public class VertexAttribState {
public static final int MAX_ATTRIBS = 16;
private static final Int2ObjectOpenHashMap<Attrib[]> vaoAttribs = new Int2ObjectOpenHashMap<>();
private static Attrib[] current;
private static final ArrayDeque<Attrib[]> pool = new ArrayDeque<>();
public static void init(int defaultVAO) {
vaoAttribs.clear();
pool.clear();
current = allocAttribArray();
vaoAttribs.put(defaultVAO, current);
}
public static void onBindVertexArray(int newVAO) {
current = vaoAttribs.computeIfAbsent(newVAO, k -> allocAttribArray());
}
public static void onDeleteVertexArray(int vaoId) {
final Attrib[] arr = vaoAttribs.remove(vaoId);
if (arr != null) {
pool.addLast(arr);
}
}
public static void reset() {
vaoAttribs.clear();
pool.clear();
current = null;
}
private static Attrib[] allocAttribArray() {
Attrib[] arr = pool.pollFirst();
if (arr != null) {
for (Attrib a : arr) a.reset();
return arr;
}
arr = new Attrib[MAX_ATTRIBS];
for (int i = 0; i < MAX_ATTRIBS; i++) arr[i] = new Attrib();
return arr;
}
public static void set(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
}
public static void set(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = vboId;
a.clientPointer = (vboId == 0) ? pointer : null;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
current[index].enabled = enabled;
}
public static Attrib get(int index) {
return current[index];
}
public static boolean hasVBOBoundAttrib() {
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (current[i].enabled && current[i].vboId != 0) return true;
}
return false;
}
public static class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -1,47 +0,0 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Streaming buffer using the classic orphan pattern.
*/
public final class OrphanStreamingBuffer {
private int bufferId;
private int capacity;
public OrphanStreamingBuffer() {
this.bufferId = RENDER_BACKEND.genBuffers();
}
public void upload(ByteBuffer data) {
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
capacity = StreamingUploader.upload(data, capacity);
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
void upload(StreamingUploader.UploadStrategy strategy, ByteBuffer data) {
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
capacity = StreamingUploader.upload(strategy, data, capacity);
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
public int getCapacity() {
return capacity;
}
public int getBufferId() {
return bufferId;
}
public void destroy() {
if (bufferId != 0) {
RENDER_BACKEND.deleteBuffers(bufferId);
bufferId = 0;
}
}
}
@@ -1,56 +0,0 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
public final class StreamingUploader {
public enum UploadStrategy {
BUFFER_DATA,
BUFFER_SUB_DATA,
MAP_BUFFER_RANGE
}
private static final int MAP_WRITE_INVALIDATE_BUFFER = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT;
public static int upload(ByteBuffer data, int capacity) {
return upload(GLStateManager.getInitConfig().getStreamingUploadStrategy(), data, capacity);
}
public static int upload(UploadStrategy strategy, ByteBuffer data, int capacity) {
switch (strategy) {
case BUFFER_DATA -> {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
case BUFFER_SUB_DATA -> {
if (data.remaining() > capacity) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
return capacity;
}
case MAP_BUFFER_RANGE -> {
if (data.remaining() > capacity) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
final int dataSize = data.remaining();
final ByteBuffer mapped = RENDER_BACKEND.mapBufferRange(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
memCopy(memAddress0(data), memAddress0(mapped), dataSize);
RENDER_BACKEND.unmapBuffer(GL15.GL_ARRAY_BUFFER);
return capacity;
}
default -> throw new UnsupportedOperationException();
}
}
}
@@ -1 +0,0 @@
com.gtnewhorizons.angelica.glsm.backend.Lwjgl2GLRenderBackend
@@ -1,46 +0,0 @@
package net.minecraft.client.renderer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Compile stub for MC's Tessellator. DirectTessellator (GTNHLib) extends this.
*/
public class Tessellator {
public static ByteBuffer byteBuffer = ByteBuffer.allocateDirect(0x200000 * 4).order(ByteOrder.nativeOrder());
public static boolean renderingWorldRenderer;
public static final Tessellator instance = new Tessellator();
public boolean defaultTexture;
public int[] rawBuffer;
public int rawBufferSize;
public int textureID;
public int rawBufferIndex;
public int vertexCount;
public int addedVertices;
public int drawMode;
public boolean isDrawing;
public boolean isColorDisabled;
public boolean hasColor;
public boolean hasTexture;
public boolean hasBrightness;
public boolean hasNormals;
public double textureU;
public double textureV;
public int brightness;
public int color;
public int normal;
public double xOffset;
public double yOffset;
public double zOffset;
public int bufferSize;
public Tessellator() {}
public int draw() { return 0; }
public void reset() {}
public void startDrawing(int mode) {}
}
@@ -1,23 +0,0 @@
package net.minecraft.client.shader;
/**
* Compile stub. DirectTessellator references this type.
*/
public class TesselatorVertexState {
public TesselatorVertexState(int[] rawBuffer, int rawBufferIndex, int vertexCount, boolean hasTexture, boolean hasBrightness, boolean hasNormals, boolean hasColor) {}
public int[] getRawBuffer() {return null;}
public int getRawBufferIndex() {return 0;}
public int getVertexCount() {return 0;}
public boolean getHasTexture() {return false;}
public boolean getHasBrightness() {return false;}
public boolean getHasNormals() {return false;}
public boolean getHasColor() {return false;}
}
@@ -1,202 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(GLSMExtension.class)
class FeedbackManagerTest {
@BeforeEach
void setUp() {
FeedbackManager.reset();
}
@AfterEach
void tearDown() {
// Ensure we're back in render mode
if (FeedbackManager.isFeedbackMode()) {
FeedbackManager.glRenderMode(GL11.GL_RENDER);
}
FeedbackManager.reset();
}
@Test
void testStateMachine_noDraws_returnsZero() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(64);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
assertTrue(FeedbackManager.isFeedbackMode());
final int result = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(0, result, "No draws should produce 0 tokens");
assertFalse(FeedbackManager.isFeedbackMode());
}
@Test
void testOverflow_returnsNegative() {
// Buffer with only 2 floats — any triangle will overflow
final FloatBuffer buf = BufferUtils.createFloatBuffer(2);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
// Set up identity matrices and a viewport
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
// Upload 3 vertices to a VBO and draw
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final float[] verts = { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f };
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(9);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_TRIANGLES, 0, 3);
final int result = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(-1, result, "Overflow should return -1");
// Cleanup
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
void testPassThrough() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(64);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
FeedbackManager.glPassThrough(42.0f);
final int result = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(2, result, "PassThrough should write 2 floats");
assertEquals(GL11.GL_PASS_THROUGH_TOKEN, buf.get(0), "First token should be PASS_THROUGH_TOKEN");
assertEquals(42.0f, buf.get(1), "Second token should be the pass-through value");
}
@Test
void testTriangleFeedback_GL2D() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
// Identity MV/proj, viewport (0,0,800,600)
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
// Upload 3 vertices forming a triangle
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
// NDC vertices: (0,0,0), (1,0,0), (0,1,0)
// With identity MVP and viewport 800x600:
// NDC(0,0) → window(400, 300)
// NDC(1,0) → window(800, 300)
// NDC(0,1) → window(400, 600)
final float[] verts = { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f };
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(9);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_TRIANGLES, 0, 3);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
// GL_2D: POLYGON_TOKEN, 3, x0, y0, x1, y1, x2, y2 = 8 floats
assertEquals(8, count, "Triangle in GL_2D should produce 8 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(3.0f, buf.get(1), 0.001f);
// Vertex 0: NDC(0,0) → window(400, 300)
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
// Vertex 1: NDC(1,0) → window(800, 300)
assertEquals(800.0f, buf.get(4), 0.01f);
assertEquals(300.0f, buf.get(5), 0.01f);
// Vertex 2: NDC(0,1) → window(400, 600)
assertEquals(400.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
// Cleanup
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
void testIdentityTransform_viewportMapping() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(64);
FeedbackManager.glFeedbackBuffer(GL11.GL_3D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
// Single point at NDC origin (0,0,0)
final float[] verts = { 0f, 0f, 0f };
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(3);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_POINTS, 0, 1);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
// GL_3D point: POINT_TOKEN, x, y, z = 4 floats
assertEquals(4, count);
assertEquals(GL11.GL_POINT_TOKEN, buf.get(0), 0.001f);
// NDC(0,0,0) → window center (400, 300, 0.5)
assertEquals(400.0f, buf.get(1), 0.01f);
assertEquals(300.0f, buf.get(2), 0.01f);
assertEquals(0.5f, buf.get(3), 0.01f);
// Cleanup
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
}
@@ -1,307 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMExtension;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for FFP TexEnv COMBINE / multi-unit features:
* TexEnvState tracking, FragmentKey packing, FragmentShaderGenerator COMBINE codegen, and glMultiTexCoord routing.
*/
@ExtendWith(GLSMExtension.class)
class FFPCombineShaderTest {
private final List<Integer> shadersToDelete = new ArrayList<>();
private final List<Integer> programsToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
// Delete GL resources
for (int p : programsToDelete) GL20.glDeleteProgram(p);
for (int s : shadersToDelete) GL20.glDeleteShader(s);
programsToDelete.clear();
shadersToDelete.clear();
// Reset texture units to defaults
for (int i = 3; i >= 0; i--) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
GLStateManager.getTextures().getTexEnvState(i).reset();
if (i > 0) {
GLStateManager.getTextures().getTextureUnitStates(i).disable();
}
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
@Test
void testTexEnvStateTracking() {
// Unit 0: COMBINE mode, SUBTRACT RGB, REPLACE alpha
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PRIMARY_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_ONE_MINUS_SRC_COLOR);
TexEnvState env0 = GLStateManager.getTextures().getTexEnvState(0);
assertEquals(GL13.GL_COMBINE, env0.mode);
assertEquals(GL13.GL_SUBTRACT, env0.combineRgb);
assertEquals(GL11.GL_REPLACE, env0.combineAlpha);
assertEquals(GL11.GL_TEXTURE, env0.sourceRgb[0]);
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, env0.operandRgb[0]);
assertEquals(GL13.GL_PRIMARY_COLOR, env0.sourceRgb[1]);
assertEquals(GL11.GL_ONE_MINUS_SRC_COLOR, env0.operandRgb[1]);
// Unit 2: independent state
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
TexEnvState env2 = GLStateManager.getTextures().getTexEnvState(2);
assertEquals(GL13.GL_COMBINE, env2.mode);
assertEquals(GL11.GL_ADD, env2.combineRgb);
// Verify unit 0 unchanged
assertEquals(GL13.GL_SUBTRACT, env0.combineRgb, "Unit 0 should be independent of unit 2");
}
@Test
void testFragmentKeyMultiUnitCombine() {
setupXaeroState();
FragmentKey fk = FragmentKey.fromState();
assertEquals(4, fk.nrEnabledUnits(), "highestEnabled is unit 3 → nrEnabledUnits=4");
assertTrue(fk.unitEnabled(0));
assertFalse(fk.unitEnabled(1), "unit 1 should be disabled");
assertTrue(fk.unitEnabled(2));
assertTrue(fk.unitEnabled(3));
assertEquals(FragmentKey.TEX_ENV_COMBINE, fk.unitMode(0));
assertEquals(FragmentKey.COMBINE_SUBTRACT, fk.unitCombineRgb(0));
assertEquals(FragmentKey.TEX_ENV_COMBINE, fk.unitMode(2));
assertEquals(FragmentKey.COMBINE_ADD, fk.unitCombineRgb(2));
assertEquals(FragmentKey.TEX_ENV_MODULATE, fk.unitMode(3));
// Verify source/operand on unit 0
assertEquals(FragmentKey.SRC_TEXTURE, fk.unitSourceRgb(0, 0));
assertEquals(FragmentKey.OP_SRC_COLOR, fk.unitOperandRgb(0, 0));
assertEquals(FragmentKey.SRC_PRIMARY_COLOR, fk.unitSourceRgb(0, 1));
assertEquals(FragmentKey.OP_SRC_COLOR, fk.unitOperandRgb(0, 1));
}
@Test
void testSingleUnitCombineCompiles() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
FragmentKey fk = FragmentKey.fromState();
String fragSource = FragmentShaderGenerator.generate(fk);
int shader = compileShader(fragSource, GL20.GL_FRAGMENT_SHADER);
assertTrue(shader > 0, "Single-unit COMBINE fragment shader should compile");
}
@Test
void testXaeroMultiUnitCombineCompiles() {
setupXaeroState();
FragmentKey fk = FragmentKey.fromState();
VertexKey vk = VertexKey.fromState(true, false, true, false);
String fragSource = FragmentShaderGenerator.generate(fk);
String vertSource = VertexShaderGenerator.generate(vk);
int vs = compileShader(vertSource, GL20.GL_VERTEX_SHADER);
int fs = compileShader(fragSource, GL20.GL_FRAGMENT_SHADER);
int prog = GL20.glCreateProgram();
programsToDelete.add(prog);
GL20.glAttachShader(prog, vs);
GL20.glAttachShader(prog, fs);
GL20.glLinkProgram(prog);
int linkStatus = GL20.glGetProgrami(prog, GL20.GL_LINK_STATUS);
if (linkStatus == 0) {
String log = GL20.glGetProgramInfoLog(prog, 4096);
fail("Xaero multi-unit program link failed:\n" + log
+ "\n\n--- Vertex Shader ---\n" + vertSource
+ "\n\n--- Fragment Shader ---\n" + fragSource);
}
}
static Stream<Arguments> allCombineFunctions() {
return Stream.of(
Arguments.of("REPLACE", GL11.GL_REPLACE, 1),
Arguments.of("MODULATE", GL11.GL_MODULATE, 2),
Arguments.of("ADD", GL11.GL_ADD, 2),
Arguments.of("ADD_SIGNED", GL13.GL_ADD_SIGNED, 2),
Arguments.of("SUBTRACT", GL13.GL_SUBTRACT, 2),
Arguments.of("INTERPOLATE", GL13.GL_INTERPOLATE, 3),
Arguments.of("DOT3_RGB", GL13.GL_DOT3_RGB, 2),
Arguments.of("DOT3_RGBA", GL13.GL_DOT3_RGBA, 2)
);
}
@ParameterizedTest(name = "COMBINE function {0}")
@MethodSource("allCombineFunctions")
void testAllCombineFunctionsCompile(String name, int glFunc, int numArgs) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, glFunc);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
// Set up appropriate sources
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
if (numArgs >= 2) {
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PRIMARY_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_ALPHA, GL13.GL_PREVIOUS);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA);
}
if (numArgs >= 3) {
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE2_RGB, GL13.GL_PREVIOUS);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE2_ALPHA, GL13.GL_PREVIOUS);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA);
}
FragmentKey fk = FragmentKey.fromState();
String fragSource = FragmentShaderGenerator.generate(fk);
int shader = compileShader(fragSource, GL20.GL_FRAGMENT_SHADER);
assertTrue(shader > 0, "COMBINE function " + name + " should compile");
}
@Test
void testCombineScopeDeclaresColorOutsideBlock() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
FragmentKey fk = FragmentKey.fromState();
String src = FragmentShaderGenerator.generate(fk);
// Result variables declared outside the scope block
assertTrue(src.contains("vec3 combRgb0;"), "combRgb0 should be declared outside scope block");
assertTrue(src.contains("float combAlpha0;"), "combAlpha0 should be declared outside scope block");
// color assignment uses the result variables outside the block
assertTrue(src.contains("vec4(combRgb0, combAlpha0)"), "color should be assigned from combine results outside block");
// The scope block uses { } to contain arg declarations
assertTrue(src.contains(" {\n"), "Should have scope block for arg declarations");
// Bonus: verify it compiles
int shader = compileShader(src, GL20.GL_FRAGMENT_SHADER);
assertTrue(shader > 0, "Scope-correct COMBINE shader should compile");
}
// ---- Test 7: glMultiTexCoord2f routes to ShaderManager.currentTexCoord ----
@Test
void testMultiTexCoordRoutesToShaderManager() {
// Not in immediate mode or display list recording, so should go to ShaderManager.currentTexCoord
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f); // reset
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.5f, 0.75f);
var texCoord = ShaderManager.getCurrentTexCoord();
assertEquals(0.5f, texCoord.x, 1e-6f, "s coordinate should be 0.5");
assertEquals(0.75f, texCoord.y, 1e-6f, "t coordinate should be 0.75");
}
@Test
void testMultiTexCoord2dDelegatesToFloat() {
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glMultiTexCoord2d(GL13.GL_TEXTURE0, 0.5, 0.75);
var texCoord = ShaderManager.getCurrentTexCoord();
assertEquals(0.5f, texCoord.x, 1e-6f, "double 0.5 should arrive as float 0.5");
assertEquals(0.75f, texCoord.y, 1e-6f, "double 0.75 should arrive as float 0.75");
}
/**
* Configure the 4-unit Xaero-like combine chain:
* Unit 0: COMBINE, RGB=SUBTRACT, SRC0=TEXTURE/SRC_COLOR, SRC1=PRIMARY_COLOR/SRC_COLOR
* Unit 1: disabled
* Unit 2: COMBINE, RGB=ADD, SRC0=TEXTURE/SRC_COLOR, SRC1=PREVIOUS/SRC_COLOR
* Unit 3: MODULATE (simple)
*/
private void setupXaeroState() {
// Unit 0: COMBINE / SUBTRACT
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PRIMARY_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
// Unit 1: disabled (skip — default is disabled)
// Unit 2: COMBINE / ADD
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PREVIOUS);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
// Unit 3: simple MODULATE
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
GLStateManager.enableTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
// Restore active unit
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
private int compileShader(String source, int type) {
int shader = GL20.glCreateShader(type);
shadersToDelete.add(shader);
GL20.glShaderSource(shader, source);
GL20.glCompileShader(shader);
int status = GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS);
if (status == 0) {
String log = GL20.glGetShaderInfoLog(shader, 4096);
fail("Shader compilation failed:\n" + log + "\n\n--- Source ---\n" + source);
}
return shader;
}
}
@@ -1,15 +0,0 @@
package net.minecraft.launchwrapper;
import java.util.HashMap;
import java.util.Map;
/**
* Stub for tests - provides blackboard without loading real launchwrapper.
*/
public class Launch {
public static Map<String, Object> blackboard = new HashMap<>();
static {
blackboard.put("lwjgl3ify:rfb-booted", Boolean.FALSE);
}
}
+5 -5
View File
@@ -1,12 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e1118b4c7c2858d28154d086dbff010e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/59a9771cad43219260d9aac9a8ec4d6a/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/832229f0f6f0be60ed817c5a5e5848eb/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/b96cb7670c095245220e7d01d5927b58/redirect
toolchainVersion=25
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+1 -1
View File
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
-113
View File
@@ -1,113 +0,0 @@
plugins {
`java-library`
`maven-publish`
}
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)
}
}
}
}
val lwjglVersion = "3.4.2-SNAPSHOT"
repositories {
mavenLocal()
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 = "Sonatype Snapshots"
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
}
maven {
name = "Maven Central Snapshots"
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
}
mavenCentral()
}
dependencies {
api(project(":glsm"))
compileOnly("com.github.GTNewHorizons:lwjgl3ify:3.0.15:dev") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.3-GTNH") { isTransitive = false }
val osName = System.getProperty("os.name").lowercase()
val osArch = System.getProperty("os.arch").lowercase()
val lwjglNatives = when {
osName.contains("linux") && osArch.contains("aarch64") -> "natives-linux-arm64"
osName.contains("linux") -> "natives-linux"
osName.contains("windows") && osArch.contains("aarch64") -> "natives-windows-arm64"
osName.contains("windows") -> "natives-windows"
osName.contains("mac") && osArch.contains("aarch64") -> "natives-macos-arm64"
osName.contains("mac") -> "natives-macos"
else -> "natives-linux"
}
// LWJGL3
implementation("org.lwjgl:lwjgl:${lwjglVersion}")
implementation("org.lwjgl:lwjgl-opengl:${lwjglVersion}")
runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:$lwjglNatives")
runtimeOnly("org.lwjgl:lwjgl-opengl:$lwjglVersion:$lwjglNatives")
compileOnly("org.apache.logging.log4j:log4j-api:2.0-beta9")
compileOnly("org.jetbrains:annotations:26.0.2")
}
tasks.named<Jar>("jar") {
manifest {
attributes(
"Lwjgl3ify-Aware" to "true"
)
}
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
groupId = "com.gtnewhorizons.angelica"
artifactId = "lwjgl3-backend"
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")
}
}
}
}
}
@@ -1 +0,0 @@
com.gtnewhorizons.angelica.lwjgl3.Lwjgl3GLRenderBackend
-20
View File
@@ -22,25 +22,5 @@ repositories {
includeGroup 'org.taumc'
}
}
maven {
name = 'forge'
url = 'https://maven.minecraftforge.net/'
content {
includeGroup 'net.minecraftforge'
}
}
ivy {
name = 'modernsplash-github'
url = 'https://github.com/MalTeeez/ModernSplash/releases/download/'
patternLayout {
artifact '[revision]/[module]-[revision](-[classifier]).[ext]'
}
content {
includeModule 'com.github.MalTeeez', 'modernsplash'
}
metadataSources {
artifact()
}
}
mavenLocal()
}
+1 -4
View File
@@ -16,8 +16,5 @@ pluginManagement {
}
plugins {
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.24")
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.20")
}
include("glsm")
include("lwjgl3-backend")
@@ -1,11 +1,17 @@
package com.gtnewhorizons.angelica;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.config.ConfigMigrator;
import com.gtnewhorizons.angelica.proxy.CommonProxy;
import com.gtnewhorizons.angelica.utils.AnimationMode;
import com.gtnewhorizons.angelica.utils.ManagedEnum;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -26,19 +32,32 @@ import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
acceptableRemoteVersions = "*",
guiFactory = "com.gtnewhorizons.angelica.config.AngelicaGuiConfigFactory")
public class AngelicaMod {
public static final String MOD_ID = "angelica";
public static final Logger LOGGER = LogManager.getLogger("Angelica");
public static final boolean lwjglDebug = Boolean.getBoolean("org.lwjgl.util.Debug");
@SidedProxy(
clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy",
serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
private static SodiumGameOptions CONFIG;
public static SodiumGameOptions options() {
if (CONFIG == null) {
CONFIG = SodiumGameOptions.load(ConfigMigrator.handleConfigMigration("angelica-options.json"));
}
return CONFIG;
}
@SidedProxy(clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy", serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
public static CommonProxy proxy;
public static final boolean lwjglDebug = Boolean.parseBoolean(System.getProperty("org.lwjgl.util.Debug", "false"));
public static final ManagedEnum<AnimationMode> animationsMode = new ManagedEnum<>(AnimationMode.VISIBLE_ONLY);
public static BlockError blockError;
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
ModStatus.preInit();
proxy.preInit(event);
blockError = new BlockError();
}
@Mod.EventHandler
@@ -1,115 +0,0 @@
package com.gtnewhorizons.angelica.api;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.chunk.Chunk;
/**
* Provides per-block RGB light color data for the rendering pipeline. Mods that replace the lighting engine (e.g. Supernova) implement this
* and register via {@link #register(BlockLightProvider)}.
*
*/
public interface BlockLightProvider {
/**
* Get RGB block light at the given world coordinates.
*
* @param blockAccess the block access context (WorldSlice on builder threads, World on main thread)
* @return packed RGB (see class Javadoc), or {@code -1} if no data is available
*/
int getBlockLightRGB(IBlockAccess blockAccess, int x, int y, int z);
/**
* Get RGB sky light at the given world coordinates.
*
* @param blockAccess the block access context (WorldSlice on builder threads, World on main thread)
* @return packed RGB (see class Javadoc), or {@code -1} if no data
*/
default int getSkyLightRGB(IBlockAccess blockAccess, int x, int y, int z) { return -1; }
/**
* Capture per-section light data on the main thread for use by builder threads.
* Called during {@code ClonedChunkSection.init()}.
*
* @return a {@link SectionLightData} snapshot, or {@code null} if not supported
*/
default SectionLightData prepareSectionData(Chunk chunk, int sectionY) {
return null;
}
/** Pack three 0-15 channels into a single int. */
static int packRGB(int r, int g, int b) {
return (r << 8) | (g << 4) | b;
}
/** Extract the red channel (0-15) from a packed RGB word. */
static int unpackR(int word) {
return (word >> 8) & 0xF;
}
/** Extract the green channel (0-15) from a packed RGB word. */
static int unpackG(int word) {
return (word >> 4) & 0xF;
}
/** Extract the blue channel (0-15) from a packed RGB word. */
static int unpackB(int word) {
return word & 0xF;
}
static BlockLightProvider getInstance() {
return Holder.INSTANCE;
}
static boolean isRegistered() {
return Holder.INSTANCE != Holder.DEFAULT;
}
/**
* Register the global block light provider. Must be called exactly once.
*
* @throws IllegalStateException if a provider has already been registered
*/
static void register(BlockLightProvider provider) {
if (Holder.INSTANCE != Holder.DEFAULT) {
throw new IllegalStateException("BlockLightProvider already registered: " + Holder.INSTANCE.getClass().getName() + ". Cannot register " + provider.getClass().getName());
}
Holder.INSTANCE = provider;
}
/**
* Call from your {@code IFMLLoadingPlugin} static initializer to enable colored light support.
* Must be called before mixin application (i.e. during coremod loading). Calling after mixins are frozen will throw.
*/
static void enableColoredLight() {
if (EarlyFlags.MIXINS_FROZEN) {
throw new IllegalStateException("Too late to enable colored light -- mixin configs have already been evaluated. Call BlockLightProvider.enableColoredLight() from your IFMLLoadingPlugin.");
}
EarlyFlags.COLORED_LIGHT = true;
}
/**
* Freeze the mixin config gate. After this call, {@link #enableColoredLight()} will throw.
* Called by the mixin config evaluation path before reading {@link #coloredLightEnabled()}.
*/
static void freezeMixinConfig() {
EarlyFlags.MIXINS_FROZEN = true;
}
/**
* Returns whether colored light support was enabled via {@link #enableColoredLight()}.
* Call {@link #freezeMixinConfig()} separately to close the enablement window.
*/
static boolean coloredLightEnabled() {
return EarlyFlags.COLORED_LIGHT;
}
final class EarlyFlags {
static boolean COLORED_LIGHT = false;
static boolean MIXINS_FROZEN = false;
}
final class Holder {
static final BlockLightProvider DEFAULT = (_, _, _, _) -> -1;
static BlockLightProvider INSTANCE = DEFAULT;
}
}
@@ -1,34 +0,0 @@
package com.gtnewhorizons.angelica.api;
/**
* Mixin interface for {@code AoFaceData} providing per-corner RGB light values.
* Each array has 4 elements corresponding to the quad's corner vertices.
*/
public interface ExtAoFaceData {
/** Per-corner red light values (block light, 0-15 range). */
float[] angelica$getRl();
/** Per-corner green light values (block light, 0-15 range). */
float[] angelica$getGl();
/** Per-corner blue light values (block light, 0-15 range). */
float[] angelica$getBl();
float angelica$getBlendedR(float[] w);
float angelica$getBlendedG(float[] w);
float angelica$getBlendedB(float[] w);
/** Per-corner red light values (sky light, 0-15 range). */
float[] angelica$getSkyRl();
/** Per-corner green light values (sky light, 0-15 range). */
float[] angelica$getSkyGl();
/** Per-corner blue light values (sky light, 0-15 range). */
float[] angelica$getSkyBl();
float angelica$getBlendedSkyR(float[] w);
float angelica$getBlendedSkyG(float[] w);
float angelica$getBlendedSkyB(float[] w);
}
@@ -1,55 +0,0 @@
package com.gtnewhorizons.angelica.api;
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFacing;
/**
* Mixin interface for {@code LightDataAccess} providing per-block RGB light cache.
*
* <h3>Channel format</h3>
* Individual RGB words use the {@link BlockLightProvider} packing: {@code (r << 8) | (g << 4) | b}, each channel 0-15.
*
*/
public interface ExtLightDataAccess {
/** RGB block light at exact world coordinates. */
int angelica$getRGB(int x, int y, int z);
/** RGB block light offset one block in {@code dir} (face neighbor lookup). */
int angelica$getRGB(int x, int y, int z, ModelQuadFacing dir);
/** RGB block light offset by two directions (edge/corner neighbor for AO). */
int angelica$getRGB(int x, int y, int z, ModelQuadFacing d1, ModelQuadFacing d2);
boolean angelica$isRGBEnabled();
/** RGB sky light at exact world coordinates. */
int angelica$getSkyRGB(int x, int y, int z);
/** RGB sky light offset one block in {@code dir} (face neighbor lookup). */
int angelica$getSkyRGB(int x, int y, int z, ModelQuadFacing dir);
/** RGB sky light offset by two directions (edge/corner neighbor for AO). */
int angelica$getSkyRGB(int x, int y, int z, ModelQuadFacing d1, ModelQuadFacing d2);
boolean angelica$isSkyRGBEnabled();
/**
* Compute cache array index for the given world coordinates. Use with
* {@link #angelica$getRGBByIndex}, {@link #angelica$getSkyRGBByIndex}, or
* {@link #angelica$getFusedByIndex}
*/
int angelica$getIndex(int x, int y, int z);
/** Like {@link #angelica$getRGB(int, int, int)} but with a pre-computed cache index. */
int angelica$getRGBByIndex(int idx, int x, int y, int z);
/** Like {@link #angelica$getSkyRGB(int, int, int)} but with a pre-computed cache index. */
int angelica$getSkyRGBByIndex(int idx, int x, int y, int z);
/**
* Single cache lookup returning both block and sky RGB in the <b>cache format</b>.
* Upper 32 bits = block word (packed RGB | SENTINEL), lower 32 bits = sky word (packed RGB | SENTINEL).
*/
long angelica$getFusedByIndex(int idx, int x, int y, int z);
}
@@ -1,11 +0,0 @@
package com.gtnewhorizons.angelica.api;
import net.minecraft.world.IBlockAccess;
/**
* Interface for {@code LightDataCache} exposing its block access reference.
*/
public interface ExtLightDataCache {
IBlockAccess angelica$getBlockAccess();
}
@@ -1,21 +0,0 @@
package com.gtnewhorizons.angelica.api;
/**
* Interface for {@code QuadLightData} providing per-vertex RGB light values.
*/
public interface ExtQuadLightData {
float[] angelica$getR();
float[] angelica$getG();
float[] angelica$getB();
boolean angelica$isRGBValid();
void angelica$setRGBValid(boolean valid);
float[] angelica$getSkyR();
float[] angelica$getSkyG();
float[] angelica$getSkyB();
boolean angelica$isSkyRGBValid();
void angelica$setSkyRGBValid(boolean valid);
}
@@ -1,118 +0,0 @@
package com.gtnewhorizons.angelica.api;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import java.util.regex.Pattern;
/**
* Public API for querying Angelica's GL call redirections.
* <p>
* Angelica's bytecode transformer redirects GL calls (e.g., {@code GL11.glPushMatrix()}) to
* {@link com.gtnewhorizons.angelica.glsm.GLStateManager GLStateManager} for state tracking and
* core profile compatibility. This works for normal Java bytecode loaded through the Forge classloader,
* but callers that bypass class transformation (e.g., Nashorn/script engines, reflection, MethodHandles)
* need to perform their own redirection.
* <p>
* This class provides the redirect mapping data and a precompiled regex pattern for transforming
* script source text before evaluation.
*
* <h3>Example: transforming a Nashorn script before eval</h3>
* Given a script that uses {@code importPackage(Packages.org.lwjgl.opengl)} and calls
* {@code GL11.glPushMatrix()}, the text transformation rewrites those calls before evaluation:
* <pre>{@code
* // Make GLStateManager available to the script -- either via importPackage:
* script = "importPackage(Packages.com.gtnewhorizons.angelica.glsm);\n" + script;
* // ... or via Java.type for an explicit binding:
* // script = "var GLStateManager = Java.type('" + GLRedirects.TARGET_CLASS + "');\n" + script;
*
* // Rewrite: GL11.glPushMatrix() -> GLStateManager.glPushMatrix()
* script = GLRedirects.getMethodRedirectPattern().matcher(script).replaceAll(mr -> {
* String target = GLRedirects.getTargetMethodName(mr.group(0));
* return target != null ? "GLStateManager." + target : mr.group(0);
* });
*
* engine.eval(script);
* }</pre>
*/
public final class GLRedirects {
/** Fully qualified name of the GLStateManager class that all methods redirect to. */
@SuppressWarnings("unused")
public static final String TARGET_CLASS = "com.gtnewhorizons.angelica.glsm.GLStateManager";
private static final String SHORT_GL_PREFIX;
private static final Map<String, Map<String, String>> NAMED_CLASS_SHORT_NAME_MAP;
private static final Map<String, String> GL_PREFIX_METHOD_REDIRECTS;
private static final Pattern METHOD_REDIRECT_PATTERN;
static {
SHORT_GL_PREFIX = buildShortGLPrefix();
NAMED_CLASS_SHORT_NAME_MAP = buildNamedClassShortNameMap();
GL_PREFIX_METHOD_REDIRECTS = GLSMRedirector.getGLPrefixMethodRedirects();
METHOD_REDIRECT_PATTERN = buildMethodRedirectPattern();
}
private GLRedirects() {}
/**
* Returns a precompiled pattern that matches redirected {@code ClassName.methodName} references
* using short (simple) class names, as they might typically appear in script text.
*
* @return compiled regex pattern
*/
public static Pattern getMethodRedirectPattern() {
return METHOD_REDIRECT_PATTERN;
}
/**
* Looks up the GLStateManager target method name for a short-name reference.
*
* @param classAndMethod short class name and method, e.g., {@code "GL11.glPushMatrix"}
* @return the target method name on GLStateManager (e.g., {@code "glPushMatrix"}), or {@code null} if this reference is not redirected
*/
public static String getTargetMethodName(String classAndMethod) {
final int dot = classAndMethod.indexOf('.');
if (dot < 0) return null;
final String className = classAndMethod.substring(0, dot);
final String methodName = classAndMethod.substring(dot + 1);
final Map<String, String> classRedirects = NAMED_CLASS_SHORT_NAME_MAP.get(className);
if (classRedirects != null) {
return classRedirects.get(methodName);
}
if (className.startsWith(SHORT_GL_PREFIX)) {
return GL_PREFIX_METHOD_REDIRECTS.get(methodName);
}
return null;
}
private static String buildShortGLPrefix() {
final String glPrefix = GLSMRedirector.getGLPrefix();
return glPrefix.substring(glPrefix.lastIndexOf('/') + 1);
}
private static Map<String, Map<String, String>> buildNamedClassShortNameMap() {
final Map<String, Map<String, String>> named = new HashMap<>();
for (final var entry : GLSMRedirector.getNamedClassMethodRedirects().entrySet()) {
final String internalName = entry.getKey();
final String shortName = internalName.substring(internalName.lastIndexOf('/') + 1);
named.put(shortName, entry.getValue());
}
return named;
}
private static Pattern buildMethodRedirectPattern() {
final TreeSet<String> alternatives = new TreeSet<>();
alternatives.add(Pattern.quote(SHORT_GL_PREFIX) + "\\w+");
for (final String shortName : NAMED_CLASS_SHORT_NAME_MAP.keySet()) {
alternatives.add(Pattern.quote(shortName));
}
final String classAlt = String.join("|", alternatives);
return Pattern.compile("\\b(" + classAlt + ")\\.\\w+\\b");
}
}
@@ -1,41 +0,0 @@
package com.gtnewhorizons.angelica.api;
/**
* Provides RGB light data for a single chunk section. Implemented by external providers (e.g. Supernova)
* and returned from {@link BlockLightProvider#prepareSectionData}.
*
* <h3>Transport format</h3>
* {@link #getRGBAndSkyRGB} returns a compact <b>transport format</b>: {@code ((block & 0xFFFF) << 16) | (sky & 0xFFFF)}.
*/
public interface SectionLightData {
/**
* Get RGB block light at section-local coordinates.
*
* @param localX 0-15
* @param localY 0-15
* @param localZ 0-15
* @return packed {@code (r << 8) | (g << 4) | b}, each channel 0-15, or {@code -1} if unavailable
*/
int getRGB(int localX, int localY, int localZ);
/**
* Get RGB sky light at section-local coordinates.
*
* @param localX 0-15
* @param localY 0-15
* @param localZ 0-15
* @return packed {@code (r << 8) | (g << 4) | b}, each channel 0-15, or {@code -1} if unavailable
*/
default int getSkyRGB(int localX, int localY, int localZ) { return -1; }
/**
* Get both block and sky RGB in a single call (transport format). Implementations can override
* to share index computation and apply section-level fast paths.
*
* @return {@code ((block & 0xFFFF) << 16) | (sky & 0xFFFF)}
*/
default long getRGBAndSkyRGB(int localX, int localY, int localZ) {
return ((long)(getRGB(localX, localY, localZ) & 0xFFFF) << 16) | (getSkyRGB(localX, localY, localZ) & 0xFFFF);
}
}
@@ -1,21 +0,0 @@
package com.gtnewhorizons.angelica.api;
/**
* Computes an RGB tint from block light and sky light channel values. The lightmap handles brightness; the tint only controls color.
*/
@FunctionalInterface
public interface TintComputer {
/**
* Blend block light RGB and sky light RGB into a tint color.
*
* @param br block light red (0-15)
* @param bg block light green (0-15)
* @param bb block light blue (0-15)
* @param sr sky light red (0-15, already adjusted for sky subtraction)
* @param sg sky light green (0-15)
* @param sb sky light blue (0-15)
* @param out float[3] output: R, G, B tint multipliers (typically 0-1)
*/
void computeTint(float br, float bg, float bb, float sr, float sg, float sb, float[] out);
}
@@ -1,82 +0,0 @@
package com.gtnewhorizons.angelica.api;
import java.util.ArrayList;
import java.util.List;
/**
* Registry for tint blend modes used by the colored light pipeline. Provides a default no-tint mode.
* External mods (e.g. Supernova) can register additional modes via {@link #registerMode(String, TintComputer)}.
*
*/
public final class TintRegistry {
private static final TintComputer NO_TINT = (br, bg, bb, sr, sg, sb, out) -> {
out[0] = 1f;
out[1] = 1f;
out[2] = 1f;
};
private static final List<TintComputer> computers = new ArrayList<>();
private static final List<String> names = new ArrayList<>();
private static TintComputer current;
private static volatile int currentOrdinal;
static {
registerMode("NONE", NO_TINT);
current = NO_TINT;
currentOrdinal = 0;
}
private TintRegistry() {}
/** Register a named tint blend mode. Returns the ordinal assigned. */
public static int registerMode(String name, TintComputer computer) {
final int ordinal = computers.size();
computers.add(computer);
names.add(name);
return ordinal;
}
/** Returns the currently active tint computer. */
public static TintComputer getCurrent() {
return current;
}
/** Returns the ordinal of the current mode, or -1 if a custom (non-registered) computer is active. */
public static int getCurrentOrdinal() {
return currentOrdinal;
}
/** Switch to a registered mode by ordinal. */
public static void setCurrentByOrdinal(int ordinal) {
if (ordinal < 0 || ordinal >= computers.size()) {
throw new IllegalArgumentException("Invalid tint mode ordinal: " + ordinal + " (registered modes: " + computers.size() + ")");
}
current = computers.get(ordinal);
currentOrdinal = ordinal;
}
/** Set a custom tint computer directly. */
public static void setCurrent(TintComputer computer) {
current = computer;
// Resolve ordinal; custom computers not in the registry get -1
for (int i = 0; i < computers.size(); i++) {
if (computers.get(i) == computer) {
currentOrdinal = i;
return;
}
}
currentOrdinal = -1;
}
/** Returns the number of registered blend modes. */
public static int getModeCount() {
return computers.size();
}
/** Returns the display name of a registered mode by ordinal. */
public static String getModeName(int ordinal) {
if (ordinal < 0 || ordinal >= names.size()) return "UNKNOWN";
return names.get(ordinal);
}
}
@@ -2,34 +2,37 @@ package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.Setter;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.gl.program.ProgramBuilder;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.apache.commons.io.IOUtils;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Objects;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memRealloc;
/**
* A batching replacement for {@code FontRenderer}
@@ -41,36 +44,43 @@ public class BatchingFontRenderer {
/** The underlying FontRenderer object that's being accelerated */
protected FontRenderer underlying;
/** Array of width of all the characters in default.png */
protected int[] charWidth;
protected int[] charWidth = new int[256];
/** Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory. */
protected byte[] glyphWidth;
/**
* Array of RGB triplets defining the 16 standard chat colors followed by 16 darker version of the same colors for
* drop shadows.
*/
private final int[] colorCode;
private int[] colorCode;
/** Location of the primary font atlas to bind. */
protected final ResourceLocation locationFontTexture;
private final int AAMode;
private final int AAStrength;
private final int alphaTestRefLocation;
private final int mvpMatrixLocation;
private final int fontShaderId;
private static int fontVAO = 0;
private static int vboPositions, vboColors, vboTexCoords, vboTexBounds, vboIndices;
private static int capPositions, capColors, capTexCoords, capTexBounds, capIndices;
final boolean isSGA;
final boolean isSplash;
/** For use with modded books. Affects calculations and forces some defaults. */
@Setter
boolean bookMode = false;
private static class FontAAShader {
private static Program fontShader = null;
public static Program getProgram() {
if (fontShader == null) {
final String vsh = ShaderLoader.getShaderSource("angelica:fontFilter.vsh");
final String fsh = ShaderLoader.getShaderSource("angelica:fontFilter.fsh");
fontShader = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0)).build();
String vsh, fsh;
try {
fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.fsh"))), StandardCharsets.UTF_8);
vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.vsh"))), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
ProgramBuilder builder = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0));
fontShader = builder.build();
}
return fontShader;
}
@@ -94,136 +104,92 @@ public class BatchingFontRenderer {
//noinspection deprecation
fontShaderId = FontAAShader.getProgram().getProgramId();
AAMode = GLStateManager.glGetUniformLocation(fontShaderId, "aaMode");
AAStrength = GLStateManager.glGetUniformLocation(fontShaderId, "strength");
alphaTestRefLocation = GLStateManager.glGetUniformLocation(fontShaderId, "alphaTestRef");
mvpMatrixLocation = GLStateManager.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
if (ebo == null) {
ebo = new IndexBuffer();
vbo = GLStateManager.glGenBuffers();
allocateBuffers();
}
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
mvpMatrixLocation = GL20.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
}
// === Batched rendering
private int batchDepth = 0;
private int vtxWriterIndex = 0;
private int idxWriterIndex = 0;
private static final int INITIAL_BATCH_SIZE = 2048;
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
"this is invalid!");
// Layout in data:
// [v, v, t, t, c, c, c, c, tb, tb, tb, tb]
// v, t and tb are floats, c is bytes; 36 bytes total
private static final int VERTEX_SIZE = 36;
private static int rawCapacity = INITIAL_BATCH_SIZE * VERTEX_SIZE;
private static ByteBuffer vertexData = memAlloc(rawCapacity);
private static long vertexDataAddress = memAddress0(vertexData);
private static int vboCapacity;
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
private static int fontVAO = 0;
private static int vbo;
private static IndexBuffer ebo;
private int batchDepth = 0;
private int vertexDataPos = 0;
private int idxWriterIndex = 0;
private FloatBuffer batchVtxPositions = memAllocFloat(INITIAL_BATCH_SIZE * 2);
private ByteBuffer batchVtxColors = memAlloc(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxTexCoords = memAllocFloat(INITIAL_BATCH_SIZE * 2);
private IntBuffer batchIndices = memAllocInt(INITIAL_BATCH_SIZE / 2 * 3);
private FloatBuffer batchVtxTexBounds = memAllocFloat(INITIAL_BATCH_SIZE * 4);
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
private void allocateBuffers() {
populateEBO(rawCapacity / VERTEX_SIZE);
}
private void populateEBO(int capacity) {
final int quadCount = capacity * 6;
final ByteBuffer data = memAlloc(quadCount * 6 * 2);
long ptr = memAddress0(data);
for (int i = 0; i < quadCount; i++) {
int base = (i * 4);
// triangle 1
memPutShort(ptr, (short) base);
memPutShort(ptr + 2, (short) (base + 1));
memPutShort(ptr + 4, (short) (base + 2));
// triangle 2
memPutShort(ptr + 6, (short) (base + 2));
memPutShort(ptr + 8, (short) (base + 1));
memPutShort(ptr + 10, (short) (base + 3));
ptr += 12;
}
ebo.upload(data);
memFree(data);
}
private void ensureCapacity() {
if (vertexDataPos + (4 * VERTEX_SIZE) > rawCapacity) {
rawCapacity *= 2;
vertexData = memRealloc(vertexData, rawCapacity);
vertexDataAddress = memAddress0(vertexData);
allocateBuffers();
}
}
/** */
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
final long ptr = vertexDataAddress + vertexDataPos;
// v, v
memPutFloat(ptr, x);
memPutFloat(ptr + 4, y);
// t, t
memPutFloat(ptr + 8, u);
memPutFloat(ptr + 12, v);
// c, c, c, c
final int oldCap = batchVtxPositions.capacity() / 2;
if (vtxWriterIndex >= oldCap) {
final int newCap = oldCap * 2;
batchVtxPositions = memRealloc(batchVtxPositions, newCap * 2);
batchVtxColors = memRealloc(batchVtxColors, newCap * 4);
batchVtxTexCoords = memRealloc(batchVtxTexCoords, newCap * 2);
batchVtxTexBounds = memRealloc(batchVtxTexBounds, newCap * 4);
final int oldIdxCap = batchIndices.capacity();
final int newIdxCap = oldIdxCap * 2;
batchIndices = memRealloc(batchIndices, newIdxCap);
}
final int idx = vtxWriterIndex;
final int idx2 = idx * 2;
final int idx4 = idx * 4;
batchVtxPositions.put(idx2, x);
batchVtxPositions.put(idx2 + 1, y);
// 0xAARRGGBB
memPutByte(ptr + 16, (byte) ((rgba >> 16) & 0xFF));
memPutByte(ptr + 17, (byte) ((rgba >> 8) & 0xFF));
memPutByte(ptr + 18, (byte) (rgba & 0xFF));
memPutByte(ptr + 19, (byte) ((rgba >> 24) & 0xFF));
// tb, tb, tb, tb
memPutFloat(ptr + 20, uMin);
memPutFloat(ptr + 24, uMax);
memPutFloat(ptr + 28, vMin);
memPutFloat(ptr + 32, vMax);
vertexDataPos += VERTEX_SIZE;
batchVtxColors.put(idx4, (byte) ((rgba >> 16) & 0xFF));
batchVtxColors.put(idx4 + 1, (byte) ((rgba >> 8) & 0xFF));
batchVtxColors.put(idx4 + 2, (byte) (rgba & 0xFF));
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
batchVtxTexCoords.put(idx2, u);
batchVtxTexCoords.put(idx2 + 1, v);
batchVtxTexBounds.put(idx4, uMin);
batchVtxTexBounds.put(idx4 + 1, uMax);
batchVtxTexBounds.put(idx4 + 2, vMin);
batchVtxTexBounds.put(idx4 + 3, vMax);
vtxWriterIndex++;
}
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
ensureCapacity();
final int vtxId = vtxWriterIndex;
pushVtx(x, y, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x, y + h, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x + w, y, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x + w, y + h, rgba, 0, 0, 0, 0, 0, 0);
pushQuadIdx();
pushQuadIdx(vtxId);
}
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
ensureCapacity();
final int vtxId = vtxWriterIndex;
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushQuadIdx();
pushQuadIdx(vtxId);
}
private void pushQuadIdx() {
private int pushQuadIdx(int startV) {
final int idx = idxWriterIndex;
batchIndices.put(idx, startV);
batchIndices.put(idx + 1, startV + 1);
batchIndices.put(idx + 2, startV + 2);
//
batchIndices.put(idx + 3, startV + 2);
batchIndices.put(idx + 4, startV + 1);
batchIndices.put(idx + 5, startV + 3);
idxWriterIndex += 6;
return idx;
}
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
@@ -298,6 +264,9 @@ public class BatchingFontRenderer {
* allow for easier optimizing of blocks of font rendering code.
*/
public void beginBatch() {
if (batchDepth == Integer.MAX_VALUE) {
throw new StackOverflowError("More than Integer.MAX_VALUE nested font rendering batch operations");
}
batchDepth++;
}
@@ -313,28 +282,28 @@ public class BatchingFontRenderer {
}
}
private static int streamUpload(int target, int vbo, long address, int bytes, int cap) {
GL15.glBindBuffer(target, vbo);
final ByteBuffer data = memByteBuffer(address, bytes);
if (bytes > cap) {
GL15.glBufferData(target, data, GL15.GL_STREAM_DRAW);
return bytes;
}
GL15.glBufferData(target, cap, GL15.GL_STREAM_DRAW);
GL15.glBufferSubData(target, 0, data);
return cap;
}
private static final Matrix4f scratchMvp = new Matrix4f();
private int fontAAModeLast = -1;
private int fontAAStrengthLast = -1;
private void flushBatch() {
if (vertexDataPos == 0) {
clearBatch();
return;
}
// Upload first (to reduce stalls)
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
vertexData.limit(vertexDataPos);
vboCapacity = StreamingUploader.upload(vertexData, vboCapacity);
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
// Sort&Draw
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
final boolean isBlendEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_BLEND);
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
boolean textureChanged = false;
@@ -345,50 +314,68 @@ public class BatchingFontRenderer {
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glShadeModel(GL11.GL_FLAT);
GLStateManager.glUseProgram(fontShaderId);
if (FontConfig.fontAAMode != fontAAModeLast) {
fontAAModeLast = FontConfig.fontAAMode;
GLStateManager.glUniform1i(AAMode, FontConfig.fontAAMode);
}
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
fontAAStrengthLast = FontConfig.fontAAStrength;
GLStateManager.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
}
GLStateManager.glUniform1f(alphaTestRefLocation, GLStateManager.getAlphaState().getReference());
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), scratchMvp);
scratchMvp.get(mvpBuf);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
final boolean useFontShader = prevProgram == 0;
if (useFontShader) {
GLStateManager.glUseProgram(fontShaderId);
if (FontConfig.fontAAMode != fontAAModeLast) {
fontAAModeLast = FontConfig.fontAAMode;
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
}
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
fontAAStrengthLast = FontConfig.fontAAStrength;
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
}
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), scratchMvp);
scratchMvp.get(mvpBuf);
GL20.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
}
}
if (fontVAO == 0) {
fontVAO = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(fontVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
ebo.bind();
// position
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 0);
GLStateManager.glEnableVertexAttribArray(0);
// texcoords
GLStateManager.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 8);
GLStateManager.glEnableVertexAttribArray(1);
// color
GLStateManager.glVertexAttribPointer(2, 4, GL11.GL_UNSIGNED_BYTE, true, VERTEX_SIZE, 16);
GLStateManager.glEnableVertexAttribArray(2);
// tex bounds
GLStateManager.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, VERTEX_SIZE, 20);
GLStateManager.glEnableVertexAttribArray(3);
fontVAO = org.lwjgl.opengl.GL30.glGenVertexArrays();
vboPositions = GL15.glGenBuffers();
vboColors = GL15.glGenBuffers();
vboTexCoords = GL15.glGenBuffers();
vboTexBounds = GL15.glGenBuffers();
vboIndices = GL15.glGenBuffers();
}
GLStateManager.glBindVertexArray(fontVAO);
final int vtxCount = vtxWriterIndex;
// Location 0: position (vec2, float)
batchVtxPositions.position(0).limit(vtxCount * 2);
capPositions = streamUpload(GL15.GL_ARRAY_BUFFER, vboPositions, memAddress(batchVtxPositions), batchVtxPositions.remaining() * 4, capPositions);
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(0);
// Location 1: color (vec4, unsigned byte normalized)
batchVtxColors.position(0).limit(vtxCount * 4);
capColors = streamUpload(GL15.GL_ARRAY_BUFFER, vboColors, memAddress(batchVtxColors), batchVtxColors.remaining(), capColors);
GL20.glVertexAttribPointer(1, 4, GL11.GL_UNSIGNED_BYTE, true, 0, 0L);
GL20.glEnableVertexAttribArray(1);
// Location 2: texcoord0 (vec2, float)
batchVtxTexCoords.position(0).limit(vtxCount * 2);
capTexCoords = streamUpload(GL15.GL_ARRAY_BUFFER, vboTexCoords, memAddress(batchVtxTexCoords), batchVtxTexCoords.remaining() * 4, capTexCoords);
GL20.glVertexAttribPointer(2, 2, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(2);
// Location 3: texbounds (vec4, float)
batchVtxTexBounds.position(0).limit(vtxCount * 4);
capTexBounds = streamUpload(GL15.GL_ARRAY_BUFFER, vboTexBounds, memAddress(batchVtxTexBounds), batchVtxTexBounds.remaining() * 4, capTexBounds);
GL20.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(3);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
// Stream upload index buffer
batchIndices.position(0).limit(idxWriterIndex);
capIndices = streamUpload(GL15.GL_ELEMENT_ARRAY_BUFFER, vboIndices, memAddress(batchIndices), batchIndices.remaining() * 4, capIndices);
// Use plain for loop to avoid allocations
final FontDrawCmd[] cmdsData = batchCommands.elements();
final int cmdsSize = batchCommands.size();
@@ -406,34 +393,32 @@ public class BatchingFontRenderer {
}
lastTexture = cmd.texture;
}
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, cmd.idxCount, GL11.GL_UNSIGNED_SHORT, (long) cmd.startVtx * 2L);
GL11.glDrawElements(GL11.GL_TRIANGLES, cmd.idxCount, GL11.GL_UNSIGNED_INT, (long) cmd.startVtx * 4L);
}
if (useFontShader) {
GLStateManager.glUseProgram(prevProgram);
}
GLStateManager.glUseProgram(prevProgram);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
if (isTextureEnabledBefore) {
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
}
if (!isBlendEnabledBefore) {
GLStateManager.disableBlend();
}
if (textureChanged) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
}
clearBatch();
}
private void clearBatch() {
// Clear for the next batch
batchCommandPool.addAll(batchCommands);
batchCommands.clear();
vertexDataPos = 0;
vtxWriterIndex = 0;
idxWriterIndex = 0;
batchVtxPositions.limit(batchVtxPositions.capacity());
batchVtxColors.limit(batchVtxColors.capacity());
batchVtxTexCoords.limit(batchVtxTexCoords.capacity());
batchVtxTexBounds.limit(batchVtxTexBounds.capacity());
batchIndices.limit(batchIndices.capacity());
}
// === Actual text mesh generation
@@ -443,7 +428,7 @@ public class BatchingFontRenderer {
}
public boolean forceDefaults() {
return this.bookMode || this.isSGA || this.isSplash;
return this.isSGA || this.isSplash;
}
public float getGlyphScaleX() {
@@ -599,7 +584,7 @@ public class BatchingFontRenderer {
// Check ASCII space, NBSP, NNBSP
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
curX += 4 * this.getWhitespaceScale() + (curBold ? 1 : 0);
curX += 4 * this.getWhitespaceScale();
continue;
}
@@ -649,8 +634,7 @@ public class BatchingFontRenderer {
final int vtxCount = 4 * charCount;
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
curX += (xAdvance + (curBold ? 1.0f : 0.0f)) + getGlyphSpacing();
if (bookMode) { curX = (int) curX; }
curX += (xAdvance + (curBold ? shadowOffset : 0.0f)) + getGlyphSpacing();
underlineEndX = curX;
strikethroughEndX = curX;
}
@@ -97,7 +97,7 @@ public final class FontProviderUnicode implements FontProvider {
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset * FontConfig.fontShadowOffsetUC;
return FontConfig.fontShadowOffset;
}
@Override
@@ -75,16 +75,13 @@ public class FontStrategist {
}
/**
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether or not
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
*/
public static FontProvider getFontProvider(BatchingFontRenderer me, char chr, boolean customFontEnabled, boolean forceUnicode) {
if (me.isSGA && FontProviderMC.get(true).isGlyphAvailable(chr)) {
return FontProviderMC.get(true);
}
if (me.bookMode) {
return FontProviderUnicode.get();
}
if (customFontEnabled && !me.isSplash) {
FontProvider fp;
fp = FontProviderCustom.getPrimary();
@@ -38,7 +38,7 @@ public class FontConfigScreen extends GuiScreen {
private ArrayList<Font> displayedFonts;
private GuiTextField searchBox;
private GuiTextField testArea;
private int lastSliderPageIndex = 0;
private int sliderPages = 0;
private int displayedSliderPage = 0;
private final int sliderWidth = 160;
private final int sliderHeight = 20;
@@ -58,7 +58,6 @@ public class FontConfigScreen extends GuiScreen {
SliderClone.Option optFontAAMode = new SliderClone.Option(0, 2, 1);
SliderClone.Option optFontAAStrength = new SliderClone.Option(1, 24, 1);
SliderClone.Option optCustomFontScale = new SliderClone.Option(0.1f, 3, 0.05f);
SliderClone.Option optShadowOffsetUC = new SliderClone.Option(0, 2, 0.05f);
public FontConfigScreen(GuiScreen parent) {
this.title = I18n.format("options.angelica.fontconfig.title");
@@ -227,16 +226,6 @@ public class FontConfigScreen extends GuiScreen {
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optShadowOffsetUC)
.initialValue(FontConfig.fontShadowOffsetUC)
.setter(value -> FontConfig.fontShadowOffsetUC = value)
.langKey("options.angelica.fontconfig.shadow_offset_unicode")
.formatString("x%3.2f")
.build()
);
final int halfWidth = this.width / 2;
final int sliderSpacing = 4;
@@ -247,11 +236,11 @@ public class FontConfigScreen extends GuiScreen {
final int y1 = this.height - 65;
final int y2 = this.height - 65 + sliderHeight + sliderSpacing;
this.lastSliderPageIndex = (sliders.size() - 1) / 6;
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.lastSliderPageIndex);
this.sliderPages = sliders.size() / 6;
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.sliderPages);
this.backButton.enabled = (this.displayedSliderPage != 0);
this.fwdButton.enabled = (this.displayedSliderPage != this.lastSliderPageIndex);
this.fwdButton.enabled = (this.displayedSliderPage != this.sliderPages);
final int start = 6 * this.displayedSliderPage;
final int end = Math.min(6 * (this.displayedSliderPage + 1), sliders.size());
@@ -315,7 +304,7 @@ public class FontConfigScreen extends GuiScreen {
}
private void switchPage(int offset) {
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.lastSliderPageIndex);
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.sliderPages);
this.buttonList.removeIf(guiButton -> guiButton instanceof SliderClone);
this.initSliders();
}
@@ -1,119 +0,0 @@
package com.gtnewhorizons.angelica.client.rendering;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.ITessellatorInstance;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraft.client.renderer.Tessellator;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Vector3f;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
/**
* DirectTessellator subclass for deferred particle batching. Accumulates vertex data from
* multiple interceptDraw() calls, recording per-draw metadata as {@link DrawRange} entries.
* Computes a delta modelview matrix at each intercept to pre-transform vertices.
*/
class DeferredBatchTessellator extends DirectTessellator {
@Desugar
record DrawRange(long stateKey, int byteOffset, int byteLength, int vertexCount, int drawMode, int flags) {}
private final List<DrawRange> ranges = new ArrayList<>();
private final Matrix4f defaultModelview = new Matrix4f();
private final Matrix4f defaultModelviewInverse = new Matrix4f();
private final Matrix4f deltaMatrix = new Matrix4f(); // reusable scratch
private final Vector3f scratch = new Vector3f();
private Matrix4fc currentTransform;
private Tessellator parentTessellator;
private boolean inIntercept;
DeferredBatchTessellator(ByteBuffer buffer) {
super(buffer, false); // deleteAfter=false: keep buffer across resets for reuse
}
void setParentTessellator(Tessellator parent) {
this.parentTessellator = parent;
}
@Override
public int draw() {
if (!inIntercept) {
final Tessellator parent = parentTessellator;
if (parent != null && parent.isDrawing) {
interceptDraw(parent);
((ITessellatorInstance) parent).discard();
}
}
isDrawing = false;
return 0;
}
@Override
protected long writeVertexData(VertexFormat format, int[] rawBuffer, int rawBufferIndex) {
if (currentTransform != null) {
return format.writeToBuffer0(writePtr, rawBuffer, rawBufferIndex, currentTransform, scratch);
}
return super.writeVertexData(format, rawBuffer, rawBufferIndex);
}
/** Snapshot the current modelview as the "default" for this bracket. */
void snapshotDefaultModelview() {
defaultModelview.set(GLStateManager.getModelViewMatrix());
defaultModelview.invert(defaultModelviewInverse);
}
@Override
protected int interceptDraw(Tessellator tess) {
inIntercept = true;
try {
// Compute delta: if current MV differs from default, pre-transform vertices
final Matrix4f currentMV = GLStateManager.getModelViewMatrix();
if (!currentMV.equals(defaultModelview)) {
defaultModelviewInverse.mul(currentMV, deltaMatrix);
this.currentTransform = deltaMatrix;
} else {
this.currentTransform = null;
}
final int byteOffsetBefore = bufferLimit();
final int vertCount = tess.vertexCount;
final int drawMode = tess.drawMode;
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
final long stateKey = DeferredDrawBatcher.captureStateKey();
final int result = super.interceptDraw(tess); // repacks int[] -> ByteBuffer + transforms if vertexTransform set
final int byteOffsetAfter = bufferLimit();
ranges.add(new DrawRange(stateKey, byteOffsetBefore, byteOffsetAfter - byteOffsetBefore, vertCount, drawMode, flags));
this.currentTransform = null; // clear for next draw
return result;
} finally {
inIntercept = false;
}
}
List<DrawRange> getRanges() { return ranges; }
void clearRanges() { ranges.clear(); }
void copyRange(int byteOffset, int byteLength, long destAddress) {
memCopy(startPtr + byteOffset, destAddress, byteLength);
}
@Override
protected void onRemovedFromStack() {
parentTessellator = null;
clearRanges();
super.onRemovedFromStack(); // calls reset(), keeps baseBuffer (deleteAfter=false)
}
}
@@ -1,179 +0,0 @@
package com.gtnewhorizons.angelica.client.rendering;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import lombok.Getter;
import net.minecraft.client.renderer.Tessellator;
import org.lwjgl.opengl.GL11;
import java.util.Comparator;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
/**
* Deferred draw batching coordinator. During rendering regions (e.g. particles),
* intercepts tessellator.draw() calls via DirectTessellator, captures pre-packed vertex data
* + GL state, then flushes all captured batches sorted by state key.
* <p>
* Vertices are pre-transformed at capture time.
*/
public class DeferredDrawBatcher {
private static final int INITIAL_BUFFER_SIZE = 64 * 1024;
@Getter private static boolean active = false;
private static DeferredBatchTessellator batchTessellator;
private DeferredDrawBatcher() {}
/**
* Begin deferred capture mode. Called after startDrawingQuads().
* Pushes a DeferredBatchTessellator onto the DirectTessellator stack so that
* subsequent tessellator.draw() calls are intercepted and accumulated.
*/
public static void enter() {
active = true;
if (batchTessellator == null) {
batchTessellator = new DeferredBatchTessellator(memAlloc(INITIAL_BUFFER_SIZE));
}
batchTessellator.clearRanges();
batchTessellator.setParentTessellator(Tessellator.instance);
batchTessellator.snapshotDefaultModelview();
TessellatorManager.startCapturingDirect(batchTessellator);
}
/**
* Exit deferred mode and flush all captured batches. Groups entries by state key
* and vertex format, issues one draw per unique group. Restores the GL state that
* was active for the last group so vanilla sees consistent state.
*/
public static void exitAndFlush() {
active = false;
final List<DeferredBatchTessellator.DrawRange> ranges = batchTessellator.getRanges();
if (!ranges.isEmpty()) {
ranges.sort(Comparator.comparingLong(DeferredBatchTessellator.DrawRange::stateKey));
flushSorted(ranges);
}
TessellatorManager.stopCapturingDirect();
}
private static void flushSorted(List<DeferredBatchTessellator.DrawRange> ranges) {
long currentKey = ranges.get(0).stateKey();
int groupStart = 0;
for (int i = 0; i <= ranges.size(); i++) {
final boolean endOfList = (i == ranges.size());
final boolean keyChanged = !endOfList && ranges.get(i).stateKey() != currentKey;
if (endOfList || keyChanged) {
applyStateKey(currentKey);
flushGroup(ranges, groupStart, i);
if (!endOfList) {
currentKey = ranges.get(i).stateKey();
groupStart = i;
}
}
}
}
/**
* Flush a group of ranges sharing the same state key.
* Subgroups by (drawMode, format flags) since those must match for merged draws.
*/
private static void flushGroup(List<DeferredBatchTessellator.DrawRange> ranges, int from, int to) {
int i = from;
while (i < to) {
final DeferredBatchTessellator.DrawRange first = ranges.get(i);
final int drawMode = first.drawMode();
final int flags = first.flags();
int totalBytes = 0;
int totalVertices = 0;
int subEnd = i;
while (subEnd < to) {
final DeferredBatchTessellator.DrawRange e = ranges.get(subEnd);
if (e.drawMode() != drawMode || e.flags() != flags) break;
totalBytes += e.byteLength();
totalVertices += e.vertexCount();
subEnd++;
// Don't try to merge strips, fans, loops or polygons
if (drawMode == GL11.GL_TRIANGLE_STRIP || drawMode == GL11.GL_TRIANGLE_FAN || drawMode == GL11.GL_LINE_STRIP ||
drawMode == GL11.GL_LINE_LOOP || drawMode == GL11.GL_QUAD_STRIP || drawMode == GL11.GL_POLYGON)
break;
}
TessellatorStreamingDrawer.drawPackedBatch(batchTessellator, ranges, i, subEnd, totalBytes, totalVertices, drawMode, flags);
i = subEnd;
}
}
/**
* Pack current GLSM state into a long key for grouping.
* Layout: [tex1En:1][tex0En:1][textureId:20][srcRgb:12][dstRgb:12][blendEnabled:1][depthMask:1] = 48 bits
* GL blend constants (e.g. GL_SRC_ALPHA=0x0302) need 12 bits.
*
* NOT captured (uniform within a particle layer bracket — set once before startDrawingQuads,
* not changed by individual particles):
* - depth test enable (always GL_LEQUAL for particles)
* - alpha test func/ref (set per-layer, not per-particle)
* - separate blend alpha factors (srcAlpha/dstAlpha) — particles use glBlendFunc not glBlendFuncSeparate
* - fog state (mode, color, start/end — constant for the frame)
*/
static long captureStateKey() {
final int textureId = GLStateManager.getTextures().getTextureUnitBindings(0).getBinding();
final int srcRgb = GLStateManager.getBlendState().getSrcRgb();
final int dstRgb = GLStateManager.getBlendState().getDstRgb();
final boolean blendEnabled = GLStateManager.getBlendMode().isEnabled();
final boolean depthMask = GLStateManager.getDepthState().isEnabled();
final boolean tex0Enabled = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled();
final boolean tex1Enabled = GLStateManager.getTextures().getTextureUnitStates(1).isEnabled();
return packStateKey(textureId, srcRgb, dstRgb, blendEnabled, depthMask, tex0Enabled, tex1Enabled);
}
static long packStateKey(int textureId, int srcRgb, int dstRgb, boolean blendEnabled, boolean depthMask, boolean tex0Enabled, boolean tex1Enabled) {
return ((long) (textureId & 0xFFFFF) << 26)
| ((long) (srcRgb & 0xFFF) << 14)
| ((long) (dstRgb & 0xFFF) << 2)
| (blendEnabled ? 2L : 0L)
| (depthMask ? 1L : 0L)
| (tex0Enabled ? (1L << 46) : 0L)
| (tex1Enabled ? (1L << 47) : 0L);
}
static int unpackTextureId(long key) { return (int) ((key >> 26) & 0xFFFFF); }
static int unpackSrcRgb(long key) { return (int) ((key >> 14) & 0xFFF); }
static int unpackDstRgb(long key) { return (int) ((key >> 2) & 0xFFF); }
static boolean unpackBlendEnabled(long key) { return (key & 2L) != 0; }
static boolean unpackDepthMask(long key) { return (key & 1L) != 0; }
static boolean unpackTex0Enabled(long key) { return (key & (1L << 46)) != 0; }
static boolean unpackTex1Enabled(long key) { return (key & (1L << 47)) != 0; }
static void applyStateKey(long key) {
final int textureId = unpackTextureId(key);
final int srcRgb = unpackSrcRgb(key);
final int dstRgb = unpackDstRgb(key);
final boolean blendEnabled = unpackBlendEnabled(key);
final boolean depthMask = unpackDepthMask(key);
final boolean tex0Enabled = unpackTex0Enabled(key);
final boolean tex1Enabled = unpackTex1Enabled(key);
GLStateManager.getTextures().getTextureUnitStates(0).setEnabled(tex0Enabled);
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(tex1Enabled);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
GLStateManager.glBlendFunc(srcRgb, dstRgb);
if (blendEnabled) {
GLStateManager.enableBlend();
} else {
GLStateManager.disableBlend();
}
GLStateManager.glDepthMask(depthMask);
}
}
@@ -1,54 +0,0 @@
package com.gtnewhorizons.angelica.client.rendering;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.renderer.texture.AbstractTexture;
import org.jetbrains.annotations.Nullable;
public class TextureTracker {
/**
* Adapted from Iris for use in GLSM
*/
public static final TextureTracker INSTANCE = new TextureTracker();
private final Int2ObjectMap<AbstractTexture> textures = new Int2ObjectOpenHashMap<>();
private boolean lockBindCallback;
private TextureTracker() {
}
public void trackTexture(int id, AbstractTexture texture) {
textures.put(id, texture);
}
@Nullable
public AbstractTexture getTexture(int id) {
return textures.get(id);
}
public void onBindTexture(int id) {
if (lockBindCallback) {
return;
}
if (GLStateManager.getActiveTextureUnit() == 0) {
lockBindCallback = true;
if (GLSMHooks.TEXTURE_BIND.hasListeners()) {
GLSMHooks.textureBindEvent.textureId = id;
GLSMHooks.TEXTURE_BIND.post(GLSMHooks.textureBindEvent);
}
// Reset texture state
RenderSystem.bindTextureToUnit(0, id);
lockBindCallback = false;
}
}
public void onDeleteTexture(int id) {
textures.remove(id);
}
}
@@ -21,9 +21,9 @@ public class DriverCompatabilityCheck {
* Should be called after GL context is created and GLStateManager is initialized.
*/
public static void checkDriverCompatibility() {
final String vendor = GLStateManager.glGetString(GL11.GL_VENDOR);
final String renderer = GLStateManager.glGetString(GL11.GL_RENDERER);
final String version = GLStateManager.glGetString(GL11.GL_VERSION);
final String vendor = GL11.glGetString(GL11.GL_VENDOR);
final String renderer = GL11.glGetString(GL11.GL_RENDERER);
final String version = GL11.glGetString(GL11.GL_VERSION);
LOGGER.info("OpenGL Vendor: {}", vendor);
LOGGER.info("OpenGL Renderer: {}", renderer);
@@ -50,6 +50,6 @@ public class DriverCompatabilityCheck {
}
LOGGER.info("Enabling workaround for NVIDIA threaded optimizations");
GLStateManager.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
GL11.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
}
}
@@ -11,6 +11,7 @@ import org.apache.logging.log4j.Logger;
public class ModStatus {
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
public static BackhandReflectionCompat backhandCompat;
public static boolean isBetterCrashesLoaded;
public static boolean isNEIDLoaded;
public static boolean isNEIDMetadataExtended;
@@ -26,8 +27,9 @@ public class ModStatus {
public static boolean isBaublesLoaded;
public static boolean isFluidLoggedLoaded;
public static void preInit() {
if (Loader.isModLoaded("backhand")) {
public static void preInit(){
isBackhandLoaded = Loader.isModLoaded("backhand");
if(isBackhandLoaded) {
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
}
@@ -1,33 +1,34 @@
package com.gtnewhorizons.angelica.compat.bettercrashes;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderGlobalExt;
import cpw.mods.fml.common.Optional;
import net.minecraft.client.Minecraft;
import vfyjxf.bettercrashes.utils.StateManager;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
@Optional.Interface(modid = "angelica", iface = "vfyjxf.bettercrashes.utils.StateManager.IResettable")
public class BetterCrashesCompat implements StateManager.IResettable {
private static BetterCrashesCompat INSTANCE;
public BetterCrashesCompat() {
AngelicaMod.LOGGER.info("BetterCrashesCompat initialized");
this.register();
}
public static void init() {
if (INSTANCE == null) {
INSTANCE = new BetterCrashesCompat();
}
}
public BetterCrashesCompat() {
LOGGER.info("BetterCrashesCompat initialized");
this.register();
}
@Override
public void resetState() {
AngelicaMod.LOGGER.info("Reloading SodiumRenderer");
LOGGER.info("Reloading SodiumRenderer");
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
AngelicaMod.LOGGER.info("Resetting GLStateManager");
LOGGER.info("Resetting GLStateManager");
GLStateManager.reset();
}
}
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
@@ -10,10 +9,14 @@ public enum InteractionHand {
MAIN_HAND,
OFF_HAND;
public ItemStack getItemInHand(EntityPlayer player) {
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND) { // off hand (requires backhand)
return BackhandReflectionCompat.getOffhandItem(player);
} else { // main hand
InteractionHand() {
}
public ItemStack getItemInHand(EntityPlayer player){
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND){ // off hand (requires backhand)
return ModStatus.backhandCompat.getOffhandItem(player);
}
else { // main hand
return player.getHeldItem();
}
}
@@ -9,7 +9,6 @@ import java.nio.IntBuffer;
import javax.imageio.ImageIO;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import lombok.Getter;
import net.coderbot.iris.Iris;
import org.lwjgl.opengl.GL11;
@@ -55,7 +54,7 @@ public class NativeImage extends BufferedImage {
public void downloadTexture(int level, boolean bl) {
// this.checkAllocated();
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, format.components);
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, format.components);
// GlStateManager._getTexImage(3553, level, format.glFormat, GL11.GL_UNSIGNED_BYTE, this.pixels);
// final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
@@ -64,7 +63,7 @@ public class NativeImage extends BufferedImage {
final IntBuffer buffer = MemoryUtilities.memAllocInt(size);
try {
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
int[] data = new int[size];
buffer.get(data);
@@ -48,11 +48,6 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableMCPatcherForgeFeatures;
@Config.Comment("Replace main menu panorama with modern equivalent.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enablePanoramaBlurShader;
@Config.Comment("Replace cloud renderer with a VBO version.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -117,7 +112,7 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean optimizeWorldUpdateLight;
@Config.Comment("Optimize Texture Animations. [From Hodgepodge]")
@Config.Comment("Speedup Animations. [From Hodgepodge]")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean speedupAnimations;
@@ -224,13 +219,6 @@ public class AngelicaConfig {
})
public static String[] blockCrackFixRenderPassWhitelist__;
@Config.Comment({"List of sprites which should always be treated as translucent.",
"Sprites added to this list will always be considered translucent,",
"",
"Requires texture reload (F3+T) to take effect."})
@Config.DefaultStringList({"jewelrycraft2:blockCrystal"})
public static String[] alwaysTranslucentSprites;
@Config.Comment("Register HardcodedCustomUniforms in Iris Shaders. May help with compatibility in certain shader packs")
@Config.DefaultBoolean(false)
public static boolean enableHardcodedCustomUniforms;
@@ -239,31 +227,6 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
public static boolean defineIsIris;
@Config.Comment("ASM transformer exclusion narrowing for mod compatibility. Disable per-mod if narrowing causes class loading issues.")
public static TransformerCompat transformerCompat = new TransformerCompat();
public static class TransformerCompat {
@Config.Comment("Narrow DragonAPI transformer exclusions to allow GL redirection")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowDragonAPI;
@Config.Comment("Narrow Xaeros Minimap/Worldmap transformer exclusions to allow GL redirection")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowXaeros;
@Config.Comment("Narrow AdvancedLightsabers transformer exclusions to allow GL redirection")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowAdvancedLightsabers;
@Config.Comment("Narrow Alfheim transformer exclusions to allow GL redirection")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowAlfheim;
}
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
@Config.DefaultBoolean(false)
public static boolean useVanillaChunkTracking;
@@ -272,19 +235,4 @@ public class AngelicaConfig {
@Config.DefaultBoolean(false)
public static boolean disableF3Additions;
@Config.Comment("Replaces various FFP uploads with statically allocated VBO's.")
@Config.DefaultBoolean(true)
public static boolean replaceFFPUploads;
@Config.Comment("Pinned OpenGL version an integer (e.g. 46, 41, 33). 0 = auto-detect. [33, 46]. (Disable with disableGLVersionPinning=true)")
@Config.DefaultInt(0)
@Config.RangeInt(min = 0, max = 46)
@Config.RequiresMcRestart
public static int pinnedGLVersion;
@Config.Comment("Disable automatic GL version pinning. When true, always probes from highest on every launch.")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean disableGLVersionPinning;
}
@@ -71,11 +71,6 @@ public class FontConfig {
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float customFontScale;
@Config.Comment("A shadow offset multiplier applied only to the Unicode font.")
@Config.DefaultFloat(0.5F)
@Config.RangeFloat(min = 0F, max = 2F)
public static float fontShadowOffsetUC;
@Config.Comment({"Mods can provide Angelica with a mapping of special characters to either get replaced with " +
"different characters or rendered using the default Unicode font. Setting this to false disables the " +
"replacement, which is useful in cases where custom fonts have direct support for these special characters. ",
@@ -9,16 +9,25 @@ import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL30;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
import static com.gtnewhorizons.angelica.glsm.GLStateManager.glBindBuffer;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glUniform1;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
public abstract class F3Graph {
private static final int NUM_SAMPLES = 240;
@@ -110,11 +119,11 @@ public abstract class F3Graph {
final int uPxPerNs = shader.getUniformLocation("pxPerNs");
final int uLeft = shader.getUniformLocation("left");
vao = GLStateManager.glGenVertexArrays();
vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vertBuf = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
vertBuf = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
try (final MemoryStack stack = stackPush()) {
final FloatBuffer vertices = stack.mallocFloat(VERT_COUNT * VERT_FLOATS);
// Since we use a triangle strip, we only need 4 vertices. The quad extends to the top of the screen so spikes
@@ -127,27 +136,27 @@ public abstract class F3Graph {
});
vertices.rewind();
GLStateManager.glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
}
// Configure vertex attribute in the VAO
GLStateManager.glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
GLStateManager.glEnableVertexAttribArray(aPos);
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
glEnableVertexAttribArray(aPos);
GLStateManager.glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
final Minecraft mc = Minecraft.getMinecraft();
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
// Load initial value for uniforms
GLStateManager.glUniform1f(uFBWidth, mc.displayWidth);
GLStateManager.glUniform1f(uFBHeight, mc.displayHeight);
GLStateManager.glUniform1f(uScaleFactor, sr.getScaleFactor());
GLStateManager.glUniform1i(uHeadIdx, samplesHead);
GLStateManager.glUniform1(uSamples, sampleBuf);
GLStateManager.glUniform1f(uPxPerNs, pxPerNs);
GLStateManager.glUniform1i(uLeft, left ? 1 : 0); // this is how you load bool uniforms
glUniform1f(uFBWidth, mc.displayWidth);
glUniform1f(uFBHeight, mc.displayHeight);
glUniform1f(uScaleFactor, sr.getScaleFactor());
glUniform1i(uHeadIdx, samplesHead);
glUniform1(uSamples, sampleBuf);
glUniform1f(uPxPerNs, pxPerNs);
glUniform1i(uLeft, left ? 1 : 0); // this is how you load bool uniforms
ShaderProgram.clear();
}
@@ -196,11 +205,11 @@ public abstract class F3Graph {
shader.use();
// Load uniforms
GLStateManager.glUniform1f(uFBWidth, mc.displayWidth);
GLStateManager.glUniform1f(uFBHeight, mc.displayHeight);
GLStateManager.glUniform1f(uScaleFactor, sr.getScaleFactor());
GLStateManager.glUniform1i(uHeadIdx, samplesHead);
GLStateManager.glUniform1(uSamples, sampleBuf);
glUniform1f(uFBWidth, mc.displayWidth);
glUniform1f(uFBHeight, mc.displayHeight);
glUniform1f(uScaleFactor, sr.getScaleFactor());
glUniform1i(uHeadIdx, samplesHead);
glUniform1(uSamples, sampleBuf);
GLStateManager.glBindVertexArray(vao);
@@ -4,10 +4,8 @@ import baubles.common.lib.PlayerHandler;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
import com.gtnewhorizons.angelica.api.BlockLightProvider;
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
@@ -53,18 +51,12 @@ public class DynamicLights {
private static final double MAX_RADIUS = 7.75;
private static final double MAX_RADIUS_SQUARED = MAX_RADIUS * MAX_RADIUS;
private final Set<IDynamicLightSource> dynamicLightSources = new ObjectOpenHashSet<>();
private IDynamicLightSource[] lightSourceArray = new IDynamicLightSource[0];
private final ReentrantReadWriteLock lightSourcesLock = new ReentrantReadWriteLock();
private volatile int lightSourceCount = 0;
private final ChunkRebuildManager chunkRebuildManager = new ChunkRebuildManager();
private long lastUpdate = System.currentTimeMillis();
private int lastUpdateCount = 0;
/** Must be called under write lock. */
private void rebuildLightSourceArray() {
this.lightSourceArray = this.dynamicLightSources.toArray(new IDynamicLightSource[0]);
}
public static DynamicLights get() {
if (instance == null)
instance = new DynamicLights();
@@ -100,7 +92,7 @@ public class DynamicLights {
if (!isEnabled())
return;
final long now = System.currentTimeMillis();
long now = System.currentTimeMillis();
if (now >= this.lastUpdate + Mode.getDelay()) {
this.lastUpdate = now;
this.lastUpdateCount = 0;
@@ -111,7 +103,7 @@ public class DynamicLights {
int worldTick = 0;
if (AdaptiveTickingEnabled) {
final Minecraft mc = Minecraft.getMinecraft();
Minecraft mc = Minecraft.getMinecraft();
if (mc.theWorld != null && mc.renderViewEntity != null) {
cameraX = mc.renderViewEntity.posX;
cameraY = mc.renderViewEntity.posY + mc.renderViewEntity.getEyeHeight();
@@ -119,15 +111,15 @@ public class DynamicLights {
worldTick = (int) (mc.theWorld.getTotalWorldTime() & 0x7FFFFFFF);
// Calculate look direction from yaw (normalized horizontal direction)
final float yaw = mc.renderViewEntity.rotationYaw;
final double yawRad = Math.toRadians(yaw);
float yaw = mc.renderViewEntity.rotationYaw;
double yawRad = Math.toRadians(yaw);
lookDirX = -Math.sin(yawRad);
lookDirZ = Math.cos(yawRad);
}
}
this.lightSourcesLock.readLock().lock();
for (var lightSource : this.lightSourceArray) {
for (var lightSource : this.dynamicLightSources) {
// Entity type filter
if (!EntityLightConfig.isEntityTypeEnabled(lightSource)) {
continue;
@@ -135,7 +127,7 @@ public class DynamicLights {
// Adaptive ticking filter
if (AdaptiveTickingEnabled) {
final AdaptiveTickMode mode = AdaptiveTickCalculator.calculate(
AdaptiveTickMode mode = AdaptiveTickCalculator.calculate(
lightSource, cameraX, cameraY, cameraZ, lookDirX, lookDirZ);
if (!mode.shouldTickThisFrame(worldTick, lightSource.hashCode())) {
continue;
@@ -157,7 +149,6 @@ public class DynamicLights {
this.lightSourcesLock.writeLock().lock();
if (this.dynamicLightSources.add(lightSource)) {
this.lightSourceCount = this.dynamicLightSources.size();
rebuildLightSourceArray();
}
this.lightSourcesLock.writeLock().unlock();
}
@@ -170,11 +161,17 @@ public class DynamicLights {
public void removeLightSource(@NotNull IDynamicLightSource lightSource) {
this.lightSourcesLock.writeLock().lock();
if (this.dynamicLightSources.remove(lightSource)) {
this.lightSourceCount = this.dynamicLightSources.size();
rebuildLightSourceArray();
if (activeRenderer != null)
lightSource.angelica$scheduleTrackedChunksRebuild(activeRenderer);
var dynamicLightSources = this.dynamicLightSources.iterator();
IDynamicLightSource it;
while (dynamicLightSources.hasNext()) {
it = dynamicLightSources.next();
if (it.equals(lightSource)) {
dynamicLightSources.remove();
this.lightSourceCount = this.dynamicLightSources.size();
if (activeRenderer != null)
lightSource.angelica$scheduleTrackedChunksRebuild(activeRenderer);
break;
}
}
this.lightSourcesLock.writeLock().unlock();
@@ -188,14 +185,13 @@ public class DynamicLights {
public void removeLightSources(@NotNull Predicate<IDynamicLightSource> filter) {
this.lightSourcesLock.writeLock().lock();
final var dynamicLightSources = this.dynamicLightSources.iterator();
var dynamicLightSources = this.dynamicLightSources.iterator();
IDynamicLightSource it;
boolean changed = false;
while (dynamicLightSources.hasNext()) {
it = dynamicLightSources.next();
if (filter.test(it)) {
dynamicLightSources.remove();
changed = true;
this.lightSourceCount = this.dynamicLightSources.size();
if (activeRenderer != null) {
if (it.angelica$getLuminance() > 0)
it.angelica$resetDynamicLight();
@@ -204,10 +200,6 @@ public class DynamicLights {
break;
}
}
if (changed) {
this.lightSourceCount = this.dynamicLightSources.size();
rebuildLightSourceArray();
}
this.lightSourcesLock.writeLock().unlock();
}
@@ -218,7 +210,7 @@ public class DynamicLights {
public void clearLightSources() {
this.lightSourcesLock.writeLock().lock();
final var dynamicLightSources = this.dynamicLightSources.iterator();
var dynamicLightSources = this.dynamicLightSources.iterator();
IDynamicLightSource it;
while (dynamicLightSources.hasNext()) {
it = dynamicLightSources.next();
@@ -230,7 +222,6 @@ public class DynamicLights {
}
}
this.lightSourceCount = 0;
this.lightSourceArray = new IDynamicLightSource[0];
this.lightSourcesLock.writeLock().unlock();
}
@@ -242,7 +233,7 @@ public class DynamicLights {
* @return {@code true} if the light source is tracked, else {@code false}
*/
public boolean containsLightSource(@NotNull IDynamicLightSource lightSource) {
final boolean result;
boolean result;
this.lightSourcesLock.readLock().lock();
result = this.dynamicLightSources.contains(lightSource);
this.lightSourcesLock.readLock().unlock();
@@ -255,7 +246,7 @@ public class DynamicLights {
* @return the number of dynamic light sources emitting light
*/
public int getLightSourcesCount() {
final int result;
int result;
this.lightSourcesLock.readLock().lock();
result = this.dynamicLightSources.size();
@@ -267,7 +258,7 @@ public class DynamicLights {
public double getDynamicLightLevel(int x, int y, int z) {
double result = 0;
this.lightSourcesLock.readLock().lock();
for (var lightSource : this.lightSourceArray) {
for (var lightSource : this.dynamicLightSources) {
result = maxDynamicLightLevel(x, y, z, lightSource, result);
}
this.lightSourcesLock.readLock().unlock();
@@ -281,7 +272,7 @@ public class DynamicLights {
public double getDynamicLightLevel(double x, double y, double z) {
double result = 0;
this.lightSourcesLock.readLock().lock();
for (var lightSource : this.lightSourceArray) {
for (var lightSource : this.dynamicLightSources) {
result = maxDynamicLightLevelExact(x, y, z, lightSource, result);
}
this.lightSourcesLock.readLock().unlock();
@@ -294,19 +285,19 @@ public class DynamicLights {
}
public static double maxDynamicLightLevel(int x, int y, int z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
final int luminance = lightSource.angelica$getLuminance();
int luminance = lightSource.angelica$getLuminance();
if (luminance > 0) {
// Can't use Entity#squaredDistanceTo because of eye Y coordinate.
final double dx = x - lightSource.angelica$getDynamicLightX() + 0.5;
final double dy = y - lightSource.angelica$getDynamicLightY() + 0.5;
final double dz = z - lightSource.angelica$getDynamicLightZ() + 0.5;
double dx = x - lightSource.angelica$getDynamicLightX() + 0.5;
double dy = y - lightSource.angelica$getDynamicLightY() + 0.5;
double dz = z - lightSource.angelica$getDynamicLightZ() + 0.5;
final double distanceSquared = dx * dx + dy * dy + dz * dz;
double distanceSquared = dx * dx + dy * dy + dz * dz;
// 7.75 because else we would have to update more chunks and that's not a good idea.
// 15 (max range for blocks) would be too much and a bit cheaty.
if (distanceSquared <= MAX_RADIUS_SQUARED) {
final double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
final double lightLevel = multiplier * (double) luminance;
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
double lightLevel = multiplier * (double) luminance;
if (lightLevel > currentLightLevel) {
return lightLevel;
}
@@ -320,16 +311,16 @@ public class DynamicLights {
* Used for smooth per-vertex lighting.
*/
public static double maxDynamicLightLevelExact(double x, double y, double z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
final int luminance = lightSource.angelica$getLuminance();
int luminance = lightSource.angelica$getLuminance();
if (luminance > 0) {
final double dx = x - lightSource.angelica$getDynamicLightX();
final double dy = y - lightSource.angelica$getDynamicLightY();
final double dz = z - lightSource.angelica$getDynamicLightZ();
double dx = x - lightSource.angelica$getDynamicLightX();
double dy = y - lightSource.angelica$getDynamicLightY();
double dz = z - lightSource.angelica$getDynamicLightZ();
final double distanceSquared = dx * dx + dy * dy + dz * dz;
double distanceSquared = dx * dx + dy * dy + dz * dz;
if (distanceSquared <= MAX_RADIUS_SQUARED) {
final double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
final double lightLevel = multiplier * (double) luminance;
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
double lightLevel = multiplier * (double) luminance;
if (lightLevel > currentLightLevel) {
return lightLevel;
}
@@ -368,11 +359,11 @@ public class DynamicLights {
this.lightSourcesLock.readLock().lock();
try {
for (var source : this.lightSourceArray) {
final double dx = source.angelica$getDynamicLightX() - centerX;
final double dy = source.angelica$getDynamicLightY() - centerY;
final double dz = source.angelica$getDynamicLightZ() - centerZ;
final double distSq = dx * dx + dy * dy + dz * dz;
for (IDynamicLightSource source : this.dynamicLightSources) {
double dx = source.angelica$getDynamicLightX() - centerX;
double dy = source.angelica$getDynamicLightY() - centerY;
double dz = source.angelica$getDynamicLightZ() - centerZ;
double distSq = dx * dx + dy * dy + dz * dz;
if (distSq <= searchRadiusSq) {
if (result == null) {
@@ -388,53 +379,6 @@ public class DynamicLights {
return result != null ? result : Collections.emptyList();
}
public int getDynamicLightRGB(double x, double y, double z) {
int accum = 0;
this.lightSourcesLock.readLock().lock();
try {
for (var lightSource : this.lightSourceArray) {
accum = accumulateSourceRGB(x, y, z, lightSource, accum);
}
} finally {
this.lightSourcesLock.readLock().unlock();
}
return accum;
}
public int getDynamicLightRGBFromSources(double x, double y, double z, List<IDynamicLightSource> sources) {
if (sources == null || sources.isEmpty()) return 0;
int accum = 0;
for (int i = 0, size = sources.size(); i < size; i++) {
accum = accumulateSourceRGB(x, y, z, sources.get(i), accum);
}
return accum;
}
private static int accumulateSourceRGB(double x, double y, double z, IDynamicLightSource source, int accum) {
final int luminance = source.angelica$getLuminance();
if (luminance <= 0) return accum;
final int rgb = source.angelica$getLuminanceRGB();
if (rgb == 0) return accum;
final double dx = x - source.angelica$getDynamicLightX();
final double dy = y - source.angelica$getDynamicLightY();
final double dz = z - source.angelica$getDynamicLightZ();
final double distSq = dx * dx + dy * dy + dz * dz;
if (distSq > MAX_RADIUS_SQUARED) return accum;
final double multiplier = 1.0 - Math.sqrt(distSq) / MAX_RADIUS;
final int r = (int) (BlockLightProvider.unpackR(rgb) * multiplier);
final int g = (int) (BlockLightProvider.unpackG(rgb) * multiplier);
final int b = (int) (BlockLightProvider.unpackB(rgb) * multiplier);
return BlockLightProvider.packRGB(
Math.max(BlockLightProvider.unpackR(accum), r),
Math.max(BlockLightProvider.unpackG(accum), g),
Math.max(BlockLightProvider.unpackB(accum), b));
}
public double getDynamicLightLevelFromSources(double x, double y, double z, List<IDynamicLightSource> sources) {
if (sources == null || sources.isEmpty()) {
return 0;
@@ -458,10 +402,10 @@ public class DynamicLights {
// lightmap is (skyLevel << 20 | blockLevel << 4)
// Get vanilla block light level.
final int blockLevel = (lightmap & 0xFFFF) >> 4;
int blockLevel = (lightmap & 0xFFFF) >> 4;
if (dynamicLightLevel > blockLevel) {
// Equivalent to a << 4 bitshift with a little quirk: this one ensure more precision (more decimals are saved).
final int luminance = (int) (dynamicLightLevel * 16.0);
int luminance = (int) (dynamicLightLevel * 16.0);
lightmap &= 0xfff00000;
lightmap |= luminance & 0x000fffff;
}
@@ -540,8 +484,8 @@ public class DynamicLights {
* @param lightSource the light source
*/
public static void updateTracking(@NotNull IDynamicLightSource lightSource) {
final boolean enabled = lightSource.angelica$isDynamicLightEnabled();
final int luminance = lightSource.angelica$getLuminance();
boolean enabled = lightSource.angelica$isDynamicLightEnabled();
int luminance = lightSource.angelica$getLuminance();
if (!enabled && luminance > 0) {
lightSource.angelica$setDynamicLightEnabled(true);
@@ -558,9 +502,9 @@ public class DynamicLights {
* @return the luminance of the item
*/
public static int getLuminanceFromItemStack(@NotNull ItemStack stack) {
final Item item = stack.getItem();
Item item = stack.getItem();
if (item instanceof ItemBlock itemBlock) {
final Block block = itemBlock.field_150939_a;
Block block = itemBlock.field_150939_a;
if (block != null) {
return block.getLightValue();
}
@@ -577,7 +521,7 @@ public class DynamicLights {
if (entity.isBurning()) return 15;
final boolean inWater = entity.isInsideOfMaterial(Material.water);
boolean inWater = entity.isInsideOfMaterial(Material.water);
// TODO only have certain items not glow in water?
if (inWater) return 0;
@@ -602,23 +546,23 @@ public class DynamicLights {
player instanceof IBattlePlayer battlePlayer &&
battlePlayer.battlegear2$isBattlemode()
) {
final ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
}
}
else if (ModStatus.isBackhandLoaded){
final ItemStack offhand = BackhandReflectionCompat.getOffhandItem(player);
ItemStack offhand = ModStatus.backhandCompat.getOffhandItem(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
}
}
if (ModStatus.isBaublesLoaded){
final var playerBaubles = PlayerHandler.getPlayerBaubles(player);
var playerBaubles = PlayerHandler.getPlayerBaubles(player);
if (playerBaubles != null){
for (int i = 0; i < playerBaubles.getSizeInventory(); i++){
final var stack = playerBaubles.getStackInSlot(i);
var stack = playerBaubles.getStackInSlot(i);
if (stack != null){
luminance = Math.max(luminance, getLuminanceFromItemStack(stack));
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.dynamiclights;
import com.gtnewhorizons.angelica.api.BlockLightProvider;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -79,13 +78,4 @@ public interface IDynamicLightSource {
boolean angelica$updateDynamicLight(@NotNull IDynamicLightWorldRenderer renderer);
void angelica$scheduleTrackedChunksRebuild(@NotNull IDynamicLightWorldRenderer renderer);
/**
* Returns the RGB luminance of the light source. Each channel 0-15, packed as {@code (r << 8) | (g << 4) | b}.
* Default implementation returns white light at the scalar luminance level.
*/
default int angelica$getLuminanceRGB() {
final int l = angelica$getLuminance();
return BlockLightProvider.packRGB(l, l, l);
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.client.rendering;
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.embeddedt.embeddium.impl.render.chunk.fog.FogService;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkFogMode;
@@ -1,11 +1,13 @@
package com.gtnewhorizons.angelica.client.rendering;
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.mitchej123.glsm.GLStateManagerService;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.IntBuffer;
@@ -35,81 +37,81 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public String glGetString(int pname) {
return GLStateManager.glGetString(pname);
return GL11.glGetString(pname);
}
// ===================== FRAMEBUFFER OPERATIONS =====================
@Override
public int glGenFramebuffers() {
return GLStateManager.glGenFramebuffers();
return GL30.glGenFramebuffers();
}
@Override
public void glDeleteFramebuffers(int framebuffer) {
GLStateManager.glDeleteFramebuffers(framebuffer);
GL30.glDeleteFramebuffers(framebuffer);
}
@Override
public void glBindFramebuffer(int target, int framebuffer) {
GLStateManager.glBindFramebuffer(target, framebuffer);
GL30.glBindFramebuffer(target, framebuffer);
}
@Override
public int glCheckFramebufferStatus(int target) {
return GLStateManager.glCheckFramebufferStatus(target);
return GL30.glCheckFramebufferStatus(target);
}
@Override
public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
GLStateManager.glFramebufferTexture2D(target, attachment, textarget, texture, level);
GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
// ===================== SHADER OPERATIONS =====================
@Override
public int glCreateShader(int type) {
return GLStateManager.glCreateShader(type);
return GL20.glCreateShader(type);
}
@Override
public void glCompileShader(int shader) {
GLStateManager.glCompileShader(shader);
GL20.glCompileShader(shader);
}
@Override
public int glGetShaderi(int shader, int pname) {
return GLStateManager.glGetShaderi(shader, pname);
return GL20.glGetShaderi(shader, pname);
}
@Override
public String glGetShaderInfoLog(int shader, int maxLength) {
return GLStateManager.glGetShaderInfoLog(shader, maxLength);
return GL20.glGetShaderInfoLog(shader, maxLength);
}
@Override
public void glDeleteShader(int shader) {
GLStateManager.glDeleteShader(shader);
GL20.glDeleteShader(shader);
}
@Override
public int glCreateProgram() {
return GLStateManager.glCreateProgram();
return GL20.glCreateProgram();
}
@Override
public void glAttachShader(int program, int shader) {
GLStateManager.glAttachShader(program, shader);
GL20.glAttachShader(program, shader);
}
@Override
public void glLinkProgram(int program) {
GLStateManager.glLinkProgram(program);
GL20.glLinkProgram(program);
}
@Override
public int glGetProgrami(int program, int pname) {
return GLStateManager.glGetProgrami(program, pname);
return GL20.glGetProgrami(program, pname);
}
@Override
@@ -119,29 +121,29 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void glDeleteProgram(int program) {
GLStateManager.glDeleteProgram(program);
GL20.glDeleteProgram(program);
}
// ===================== UNIFORM/ATTRIBUTE OPERATIONS =====================
@Override
public int glGetUniformLocation(int program, CharSequence name) {
return GLStateManager.glGetUniformLocation(program, name);
return GL20.glGetUniformLocation(program, name);
}
@Override
public void glUniform1i(int location, int value) {
GLStateManager.glUniform1i(location, value);
GL20.glUniform1i(location, value);
}
@Override
public int glGetAttribLocation(int program, CharSequence name) {
return GLStateManager.glGetAttribLocation(program, name);
return GL20.glGetAttribLocation(program, name);
}
@Override
public void glBindAttribLocation(int program, int index, CharSequence name) {
GLStateManager.glBindAttribLocation(program, index, name);
GL20.glBindAttribLocation(program, index, name);
}
// ===================== VAO OPERATIONS =====================
@@ -165,7 +167,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public int glGenBuffers() {
return GLStateManager.glGenBuffers();
return GL15.glGenBuffers();
}
@Override
@@ -175,20 +177,20 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void glDeleteBuffers(int buffer) {
GLStateManager.glDeleteBuffers(buffer);
GL15.glDeleteBuffers(buffer);
}
// ===================== TEXTURE OPERATIONS =====================
@Override
public int glGenTextures() {
return GLStateManager.glGenTextures();
return GL11.glGenTextures();
}
@Override
public void glGenTextures(int[] textures) {
final IntBuffer buffer = getIntArrayBuffer(textures.length);
GLStateManager.glGenTextures(buffer);
IntBuffer buffer = getIntArrayBuffer(textures.length);
GL11.glGenTextures(buffer);
buffer.get(textures);
}
@@ -199,7 +201,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void glDeleteTextures(int[] textures) {
final IntBuffer buffer = getIntArrayBuffer(textures.length);
IntBuffer buffer = getIntArrayBuffer(textures.length);
buffer.put(textures);
buffer.flip();
GLStateManager.glDeleteTextures(buffer);
@@ -222,7 +224,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
GLStateManager.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
GL11.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
}
@Override
@@ -351,7 +353,7 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public void setBoundTexture(int unit, int texture) {
final int currentUnit = GLStateManager.getActiveTextureUnit();
int currentUnit = GLStateManager.getActiveTextureUnit();
if (currentUnit != unit) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
}
@@ -1,10 +1,11 @@
package com.gtnewhorizons.angelica.client.rendering;
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.mitchej123.glsm.RenderSystemService;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
public class AngelicaRenderSystemService implements RenderSystemService {
@@ -113,17 +114,17 @@ public class AngelicaRenderSystemService implements RenderSystemService {
@Override
public void glUniform1i(int location, int value) {
GLStateManager.glUniform1i(location, value);
GL20.glUniform1i(location, value);
}
@Override
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GLStateManager.glUniformMatrix3(location, transpose, value);
GL20.glUniformMatrix3(location, transpose, value);
}
@Override
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
GLStateManager.glUniformMatrix4(location, transpose, value);
GL20.glUniformMatrix4(location, transpose, value);
}
// ===================== THREAD ASSERTIONS =====================
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import net.minecraft.launchwrapper.Launch;
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.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
/**
* Compat transformation for mod shaders running under core profile.
@@ -39,18 +39,19 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
*/
public class CompatShaderTransformer {
private static final Pattern VERSION_PATTERN = Pattern.compile("#version[ \\t]+(\\d+)(?:[ \\t]+(\\w+))?");
private static final Pattern VERSION_PATTERN =
Pattern.compile("#version\\s+(\\d+)(?:\\s+(\\w+))?");
private static final Pattern DEFINE_PATTERN = Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
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",
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
"shadow2D", "gl_FrontLightModelProduct",
"gl_LightSource", "gl_FrontMaterial"
"shadow2D", "gl_FrontLightModelProduct"
);
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
@@ -67,9 +68,14 @@ public class CompatShaderTransformer {
private static final Path DUMP_DIR;
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
static {
DUMP_DIR = Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false")) ? Paths.get("compat_shaders") : null;
boolean isDev = false;
try {
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
isDev = Boolean.TRUE.equals(deobfEnv);
} catch (Exception ignored) {}
DUMP_DIR = (isDev || Boolean.parseBoolean(System.getProperty("angelica.compat.dumpShaders", "false")))
? Paths.get("compat_shaders") : null;
}
private static final int CACHE_SIZE = 32;
@@ -77,9 +83,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();
@@ -105,7 +111,7 @@ public class CompatShaderTransformer {
result = transformInternal(source, isFragment);
cache.put(key, result);
} catch (Exception e) {
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
result = fixupVersion(source);
}
}
@@ -132,7 +138,7 @@ public class CompatShaderTransformer {
declaredVersion = Integer.parseInt(versionMatcher.group(1));
}
final int targetVersion = Math.max(declaredVersion, RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330);
final int targetVersion = Math.max(declaredVersion, 330);
// Pre-parse reserved word renaming — prevents ANTLR parse failures
source = GlslTransformUtils.replaceTexture(source);
@@ -151,26 +157,6 @@ public class CompatShaderTransformer {
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
}
// gl_LightSource[i] → struct + uniform array (both via injectFunction to keep struct before uniform)
if (source.contains("gl_LightSource")) {
transformer.injectFunction("struct angelica_LightSourceParameters {"
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
+ "};");
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
transformer.rename("gl_LightSource", "angelica_LightSource");
}
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
if (source.contains("gl_FrontMaterial")) {
transformer.injectFunction("struct angelica_MaterialParameters {"
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
+ "};");
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
}
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
@@ -254,9 +240,7 @@ 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))");
}
/**
@@ -299,9 +283,10 @@ 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));");
}
/**
@@ -348,7 +333,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) {
GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
}
}
@@ -0,0 +1,306 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
/**
* Per-program compat uniform location cache and upload manager.
*
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader programs.
*/
public class CompatUniformManager {
// Uniform location indices within the cached array (package-private for tests)
static final int LOC_MODELVIEW = 0;
static final int LOC_MODELVIEW_INVERSE = 1;
static final int LOC_PROJECTION = 2;
static final int LOC_PROJECTION_INVERSE = 3;
static final int LOC_NORMAL = 4;
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
static final int LOC_FOG_DENSITY = 6;
static final int LOC_FOG_START = 7;
static final int LOC_FOG_END = 8;
static final int LOC_FOG_COLOR = 9;
static final int LOC_IRIS_MODELVIEW = 10;
static final int LOC_IRIS_PROJECTION = 11;
static final int LOC_IRIS_NORMAL = 12;
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
static final int LOC_ALPHA_TEST_REF = 14;
static final int LOC_SCENE_COLOR = 15;
static final int LOC_CLIP_PLANES = 16;
static final int LOC_CLIP_PLANES_ENABLED = 17;
static final int LOC_COUNT = 18;
private static final String[] UNIFORM_NAMES = {
"angelica_ModelViewMatrix",
"angelica_ModelViewMatrixInverse",
"angelica_ProjectionMatrix",
"angelica_ProjectionMatrixInverse",
"angelica_NormalMatrix",
"angelica_LightmapTextureMatrix",
"angelica_FogDensity",
"angelica_FogStart",
"angelica_FogEnd",
"angelica_FogColor",
"iris_ModelViewMatrix",
"iris_ProjectionMatrix",
"iris_NormalMatrix",
"iris_LightmapTextureMatrix",
"angelica_currentAlphaTest",
"angelica_SceneColor",
"angelica_ClipPlane[0]",
"angelica_ClipPlanesEnabled",
};
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
// Reusable NIO buffers for upload
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
private static final Matrix3f normalMatrix = new Matrix3f();
private static final Matrix4f scratchMatrix = new Matrix4f();
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
private static final FloatBuffer lightmapMatrixBuf;
static {
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
}
// Dirty tracking: skip uploads when state hasn't changed and program is the same
private static int lastProgram = -1;
private static int lastMvGen = -1;
private static int lastProjGen = -1;
private static int lastTexMatGen = -1;
private static int lastFragmentGen = -1;
private static int lastLightingGen = -1;
private static int lastClipPlaneGen = -1;
private CompatUniformManager() {}
public static void onLinkProgram(int program) {
int[] locs = new int[LOC_COUNT];
boolean hasAny = false;
for (int i = 0; i < LOC_COUNT; i++) {
locs[i] = GL20.glGetUniformLocation(program, UNIFORM_NAMES[i]);
if (locs[i] != -1) hasAny = true;
}
if (hasAny) {
programLocations.put(program, locs);
LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
public static void onUseProgram(int program) {
if (program == 0) return;
int[] locs = programLocations.get(program);
if (locs == null) return;
final boolean programChanged = program != lastProgram;
lastProgram = program;
// Matrix uniforms — skip if generation unchanged and same program
final int mvGen = GLStateManager.mvGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color) — skip if lighting state unchanged
if (locs[LOC_SCENE_COLOR] != -1) {
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
lastLightingGen = litGen;
uploadSceneColor(locs);
}
}
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
if (mvChanged) {
// ModelView
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
mv.get(mat4Buf);
if (locs[LOC_MODELVIEW] != -1) {
GL20.glUniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW] != -1) {
GL20.glUniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
}
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
GL20.glUniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
mv.normal(normalMatrix);
normalMatrix.get(mat3Buf);
if (locs[LOC_NORMAL] != -1) {
GL20.glUniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
}
if (locs[LOC_IRIS_NORMAL] != -1) {
GL20.glUniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
}
if (projChanged) {
// Projection
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
proj.get(mat4Buf);
if (locs[LOC_PROJECTION] != -1) {
GL20.glUniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION] != -1) {
GL20.glUniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
}
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
GL20.glUniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
}
if (texMatChanged) {
// Iris lightmap texture matrix (constant)
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
GL20.glUniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
}
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
GL20.glUniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
}
}
}
private static void uploadFragmentUniforms(int[] locs) {
// Fog uniforms
FogStateStack fog = GLStateManager.getFogState();
if (locs[LOC_FOG_DENSITY] != -1) {
GL20.glUniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
}
if (locs[LOC_FOG_START] != -1) {
GL20.glUniform1f(locs[LOC_FOG_START], fog.getStart());
}
if (locs[LOC_FOG_END] != -1) {
GL20.glUniform1f(locs[LOC_FOG_END], fog.getEnd());
}
if (locs[LOC_FOG_COLOR] != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y)
.put((float) fog.getFogColor().z).put(fog.getFogAlpha());
vec4Buf.flip();
GL20.glUniform4(locs[LOC_FOG_COLOR], vec4Buf);
}
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
if (locs[LOC_ALPHA_TEST_REF] != -1) {
final float ref;
if (!GLStateManager.getAlphaTest().isEnabled()
|| GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
ref = -1.0f;
} else {
ref = GLStateManager.getAlphaState().getReference();
}
GL20.glUniform1f(locs[LOC_ALPHA_TEST_REF], ref);
}
}
/**
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a
* Same computation as {@code Uniforms.uploadLightingPreComputed()}.
*/
private static void uploadSceneColor(int[] locs) {
final MaterialState mat = GLStateManager.getFrontMaterial();
final LightModelState lm = GLStateManager.getLightModel();
vec4Buf.clear();
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
.put(mat.diffuse.w);
vec4Buf.flip();
GL20.glUniform4(locs[LOC_SCENE_COLOR], vec4Buf);
}
private static void uploadClipPlanes(int[] locs) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
GL20.glUniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
GL20.glUniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
}
public static void onDeleteProgram(int program) {
programLocations.remove(program);
}
public static boolean hasProgram(int program) {
return programLocations.containsKey(program);
}
public static int[] getLocations(int program) {
return programLocations.get(program);
}
}
@@ -1,5 +1,6 @@
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;
@@ -7,7 +8,6 @@ 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;
@@ -19,6 +19,8 @@ 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;
@@ -47,6 +49,8 @@ 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");
@@ -56,7 +60,7 @@ public class DisplayListManager {
static {
LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.logDisplayListCompilation");
if (LOG_DISPLAY_LIST_COMPILATION) {
GLStateManager.LOGGER.warn("Display list compilation logging ENABLED (-Dangelica.logDisplayListCompilation=true)");
LogManager.getLogger("DisplayListManager").warn("Display list compilation logging ENABLED (-Dangelica.logDisplayListCompilation=true)");
}
}
@@ -109,6 +113,7 @@ 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,
@@ -139,16 +144,6 @@ 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.
*/
@@ -165,7 +160,7 @@ public class DisplayListManager {
// Save pending transform ops for logging (before we clear them)
if (multMatrixSources != null && pendingTransformOps != null) {
if (pendingTransformOps.isEmpty()) {
GLStateManager.LOGGER.warn("flushMatrix: non-identity transform with no tracked ops");
LOGGER.warn("flushMatrix: non-identity transform with no tracked ops");
multMatrixSources.add(Collections.singletonList("(unknown source)"));
} else {
multMatrixSources.add(new ArrayList<>(pendingTransformOps));
@@ -179,6 +174,11 @@ 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,9 +258,6 @@ 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) {
@@ -627,6 +624,9 @@ 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,6 +660,10 @@ public class DisplayListManager {
if (currentRecorder != null) {
currentRecorder.recordMultMatrix(matrix);
}
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
GLStateManager.getMatrixStack().mul(matrix);
}
return;
}
@@ -965,7 +969,7 @@ public class DisplayListManager {
// ==================== DEBUG LOGGING ====================
public static void logCompiledDisplayList(int listId, CompiledDisplayList compiled, StackTraceElement[] stackTrace) {
GLStateManager.LOGGER.debug(getCompiledDisplayListString(listId, compiled, stackTrace));
LOGGER.debug(getCompiledDisplayListString(listId, compiled, stackTrace));
}
public static String getCompiledDisplayListString(int listId, CompiledDisplayList compiled, StackTraceElement[] stackTrace) {
@@ -299,7 +299,8 @@ public class Feature {
));
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
GLStateManager.activeTextureUnit // Active texture unit
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
, GLStateManager.texEnvMode // GL_TEXTURE_ENV_MODE
, GLStateManager.texEnvColor // GL_TEXTURE_ENV_COLOR
// Enable bits for the four texture coordinates
// Border color for each texture image
@@ -313,10 +314,10 @@ public class Feature {
// Current texture bindings (for example, GL_TEXTURE_BINDING_2D) - Below
));
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
// Current Texture Bindings - GL_TEXTURE_BINDING_2D
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.textures.getTexEnvState(i));
// textureAttribs.add(GLStateManager.textures.getInfo(i))
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
@@ -5,7 +5,10 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.ARBDebugOutputCallback;
import org.lwjgl.opengl.EXTBlendColor;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -13,72 +16,155 @@ 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()) {
GLStateManager.LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
return 0;
}
return setupDebugMessageCallbackImpl();
}
public static Throwable filterStackTrace(Throwable throwable, int offset) {
final StackTraceElement[] elems = throwable.getStackTrace();
final StackTraceElement[] filtered = new StackTraceElement[elems.length];
int j = 0;
for (int i = offset; i < elems.length; i++) {
String className = elems[i].getClassName();
if (className == null) {
className = "";
}
filtered[j++] = elems[i];
}
final StackTraceElement[] newElems = new StackTraceElement[j];
System.arraycopy(filtered, 0, newElems, 0, j);
throwable.setStackTrace(newElems);
return throwable;
}
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() {
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 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) {
final String fullMessage = String.format("[GL] %s %s (0x%X) from %s: %s%s", severity, type, id, source, message, buildStackTrace());
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)) {
GLStateManager.LOGGER.error(fullMessage);
} else if ("MEDIUM".equals(severity)) {
GLStateManager.LOGGER.warn(fullMessage);
} else {
GLStateManager.LOGGER.info(fullMessage);
}
}
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() {
return BackendManager.RENDER_BACKEND.setupDebugOutput(
(source, type, id, severity, message) ->
logDebugMessage(id, getDebugSource(source), getDebugType(type), getDebugSeverity(severity), message)
);
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;
}
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);
// 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;
} else {
LOGGER.info("[GL] No debug output implementation is available, cannot return debug info.");
return 0;
}
}
public static int disableDebugMessages() {
return BackendManager.RENDER_BACKEND.disableDebugOutput();
}
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 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";
@@ -88,9 +174,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";
@@ -101,9 +187,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";
@@ -111,9 +197,65 @@ 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);
};
}
public static String getMatrixModeName(int mode) {
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) {
return switch (mode) {
case GL11.GL_MODELVIEW -> "MODELVIEW";
case GL11.GL_PROJECTION -> "PROJECTION";
@@ -121,9 +263,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";
@@ -153,9 +295,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";
@@ -167,9 +309,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";
@@ -188,17 +330,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";
@@ -206,9 +348,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";
@@ -225,9 +367,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";
@@ -250,9 +392,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";
@@ -269,9 +411,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";
@@ -283,9 +425,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";
@@ -299,9 +441,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";
@@ -312,18 +454,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";
@@ -332,9 +474,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";
@@ -344,9 +486,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";
@@ -354,9 +496,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) {
@@ -378,18 +520,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";
@@ -409,9 +551,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";
@@ -423,9 +565,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";
@@ -460,92 +602,74 @@ 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 = BackendManager.RENDER_BACKEND.getInteger(GL43.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
private final String[] groupStack = new String[maxDepth + 1];
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);
@Override
public void nameObject(int id, int object, String name) {
BackendManager.RENDER_BACKEND.objectLabel(id, object, name);
}
@Override
public void nameObject(int id, int object, String name) {
KHRDebug.glObjectLabel(id, object, name);
}
@Override
public void pushGroup(String name) {
if (depth + 1 > maxDepth) {
final StringBuilder sb = new StringBuilder();
sb.append("Debug group stack overflow (maxDepth=").append(maxDepth).append("), current stack:\n");
for (int i = 0; i < depth; i++) {
sb.append(" [").append(i).append("] ").append(groupStack[i]).append('\n');
}
sb.append(" -> ").append(name).append(" (rejected)");
throw new RuntimeException(sb.toString());
}
groupStack[depth] = name;
BackendManager.RENDER_BACKEND.pushDebugGroup(GL43.GL_DEBUG_SOURCE_APPLICATION, ID, name);
@Override
public void pushGroup(String name) {
depth++;
}
if (depth > maxDepth) {
throw new RuntimeException("Stack overflow");
}
KHRDebug.glPushDebugGroup(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, ID, name);
}
@Override
public void popGroup() {
if (depth - 1 < 0) {
@Override
public void popGroup() {
depth--;
if (depth < 0) {
throw new RuntimeException("Stack underflow");
}
BackendManager.RENDER_BACKEND.popDebugGroup();
depth--;
groupStack[depth] = null;
}
KHRDebug.glPopDebugGroup();
}
@Override
public void debugMessage(String message) {
BackendManager.RENDER_BACKEND.debugMessageInsert(
GL43.GL_DEBUG_SOURCE_APPLICATION,
GL43.GL_DEBUG_TYPE_MARKER,
ID,
GL43.GL_DEBUG_SEVERITY_NOTIFICATION,
message);
KHRDebug.glDebugMessageInsert(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, KHRDebug.GL_DEBUG_TYPE_MARKER, ID, KHRDebug.GL_DEBUG_SEVERITY_NOTIFICATION, message);
}
@Override
public String getObjectLabel(int glProgram, int program) {
if (program == 0) return "";
if(program == 0)
return "";
return BackendManager.RENDER_BACKEND.getObjectLabel(glProgram, program);
return KHRDebug.glGetObjectLabel(glProgram, program, maxNameLength);
}
}
private static class UnsupportedDebugState implements DebugState {
private static class UnsupportedDebugState implements DebugState {
@Override
public void nameObject(int id, int object, String name) {
}
@Override
public void nameObject(int id, int object, String name) {
}
@Override
public void pushGroup(String name) {
}
@Override
public void pushGroup(String name) {
}
@Override
public void popGroup() {
}
@Override
public void popGroup() {
}
@Override
public void debugMessage(String name) {
@@ -558,43 +682,40 @@ public final class GLDebug {
}
}
public static void initDebugState() {
if (BackendManager.RENDER_BACKEND.supportsDebugOutput()) {
debugState = new KHRDebugState();
} else {
debugState = new UnsupportedDebugState();
}
}
public static void initDebugState() {
if (GLStateManager.capabilities.GL_KHR_debug || GLStateManager.capabilities.OpenGL43) {
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,13 +36,7 @@ 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(
// Always rename sample, glsl-transformation-lib doesn't like it
0, List.of(
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample")
),
400, List.of(
new ReservedWordRename(Pattern.compile("\\bsampler\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
)
400, List.of(new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"))
);
public static String replaceTexture(String input) {
@@ -61,8 +55,7 @@ public class GlslTransformUtils {
public static String renameReservedWords(String source, int targetVersion) {
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
// Rename identifiers that become reserved words at or above this GLSL version
if (targetVersion >= entry.getKey()) {
if (targetVersion < entry.getKey()) {
for (var rename : entry.getValue()) {
source = rename.pattern().matcher(source).replaceAll(rename.replacement());
}
@@ -73,7 +66,6 @@ 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;
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -9,7 +8,6 @@ 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;
@@ -48,7 +46,7 @@ public final class QuadConverter {
}
if (eboId == 0) {
eboId = RENDER_BACKEND.genBuffers();
eboId = GL15.glGenBuffers();
}
// Generate index data: 6 ints per quad
@@ -68,8 +66,8 @@ public final class QuadConverter {
ptr += 24;
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
memFree(indexData);
maxQuads = newMaxQuads;
@@ -86,13 +84,11 @@ public final class QuadConverter {
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
final int quadCount = vertexCount / 4;
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
GL11.glDrawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
}
/**
@@ -110,23 +106,23 @@ public final class QuadConverter {
final int prevEbo = GLStateManager.getBoundEBO();
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
scratchEboId = GL15.glGenBuffers();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth — allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
GL15.glBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
GL11.glDrawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
memFree(dst);
}
@@ -144,11 +140,17 @@ public final class QuadConverter {
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
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;
}
// Read source indices from caller's EBO
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
final long srcAddr = memAddress0(src);
// Allocate output as GL_UNSIGNED_INT
@@ -282,12 +284,12 @@ public final class QuadConverter {
*/
public static void destroy() {
if (eboId != 0) {
RENDER_BACKEND.deleteBuffers(eboId);
GL15.glDeleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
RENDER_BACKEND.deleteBuffers(scratchEboId);
GL15.glDeleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
@@ -0,0 +1,523 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import net.coderbot.iris.gl.shader.StandardMacros;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3i;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBBufferStorage;
import org.lwjgl.opengl.ARBClearBufferObject;
import org.lwjgl.opengl.ARBClearTexture;
import org.lwjgl.opengl.ARBShaderImageLoadStore;
import org.lwjgl.opengl.ARBShaderStorageBufferObject;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GL44;
import org.lwjgl.opengl.NVXGpuMemoryInfo;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
* This class is responsible for abstracting calls to OpenGL and asserting that calls are run on the render thread.
*/
public class RenderSystem {
private static final Logger LOGGER = LogManager.getLogger("RenderSystem");
private static DSAAccess dsaState;
private static boolean supportsCompute;
private static boolean supportsImageLoadStore;
private static boolean supportsSSBO;
private static boolean supportsBufferStorage;
private static boolean supportsClearTexture;
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
private RenderSystem() {}
public static void initRenderer() {
try {
if (GLStateManager.capabilities.OpenGL45) {
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
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();
LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {}
if (dsaState == null) {
dsaState = new DSAUnsupported();
LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
}
supportsCompute = supportsCompute();
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
supportsImageLoadStore = GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_shader_image_load_store || GLStateManager.capabilities.GL_EXT_shader_image_load_store;
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
supportsBufferStorage = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage;
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
// Cache maximum image units
if (supportsImageLoadStore) {
if (GLStateManager.capabilities.OpenGL42) {
maxImageUnits = GL11.glGetInteger(GL42.GL_MAX_IMAGE_UNITS);
} else if (GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
maxImageUnits = GL11.glGetInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS);
} else {
maxImageUnits = GL11.glGetInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
}
} else {
maxImageUnits = 0;
}
// Cache maximum SSBO bindings
if (supportsSSBO) {
if (GLStateManager.capabilities.OpenGL43) {
maxSSBOBindings = GL11.glGetInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
} else {
maxSSBOBindings = GL11.glGetInteger(ARBShaderStorageBufferObject.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
}
} else {
maxSSBOBindings = 0;
}
// Check for sampler objects support (GL 3.3+ or ARB extension)
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33
|| GLStateManager.capabilities.GL_ARB_sampler_objects;
if (supportsSamplerObjects) {
samplers = new int[GL11.glGetInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
}
maxGlslVersion = Integer.parseInt(StandardMacros.getGlVersion(GL20.GL_SHADING_LANGUAGE_VERSION));
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
LOGGER.info("Max GLSL version: {}, GPU Shader4: {}", maxGlslVersion, supportsGpuShader4);
LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = GL11.glGetInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
GLStateManager.lineWidthMax = 1.0f;
}
}
}
public static void generateMipmaps(int texture, int mipmapTarget) {
dsaState.generateMipmaps(texture, mipmapTarget);
}
public static void bindAttributeLocation(int program, int index, CharSequence name) {
GL20.glBindAttribLocation(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);
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
GL20.glUniformMatrix4(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) {
GL20.glUniform1f(location, v0);
}
public static void uniform1i(int location, int v0) {
GL20.glUniform1i(location, v0);
}
public static void uniform2f(int location, float v0, float v1) {
GL20.glUniform2f(location, v0, v1);
}
public static void uniform2i(int location, int v0, int v1) {
GL20.glUniform2i(location, v0, v1);
}
public static void uniform3f(int location, float v0, float v1, float v2) {
GL20.glUniform3f(location, v0, v1, v2);
}
public static void uniform3i(int location, int v0, int v1, int v2) { GL20.glUniform3i(location, v0, v1, v2); }
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
GL20.glUniform4f(location, v0, v1, v2, v3);
}
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
GL20.glUniform4i(location, v0, v1, v2, v3);
}
public static int getAttribLocation(int programId, String name) {
return GL20.glGetAttribLocation(programId, name);
}
public static int getUniformLocation(int programId, String name) {
return GL20.glGetUniformLocation(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, long pixels_buffer_offset) {
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
GL11.glBindTexture(target, texture);
GL11.glTexImage1D(target, level, internalformat, width, border, format, type, pixels);
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
GL11.glBindTexture(target, texture);
GL12.glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
}
public static String getProgramInfoLog(int program) {
return GL20.glGetProgramInfoLog(program, GL20.glGetProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
public static String getShaderInfoLog(int shader) {
return GL20.glGetShaderInfoLog(shader, GL20.glGetShaderi(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 GL20.glGetActiveUniform(program, index, maxLength, sizeType);
}
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
GL11.glReadPixels(x, y, width, height, format, type, pixels);
}
public static void bufferData(int target, FloatBuffer data, int usage) {
GL15.glBufferData(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) {
GL20.glVertexAttrib4f(index, v0, v1, v2, v3);
}
public static void detachShader(int program, int shader) {
GL20.glDetachShader(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) {
if (GLStateManager.capabilities.OpenGL42) {
GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format);
} else {
EXTShaderImageLoadStore.glBindImageTextureEXT(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) {
GL20.glGetProgram(program, value, storage);
}
public static void dispatchCompute(int workX, int workY, int workZ) {
GL43.glDispatchCompute(workX, workY, workZ);
}
public static void dispatchCompute(Vector3i workGroups) {
GL43.glDispatchCompute(workGroups.x, workGroups.y, workGroups.z);
}
public static void dispatchComputeIndirect(long offset) {
GL43.glDispatchComputeIndirect(offset);
}
public static void bindBuffer(int target, int buffer) {
GLStateManager.glBindBuffer(target, buffer);
}
public static void memoryBarrier(int barriers) {
if (supportsCompute) {
GL42.glMemoryBarrier(barriers);
}
}
public static boolean supportsBufferBlending() {
return GLStateManager.capabilities.GL_ARB_draw_buffers_blend || GLStateManager.capabilities.OpenGL40;
}
public static void disableBufferBlend(int buffer) {
GL30.glDisablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void enableBufferBlend(int buffer) {
GL30.glEnablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
GL40.glBlendFuncSeparatei(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 GL15.glGenBuffers();
}
public static void bindBufferBase(int target, int index, int buffer) {
GL30.glBindBufferBase(target, index, buffer);
}
public static void bufferStorage(int target, long size, int flags) {
if (GLStateManager.capabilities.OpenGL44) {
GL44.glBufferStorage(target, size, flags);
} else if (GLStateManager.capabilities.GL_ARB_buffer_storage) {
ARBBufferStorage.glBufferStorage(target, size, flags);
} else {
// Fallback: use mutable storage
GL15.glBufferData(target, size, GL15.GL_DYNAMIC_DRAW);
}
}
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
buf.asIntBuffer().put(data);
if (GLStateManager.capabilities.OpenGL43) {
GL43.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
} else if (GLStateManager.capabilities.GL_ARB_clear_buffer_object) {
ARBClearBufferObject.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
}
}
public static void deleteBuffers(int buffer) {
GL15.glDeleteBuffers(buffer);
}
public static long getVRAM() {
if (GLStateManager.capabilities.GL_NVX_gpu_memory_info) {
return GL11.glGetInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1024L;
} else {
// Default to 4GB if we can't query VRAM
return 4294967296L;
}
}
public static void clearTexImage(int texture, int target, int level, int format, int type) {
if (GLStateManager.capabilities.OpenGL44) {
GL44.glClearTexImage(texture, level, format, type, (ByteBuffer) null);
} else {
ARBClearTexture.glClearTexImage(texture, level, format, type, (ByteBuffer) null);
}
}
public static void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
dsaState.textureStorage1D(texture, target, levels, internalFormat, width);
}
public static void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
dsaState.textureStorage2D(texture, target, levels, internalFormat, width, height);
}
public static void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
dsaState.textureStorage3D(texture, target, levels, internalFormat, width, height, depth);
}
public static int getMaxGlslVersion() {
return maxGlslVersion;
}
public static boolean supportsGpuShader4() {
return supportsGpuShader4;
}
public static boolean supportsSnormFormats() {
return GLStateManager.capabilities.OpenGL31;
}
public static boolean supportsPackedFloatRenderable() {
return GLStateManager.capabilities.OpenGL30 && maxGlslVersion >= 130;
}
public static boolean supportsTesselation() {
return supportsTesselation;
}
public static boolean supportsSamplerObjects() {
return supportsSamplerObjects;
}
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return GL33.glGenSamplers();
}
public static void destroySampler(int sampler) {
if (!supportsSamplerObjects || sampler == 0) return;
GL33.glDeleteSamplers(sampler);
}
public static void samplerParameteri(int sampler, int pname, int param) {
if (!supportsSamplerObjects || sampler == 0) return;
GL33.glSamplerParameteri(sampler, pname, param);
}
public static void bindSamplerToUnit(int unit, int sampler) {
if (!supportsSamplerObjects) return;
if (samplers[unit] == sampler) return;
GL33.glBindSampler(unit, sampler);
samplers[unit] = sampler;
}
public static void unbindAllSamplers() {
if (!supportsSamplerObjects) return;
for (int i = 0; i < samplers.length; i++) {
if (samplers[i] != 0) {
GL33.glBindSampler(i, 0);
samplers[i] = 0;
}
}
}
}
@@ -1,4 +1,4 @@
package com.gtnewhorizons.angelica.client.rendering;
package com.gtnewhorizons.angelica.glsm;
import net.minecraft.block.Block;
@@ -2,6 +2,8 @@ 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;
@@ -18,6 +20,8 @@ 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
@@ -49,7 +53,7 @@ class TransformOptimizer {
// After Pop, GL state is restored to the pushed value so lastEmitted should also reflect this
lastEmitted.set(accumulated);
} else {
GLStateManager.LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
accumulated.identity();
lastEmitted.identity();
}
Loaded 100 of 451 files, more files were not shown because too many files have changed in this diff. Show more